Skip to content

Commit e577f19

Browse files
alexkromanclaude
andauthored
docs: add an evals README, move the engine guide into the package (#137)
Two documentation moves, both about putting the guide next to the thing it documents. `evals/` had no directory-level README — only the harness's own, one level down. Add one that says what the directory is (offline decision support, the repo's only Python, none of it shipped), indexes the harnesses, and records how check.sh gates it: ruff format/check over evals/ and pytest over the one named test file, all three in the --portable subset. That last detail is the trap a second harness would hit — the pytest step names a file rather than globbing, so new tests have to be added to check.sh in the same commit or they never run. `BLURTENGINE.md` becomes `Sources/BlurtEngine/README.md`, so the engine's developer guide sits in the package it describes and renders as the landing page for anyone browsing there. Its two outbound links are repointed at the root, the inbound links in README.md and AGENTS.md follow it, and the repository map in AGENTS.md gains both files. Package.swift excludes the new README from the target: SwiftPM has no rule for a stray .md inside a target directory, so an undeclared one lands in the unhandled-files list. scripts/check.sh --portable is green (prettier, markdownlint, ruff, pytest); the Swift half needs a Mac, so CI is the authority there. Claude-Session: https://claude.ai/code/session_015BBN7mYHeENP9sJc3TNjZW Co-authored-by: Claude <noreply@anthropic.com>
1 parent 825d4f9 commit e577f19

5 files changed

Lines changed: 69 additions & 12 deletions

File tree

