You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Aggregate independent checks to report all failures at once (#138)
* Report every independent check failure, and gate the settled decisions
Two gaps ported from the tooling in alexkroman/agent, both about a red run
telling you everything it knows instead of one thing at a time.
check.sh aggregates its read-only checks. Everything before `swift test` is
independent and non-mutating, so there was no ordering reason for the first
failure to hide the rest — but under a plain `set -e` it did: one pending
swift-format reflow ended the run before swiftlint, actionlint, zizmor,
prettier, markdownlint, shellcheck, shfmt, ruff and pytest had said anything.
That is the same complaint PR #128's reordering comment makes about the build
("their findings arrived one red run at a time"); reordering fixed only the half
that was the build's fault. `run_check` now records a failure and continues, and
an exit trap reports the tally. The trap rather than a block at the bottom is
load-bearing: the fail-fast region sits between the two, so `swift test` failing
under errexit would exit straight past a closing summary and throw away
everything already collected. swiftlint analyze and periphery are aggregated for
the same reason at higher cost — each takes minutes, and periphery runs its own
xcodebuild. The build, coverage gate, sanitizers and integration steps stay
fail-fast, where the dependency is real.
check-invariants.sh mechanizes the grep-decidable half of AGENTS.md's "Settled
decisions" table, which until now was enforced only for as long as a reviewer
remembered it. Twelve rules: AVAudioEngine/installTap capture, a streaming or
on-device path, a client-side cleanup pass, config.language_code, config.prompt,
a keystroke-typing injector, LSUIElement, a KeyboardShortcuts import, a
self-replacing updater, the production Keychain under Tests/, and deprecated
shims. Shape is check-portability.sh's, since both answer "does this tree
contain a construct we decided against?": parallel rule arrays, a --self-test
asserting each pattern still matches its own probe, and a `// invariant-ok:`
escape hatch. Two additions to that shape — negative probes drawn from real
lines that sit one character from a rule (the Accessibility prompt dictionary
against the "prompt" wire key, the test keychain against the production one),
and a check that every rule's scope matches tracked files, because a scope typo
kills a rule as quietly as a broken regex.
Scopes do real work: KeytermsWireTests contains
`#expect(object.keys.contains("language_code") == false)` — the invariant being
enforced — so that rule reads Sources/ only. Rules that cannot separate a
violation from correct code stay prose and stay in review; the filler-word
clause is the clearest case, since CleanupInstruction's rewrite instruction
legitimately names filler sounds.
Verified with scripts/check.sh --portable (green), and against deliberately
broken trees: five simultaneous failures all reported in one run, and a
recorded failure still reported when a simulated `swift test` abort ends the
run early.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TACi7k2226no2tVcR3YJr1
* Pin each invariant rule to the prose it enforces
The gate shipped in the previous commit looks like it enforces AGENTS.md's
settled-decisions table, but nothing tied a rule to a row — and the rows move.
PR #132 rewrote the config.prompt and language entries three weeks ago. A rule
that outlives its row is the one failure this file cannot survive: it keeps
firing, keeps citing AGENTS.md, and keeps sounding authoritative while enforcing
a decision the project has reversed. That is strictly worse than the prose it
replaced — prose that no longer reflects the design gets read and ignored, a
gate that no longer reflects the design blocks the change that reflects it.
Each rule now carries a verbatim slice of its "Don't" cell and of its bullet in
the guardrails skill, and --self-test asserts both still resolve. The table
anchor is matched inside the table section only (sed-extracted), so a deleted
row cannot keep its pin alive by being mentioned in passing elsewhere in the
guide; a failed extraction reports itself as a moved heading rather than as
twelve simultaneously-unpinned rules. The skill anchors are deliberately not the
table's wording — the skill says the same things differently, and a pin that
assumed identical text would only be checking that someone had copy-pasted.
This also makes CLAUDE.md's "keep the two in agreement when you change either"
cost one grep instead of a reviewer's memory, for the twelve decisions that are
mechanized. The remaining rows stay a matter of care, and the docs now say which
is which.
The failure message asks for a decision rather than a patch: reworded row,
update the anchor; reversed decision, delete the rule with it.
Verified: deleting a row, rewording a row, dropping a skill bullet, and renaming
the table heading each fail --self-test with the right message; the clean tree
passes; scripts/check.sh --portable is green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TACi7k2226no2tVcR3YJr1
* Tighten the portable-subset paragraph in the check skill
The tool list was a slash-separated run-on inside a parenthetical that had grown
another item, wrapping mid-clause and reading as a fragment. Same facts, same
tools, split into a list and a second paragraph.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TACi7k2226no2tVcR3YJr1
* Drop the duplicated invariants list from the engine guide
The engine README carried a third copy of the settled-decisions list, after
AGENTS.md's table and the project-guardrails skill. Three copies is two chances
to drift, and this one had no mechanism keeping it honest: the rules pinned by
check-invariants.sh anchor to the AGENTS.md row and the skill bullet, so a
change here would have gone unnoticed by everything.
Nothing unique is lost. Every bullet was already covered — the Swift 6
concurrency rule near-verbatim in AGENTS.md's Conventions, dependency-freedom in
several places, and the rest are rows of the settled-decisions table. The
section stays, pointing at the single copy, because an embedder reading this
guide still needs to know the constraints exist.
The inline rationale under `MicCaptureProtocol` is deliberately kept: explaining
why capture is a fresh AVAudioRecorder per session is part of documenting that
seam, not a duplicate of the list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TACi7k2226no2tVcR3YJr1
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claude/skills/check/SKILL.md
+31-14Lines changed: 31 additions & 14 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
---
2
2
name: check
3
-
description: Verify the repo is green by running scripts/check.sh — the same full health check CI runs (swift test + coverage gate, sanitizers, xcodegen drift, app build, swift-format/swiftlint/periphery/prettier/markdownlint/shellcheck/shfmt, site deployability, ruff + pytest over evals/). Use before claiming a change builds, passes, or is ready to commit/PR. Bakes in the macOS-only guard so a Linux/web sandbox flags "verify on a Mac" instead of fabricating a green result; there, scripts/check.sh --portable runs the platform-independent subset (docs/site/scripts/workflows).
3
+
description: Verify the repo is green by running scripts/check.sh — the same full health check CI runs (swift test + coverage gate, sanitizers, xcodegen drift, app build, swift-format/swiftlint/periphery/prettier/markdownlint/shellcheck/shfmt, site deployability, settled-decision invariants, ruff + pytest over evals/). Its read-only checks report together, so a red run names every failure at the bottom rather than stopping at the first. Use before claiming a change builds, passes, or is ready to commit/PR. Bakes in the macOS-only guard so a Linux/web sandbox flags "verify on a Mac" instead of fabricating a green result; there, scripts/check.sh --portable runs the platform-independent subset (docs/site/scripts/workflows).
4
4
---
5
5
6
6
# check — is this green?
@@ -27,19 +27,20 @@ What you CAN run there is the portable subset:
27
27
scripts/check.sh --portable
28
28
```
29
29
30
-
It runs the repo-integrity guards (dependencies, sound catalog, site, shell
| Add an external SPM dependency to the engine | Dependency-free by rule (biggest supply-chain risk); a `check.sh` guard fails on `.package(` in `Package.swift` or a `url:`/`github:` package in `project.yml`. Extend `BlurtEngine` instead. |
Copy file name to clipboardExpand all lines: Sources/BlurtEngine/README.md
+1-8Lines changed: 1 addition & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -280,11 +280,4 @@ Two ways out, neither taken yet: drop it from the public product (its own target
280
280
281
281
## Invariants — don't break these
282
282
283
-
Each of these was tried the other way and reverted; the longer stories are in [AGENTS.md](../../AGENTS.md) and the source comments:
284
-
285
-
-**No external SPM dependencies in the engine.** Foundation/Security/AVFoundation only.
286
-
-**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`.
287
-
-**No `AVAudioEngine`/`installTap` capture path.** Fresh `AVAudioRecorder` per session, resolved at record time.
288
-
-**Paste is always clipboard-based** (save → write → ⌘V → settle → restore), with the copied-to-clipboard degradation for lost targets.
289
-
-**No English-pinning or filler-word clauses**, no reviving `config.prompt`, and no widening the conversation context past the recent dictations and the prior chunk — the app name, window title, field label and selected text stay on the machine. Key terms go on `config.word_boost`, never back into the context turns.
290
-
-**Actors own state** (`DictationSession`, `KeyInjector`, `MicCapture`); the stateless API client stays a `Sendable` struct. Keep new code Swift 6 strict-concurrency clean.
283
+
Each was tried the other way and reverted, and they bind engine code as much as the app's. The list is deliberately not repeated here: it lives once, in [AGENTS.md's Settled decisions](../../AGENTS.md#settled-decisions--dont-reintroduce-these) table, alongside the engine conventions those rules rest on (dependency-free by rule; actors own state; the stateless API client stays a `Sendable` struct; new code Swift 6 strict-concurrency clean). `scripts/check-invariants.sh` mechanizes the subset a regex can decide and fails `check.sh` on them, so a good number are enforced rather than remembered — and it pins each rule to the table row it came from, which only works while there is one row to pin to.
0 commit comments