AGENTS.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ and the rebindable dictation trigger is home-grown (`CGEventTap` over a pure sta
4343
package.
4444

4545
For embedding or extending the engine's public API — seam contracts, error table, quick start —
46-
see [`BLURTENGINE.md`](./BLURTENGINE.md). This file covers repo-wide workflow and the _why_ behind
47-
the design; BLURTENGINE.md covers the _what_ of the API surface.
46+
see [`Sources/BlurtEngine/README.md`](./Sources/BlurtEngine/README.md). This file covers repo-wide
47+
workflow and the _why_ behind the design; the engine's README covers the _what_ of the API surface.
4848

4949
## Repository map
5050

5151
```text
5252
Sources/BlurtEngine/ the engine (dependency-free Swift package)
53+
README.md the engine's developer guide (quick start, seams, error table)
5354
Audio/ MicCapture (+meter), SoundPack/Catalog/Store — record cues
5455
Config/ Keychain-backed API key, key terms, developer mode, DefaultsKey +
5556
PersistedSettings (every defaults key, and the reset sweep over them)
@@ -79,10 +80,11 @@ scripts/ check.sh, check-site.sh, bootstrap.sh, dev-build.sh
7980
generate-sounds.swift (regenerates the cues AND
8081
SoundPackCatalog.swift together)
8182
Brewfile Homebrew-managed check.sh tools — the whole toolchain
82-
evals/dictation-prompt/ offline DSPy harness for tuning the dictation API's cleanup
83-
instruction — nothing here ships in the app, but check.sh does
84-
lint (ruff), format-check (ruff format), and test (pytest) it
85-
evals/ruff.toml ruff config for the above — the repo's only Python config
83+
evals/ offline decision support — the repo's only Python, none of it
84+
shipped (see evals/README.md)
85+
dictation-prompt/ DSPy harness for tuning the dictation API's cleanup instruction;
86+
check.sh lints (ruff), format-checks (ruff format), and tests it
87+
ruff.toml ruff config for the above — the repo's only Python config
8688
site/ the GitHub Pages site (html/css, sitemap) — formatted by prettier and
8789
checked for deployability by scripts/check-site.sh
8890
.github/workflows/ check.yml (the gate, macos-26),

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ let package = Package(
99
],
1010
targets: [
1111
.target(
12-
name: "BlurtEngine"
12+
name: "BlurtEngine",
13+
// The engine's developer guide lives next to the code it documents. SwiftPM
14+
// has no rule for a stray .md inside a target, so declare it excluded rather
15+
// than let it land in the target's unhandled-files list.
16+
exclude: ["README.md"]
1317
),
1418
.testTarget(
1519
name: "BlurtEngineTests",

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,9 @@ App/Blurt/ AppKit/SwiftUI shell (Xcode project generated by XcodeG
207207

208208
The engine is a standalone package you can embed to build your own dictation
209209
app — mic capture, dictation-API transcription, and paste-into-the-focused-app behind
210-
three protocol seams, fully stubbed in tests. [`BLURTENGINE.md`](./BLURTENGINE.md)
211-
is the developer guide.
210+
three protocol seams, fully stubbed in tests.
211+
[`Sources/BlurtEngine/README.md`](./Sources/BlurtEngine/README.md) is the
212+
developer guide.
212213

213214
Latency note: perceived speed is mostly bookkeeping. `press()` warms up the
214215
HTTPS connection and kicks off the focused-field context read without awaiting
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Building on BlurtEngine
22

3-
BlurtEngine is the Swift package that powers [Blurt](README.md)'s dictation pipeline: capture speech from the microphone, transcribe it in a single AssemblyAI dictation API call (transcription plus a server-side LLM cleanup rewrite; the request carries the audio and nothing about the user's screen), and paste the polished text into the focused app. This guide is for developers embedding the engine in their own macOS app or extending it inside this repository. For repo-wide conventions and agent workflow, see [AGENTS.md](AGENTS.md).
3+
BlurtEngine is the Swift package that powers [Blurt](../../README.md)'s dictation pipeline: capture speech from the microphone, transcribe it in a single AssemblyAI dictation API call (transcription plus a server-side LLM cleanup rewrite; the request carries the audio and nothing about the user's screen), and paste the polished text into the focused app. This guide is for developers embedding the engine in their own macOS app or extending it inside this repository. For repo-wide conventions and agent workflow, see [AGENTS.md](../../AGENTS.md).
44

55
## What you get
66

@@ -24,7 +24,7 @@ targets: [
2424
]
2525
```
2626

27-
A local checkout works the same way with `.package(path: "../blurt")`. One thing to know before you pin a version: the tags are **Blurt's app releases**, minted by the DMG pipeline in [RELEASE.md](RELEASE.md), not independent engine releases — a patch bump says nothing about whether the engine changed. Read [Embedding outside Blurt](#embedding-outside-blurt) before shipping it inside another app.
27+
A local checkout works the same way with `.package(path: "../blurt")`. One thing to know before you pin a version: the tags are **Blurt's app releases**, minted by the DMG pipeline in [RELEASE.md](../../RELEASE.md), not independent engine releases — a patch bump says nothing about whether the engine changed. Read [Embedding outside Blurt](#embedding-outside-blurt) before shipping it inside another app.
2828

2929
Then compose a session:
3030

@@ -280,7 +280,7 @@ Two ways out, neither taken yet: drop it from the public product (its own target
280280

281281
## Invariants — don't break these
282282

283-
Each of these was tried the other way and reverted; the longer stories are in [AGENTS.md](AGENTS.md) and the source comments:
283+
Each of these was tried the other way and reverted; the longer stories are in [AGENTS.md](../../AGENTS.md) and the source comments:
284284

285285
- **No external SPM dependencies in the engine.** Foundation/Security/AVFoundation only.
286286
- **No streaming STT, no local models, no client-side LLM cleanup pass.** One dictation request per utterance is the architecture; the cleanup rewrite is server-side (the request's `llm` block), and transcription steering belongs in `ConversationContext`.

evals/README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Evals
2+
3+
Offline harnesses that decide things about Blurt's behavior before the decision reaches Swift.
4+
This is the repo's only Python, and **none of it ships in the app** — a run's output is a measured
5+
artifact (today: one instruction string) that a human copies into the engine.
6+
7+
| Directory | What it decides |
8+
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9+
| [`dictation-prompt/`](./dictation-prompt/README.md) | The dictation API's server-side cleanup instruction — `config.llm.instruction`, the string `CleanupInstruction.text` carries. A DSPy/GEPA search over candidate instructions, scored on disfluent-to-clean transcript pairs. |
10+
11+
Each harness documents its own defaults, corpora, and what its numbers can and cannot establish.
12+
Read that README before reading a result: the ceilings here are set by the corpus and by the fact
13+
that we score a stand-in model rather than the service's own rewrite model, and both READMEs say so
14+
where it matters.
15+
16+
## It is gated like shipped code
17+
18+
`scripts/check.sh` runs three checks over this directory, and CI runs the same ones:
19+
20+
| Check | Scope | Fix |
21+
| --------------------- | ------------------------------------- | ------------------------- |
22+
| `ruff format --check` | `evals/` | `ruff format evals/` |
23+
| `ruff check` | `evals/` | `ruff check --fix evals/` |
24+
| `pytest -q` | `evals/dictation-prompt/test_eval.py` | fix the test or the code |
25+
26+
All three are platform-independent, so they also run in `scripts/check.sh --portable` — an eval
27+
change can be verified off-Mac, unlike anything touching Swift. A harness whose own correctness is
28+
unchecked is a bad instrument, which is why non-shipping code is gated at all.
29+
30+
Two things follow from how that gate is wired:
31+
32+
- **`ruff.toml` is scoped to this directory**, not the repo root — Blurt is a Swift project that
33+
happens to contain some Python. Ruff finds it by walking up from each file, so `ruff check evals/`
34+
from the repo root picks it up. The config comments explain the line width and the `src` setting.
35+
- **The pytest step names one file.** A second harness's tests are not picked up by wildcard; add
36+
them to the `pytest` invocation in `scripts/check.sh` in the same commit that adds them, or they
37+
are decoration.
38+
39+
## Running one
40+
41+
The scripts carry [PEP 723](https://peps.python.org/pep-0723/) headers, so `uv run` installs their
42+
dependencies into a throwaway environment — nothing to set up, no repo-level lockfile or virtualenv:
43+
44+
```bash
45+
uv run evals/dictation-prompt/optimize_cleanup_prompt.py --out results.json
46+
```
47+
48+
The tests and the `--dry-run` paths deliberately import nothing outside the standard library
49+
(`pytest` aside), so they stay runnable with plain `python3` and no API key. A real run costs paid
50+
model calls; each harness's README says how many and what the defaults commit you to.

0 commit comments

Comments
 (0)