@andresmassello/uscha 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +21 -4
  2. package/package.json +1 -1
  3. package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +2 -0
  4. package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +2 -0
  5. package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +141 -8
  6. package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +1946 -74
  7. package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +58 -4
  8. package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +2 -0
  9. package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +2 -0
  10. package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +2 -0
  11. package/uscha-kit/.claude/skills/uscha-status/SKILL.md +40 -0
  12. package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +2 -0
  13. package/uscha-kit/.claude-plugin/plugin.json +2 -2
  14. package/uscha-kit/.codex-plugin/plugin.json +1 -1
  15. package/uscha-kit/README.md +229 -5
  16. package/uscha-kit/VERSION +1 -1
  17. package/uscha-kit/install-uscha.py +18 -2
  18. package/uscha-kit/skills/uscha-adr-refine/SKILL.md +2 -0
  19. package/uscha-kit/skills/uscha-characterize/SKILL.md +2 -0
  20. package/uscha-kit/skills/uscha-devloop/SKILL.md +141 -8
  21. package/uscha-kit/skills/uscha-devloop/qa_ledger.py +1946 -74
  22. package/uscha-kit/skills/uscha-discovery/SKILL.md +58 -4
  23. package/uscha-kit/skills/uscha-mirador/SKILL.md +2 -0
  24. package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +2 -0
  25. package/uscha-kit/skills/uscha-rubric/SKILL.md +2 -0
  26. package/uscha-kit/skills/uscha-status/SKILL.md +40 -0
  27. package/uscha-kit/skills/uscha-sysdoc/SKILL.md +2 -0
  28. package/uscha-kit/templates/CLAUDE.md +16 -0
  29. package/uscha-kit/templates/CONSTITUTION.md +45 -0
  30. package/uscha-kit/templates/docs/adr/README.md +15 -0
  31. package/uscha-kit/templates/scripts/smoke-report-example.json +24 -0
  32. package/uscha-kit/uscha.config.json +6 -1
package/README.md CHANGED
@@ -10,6 +10,10 @@ never what was claimed.
10
10
  **[uscha.dev](https://uscha.dev)** — the method, the five rules, the skills, the library
11
11
  (the diamond thesis, how-it-works diagrams, essay, 2-day dev course, reference, paper).
12
12
 
13
+ **New here? Start with [First use: from install to one measured result](docs/FIRST-USE-EN.md)**
14
+ — one machine, one small repo, one criterion closed by a real test, and the merge left to you
15
+ ([en español](docs/FIRST-USE.md)).
16
+
13
17
  ```bash
14
18
  npx --yes @andresmassello/uscha@latest install --target claude # Claude Code
15
19
  npx --yes @andresmassello/uscha@latest install --target codex # Codex
@@ -40,7 +44,7 @@ Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip
40
44
  runtime dependencies). The npm package is a thin router; the canonical installer is
41
45
  `uscha-kit/install-uscha.py`.
42
46
 
43
- **Kit v2.1.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
47
+ **Kit v2.2.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
44
48
  [changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
45
49
  (the per-release changelogs live in the repo, not in the npm tarball)
46
50
 
@@ -76,7 +80,7 @@ and see which file, which test, and when.
76
80
  | `/uscha-mirador` | Bird's-eye HTML dashboard: readiness, trail, acceptance, loops |
77
81
  | `/uscha-status` | One-line progress readout, in chat |
78
82
 
79
- **A measurement engine** (`qa_ledger.py`, 53 subcommands, Python stdlib) that ingests
83
+ **A measurement engine** (`qa_ledger.py`, 56 subcommands, Python stdlib) that ingests
80
84
  evidence from **11 language stacks** — maven, gradle, ant, python, node, go, rust, dotnet,
81
85
  cpp, swift, flutter — and computes a readiness score with hard caps and visible provenance.
82
86
 
@@ -132,6 +136,14 @@ else `doctor` reports `advisory` rather than implying a guard it cannot see.
132
136
  method is enforced by the engine, not by asking the agent to be disciplined.
133
137
  - **Model-agnostic.** The engine never reads tokens, model names or vendor telemetry. Any
134
138
  model-reported number enters through an adapter, never the engine.
139
+ - **Field truth for greenfield.** In a new system every test payload was invented by the
140
+ agent that wrote the code, so `corpus-run` scores a corpus of REAL inputs against the
141
+ real command and persists the percentage as a fact — advisory until the project
142
+ declares a budget, a blocking gate once it does (ADR-046).
143
+ - **Evidence is executed, not narrated.** The smoke run is a report the project's tool
144
+ writes, not a paragraph an agent types: `smoke-ingest` reads `{"checks": [{"name",
145
+ "ok", ...}]}`, a failed check blocks like any fact gate, and an empty or malformed
146
+ report is refused rather than scored (ADR-047).
135
147
 
136
148
  ## The diamond — specs are the source code, end to end
137
149
 
@@ -172,7 +184,7 @@ automatic tool can perform: a human verdict.
172
184
  from the compiled code: 0.815 measured (12 archetypes) — names AND behaviour
173
185
  ```
174
186
 
175
- **What each arrow is, in the engine (kit 1.96.0, 53 subcommands, all measured):**
187
+ **What each arrow is, in the engine (kit 1.96.0, 56 subcommands, all measured):**
176
188
 
177
189
  | Leg | Subcommands | What it establishes |
178
190
  |---|---|---|
@@ -188,7 +200,12 @@ under an oracle the compilers never saw — that is the closed loop working. It
188
200
  1.99.0, when a fourth compiler from a second vendor read one genuinely ambiguous sentence in
189
201
  `transformer` the other way and lost a case the three Claude-family models had agreed on
190
202
  (ADR-042). The number went down because the bench got harder, and the whole point of the arm was
191
- to let it. 0.815 is the mean *recoverability* of the asset from compiled code counting only
203
+ to let it. Since 2.2.0 that headline is a **derived fact**: `qa_ledger.py facts` counts the verdict
204
+ rows of `DIAMOND-BENCH.md` — the report `bench` generates over the committed fixture — into
205
+ `SYSTEM-FACTS.json`, and `facts --check` compares every published copy of it against that count.
206
+ It was added because the project's own homepage said 9/12 for nine releases after the number
207
+ moved, and every one of those releases was green: the claim sat in one HTML element and its noun
208
+ in the next, where the gate could not see it. 0.815 is the mean *recoverability* of the asset from compiled code counting only
192
209
  static and behavioural footing. It read **0.062** until 1.90.0, with the behaviour dimension
193
210
  `UNMEASURED`, because no oracle case carried an AC tag — a named absence, not a zero. The 12
194
211
  bench oracles are now curated per case (`ORACLE-TAGS-CURATED.json`, human-authored; payloads and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andresmassello/uscha",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Spec-driven development for LLM coding agents: 9 skills + a stdlib evidence engine. Facts block, guesses advise; the human approves.",
5
5
  "author": {
6
6
  "name": "Andres Massello",
@@ -19,6 +19,8 @@ phases. **You are NOT a generator. You are an interrogator that distills.** The
19
19
  is in the questions, not in agreeing.
20
20
 
21
21
  <!-- uscha:orientation-block:begin -->
22
+ <!-- uscha kit: 2.2.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
23
+
22
24
  ## First contact (show ONCE, then never again)
23
25
 
24
26
  **Only when this project has no uscha artifacts yet** -- no `QA-LEDGER.json`, no `SPEC.md` or
@@ -19,6 +19,8 @@ what the code DOES, mechanically, by running it — never what it should do.** Y
19
19
  the capture harness; you may NOT create, rename, or edit any `.approved` file.
20
20
 
21
21
  <!-- uscha:orientation-block:begin -->
22
+ <!-- uscha kit: 2.2.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
23
+
22
24
  ## First contact (show ONCE, then never again)
23
25
 
24
26
  **Only when this project has no uscha artifacts yet** -- no `QA-LEDGER.json`, no `SPEC.md` or
@@ -27,6 +27,8 @@ artifacts; these can block) and **self-reported** agent counts (log-step — nar
27
27
  recorded for the retrospective; a measured red always overrides a narrated green).
28
28
 
29
29
  <!-- uscha:orientation-block:begin -->
30
+ <!-- uscha kit: 2.2.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
31
+
30
32
  ## First contact (show ONCE, then never again)
31
33
 
32
34
  **Only when this project has no uscha artifacts yet** -- no `QA-LEDGER.json`, no `SPEC.md` or
@@ -110,10 +112,30 @@ Keep the CONTENT in the conversation's language, but keep the labels (`CLOSED`,
110
112
  only *run* the existing suite.
111
113
  4. **Stop at merge.** You create the PR and confirm CI is green. You do NOT merge.
112
114
  The human owns the merge gate.
113
- 5. **Tracked-markdown protocol.** Before modifying any tracked `.md`
114
- (CLAUDE.md, plan/delta docs, docs/adr), ask the human for the current version first.
115
- Those files carry real progress (checkboxes, notes); never regenerate from scratch.
116
- 6. **The golden is the one artifact you cannot author.** For migration/legacy work,
115
+ 5. **Tracked-markdown protocol: READ the file, never ask for it.** Before modifying any
116
+ tracked `.md` (CLAUDE.md, plan/delta docs, docs/adr), READ the current version out of
117
+ the tree. Those files carry real progress (checkboxes, notes): preserve it and edit in
118
+ place, never regenerate from scratch. The current version of a tracked file is
119
+ INFORMATION the tree already holds, so asking the human for it is noise — you ask the
120
+ human for DECISIONS, never for information you can read (ADR-044).
121
+ 6. **`origin: agent` — a decision the human never made does not enter scope silently.**
122
+ Every acceptance criterion, ADR decision item or HANDOFF rule that YOU introduce
123
+ (rather than one the human answered) carries the marker on its own line:
124
+
125
+ ```
126
+ - [ ] AC-12 — when X then Y. (origin: agent)
127
+ - [ ] AC-12 — when X then Y. (origin: agent, confirmed: 2026-09-07)
128
+ ```
129
+
130
+ Unmarked items are human-origin by default; nothing existing is retro-tagged. An
131
+ unconfirmed item is **not in scope**: do not implement it, do not gate on it, do not
132
+ quote it as agreed. Ask the human to confirm it ONE BY ONE — a package-level "ok" over
133
+ a twenty-item summary confirms nothing — and only then write
134
+ `confirmed: YYYY-MM-DD` on the same line. List every still-unconfirmed item in the PR
135
+ body under **Agent-origin, unconfirmed**. `spec-check` names them with file:line and
136
+ `readiness` counts them on their own line; both are ADVISORY and neither changes an
137
+ exit code or the score.
138
+ 7. **The golden is the one artifact you cannot author.** For migration/legacy work,
117
139
  `.approved` fixtures are field truth captured from the ORIGINAL code and approved by
118
140
  a HUMAN. You emit `.received` and stop; you never write, edit or rename `.approved`
119
141
  (a `PreToolUse` hook denies the write — INV-GOLDEN-01).
@@ -126,6 +148,12 @@ QL="./.claude/skills/uscha-devloop/qa_ledger.py" # instalac
126
148
  [ -f "$QL" ] || QL="$HOME/plugins/uscha/skills/uscha-devloop/qa_ledger.py" # Codex plugin install
127
149
  [ -f "$QL" ] || QL="$HOME/.claude/skills/uscha-devloop/qa_ledger.py" # Claude global install
128
150
  python3 $QL init --config uscha.config.json
151
+
152
+ # adding a service LATER never re-runs init: that would build a NEW ledger and drop every
153
+ # step. --add-repo appends and re-seals the checksum, leaving every existing repo untouched
154
+ # (kit 2.2.0). The new repo has no evidence yet, so it reads UNMEASURED and the repo
155
+ # averages read lower until its first snapshot — that is absence, not a regression:
156
+ python3 $QL init --add-repo <NAME> --path <path> --type <type> --test-command "<cmd>"
129
157
  ```
130
158
 
131
159
  ## Execution policy line (before every phase)
@@ -368,7 +396,9 @@ python3 $QL log-step --repo <REPO> --tool <code-review|judgment-day|improve> \
368
396
  weaken the measuring apparatus? did it ADD a secret? — private keys, cloud tokens and
369
397
  key-container files block as facts, kit 1.12.0; generic password/token literals advise;
370
398
  new dependencies advise too — the change-budget rule "no new deps without approval" made
371
- visible, kit 1.30.0)
399
+ visible, kit 1.30.0). A rename is reported as a MOVE, never as a deleted test, and `--repo`
400
+ SCOPES the diff to that repo's path, so a monorepo sibling's hunks are neither this repo's
401
+ findings nor this repo's exit code (kit 2.2.0)
372
402
  and, for migration work, golden-diff (does behavior still match the human-approved
373
403
  baseline?). Then PERSIST each verdict with `log-gate` — a failing fact gate blocks
374
404
  convergence and caps readiness ≤65 through the ledger:
@@ -397,6 +427,10 @@ have caught this and what was done>"` — reflection is part of closing, not opt
397
427
  python3 $QL gate-check --from-git --base <base> --repo <REPO> # exit 1 = BLOCKER
398
428
  python3 $QL log-gate --repo <REPO> --iteration <N> --kind gate-check --verdict <pass|fail>
399
429
 
430
+ # CI, when the pipeline has run for this commit (kit 2.2.0). A green run is a FACT and
431
+ # belongs in the ledger; --ref is the receipt that outlives the conversation:
432
+ python3 $QL log-gate --repo <REPO> --iteration <N> --kind ci --verdict <pass|fail|not-run> --ref <run URL or id>
433
+
400
434
  python3 $QL golden-diff [--dir <fixtures-root>] # exit 0 CLEAN · 1 DIVERGE · 2 NOT-RUN
401
435
  python3 $QL log-gate --repo <REPO> --iteration <N> --kind golden-diff \
402
436
  --verdict <pass|fail|not-run> # not-run records the absence — it is never green
@@ -406,6 +440,33 @@ python3 $QL log-gate --repo <REPO> --iteration <N> --kind golden-diff \
406
440
  but when a PIT report EXISTS and fails the gate, persist it the same way:
407
441
  `log-gate --kind pit-check --verdict fail`.)
408
442
 
443
+ **Field truth — the greenfield evidence class (kit 2.2.0, ADR-046).** `characterize` and
444
+ `golden-diff` answer "does it still do what the OLD code did?", which greenfield has no way to
445
+ ask: there is no old code, and every test payload was invented by the agent that wrote the
446
+ subject. `corpus-run` is the evidence class for that gap — REAL inputs with their real expected
447
+ outputs, one JSON object per line (`input`, `expected`, optional `id`), each fed to the command
448
+ on stdin and compared against its trimmed stdout. Run it when the repo declares a corpus
449
+ (`repos[R].corpus`) or when the human hands you one; it is NOT part of the inner loop, since a
450
+ real corpus can be large (schedule it like pit-check). It is **advisory** until the project
451
+ declares a budget — with no `--threshold`, `repos[R].corpus_threshold` or
452
+ `defaults.corpus_threshold`, the percentage is measured and gates nothing, and it never counts as
453
+ an `ok` gate. With one declared, a run under it caps readiness ≤65 and blocks convergence like
454
+ any fact gate. A missing, empty or malformed corpus is exit 2 naming the line — never a scored
455
+ 0 %. Never author a corpus yourself: an invented corpus is the invented input this instrument
456
+ exists to expose.
457
+
458
+ ```bash
459
+ python3 $QL corpus-run --repo <REPO> --corpus <path.jsonl> --command "<cmd>" [--threshold <P>] [--ac AC-FIELD-01] [--timeout 30] # exit 1 = under the declared budget
460
+ # a corpus measured elsewhere (CI, a nightly) goes in through the parity door:
461
+ python3 $QL log-gate --repo <REPO> --iteration <N> --kind corpus --verdict <pass|fail|advisory|not-run>
462
+ ```
463
+
464
+ `--ac` stamps criterion ids on the record: a criterion whose only evidence is a corpus record
465
+ closes MEASURED iff that record PASSED, and a ticked criterion without one reports
466
+ `narrated_only` — the same rule a green JUnit testcase has always obeyed. `readiness` prints one
467
+ conditional `--- field <repo>: ...` line per repo that declares a corpus or ran one; it carries
468
+ no weight in the score (that dimension is deferred to its own ADR).
469
+
409
470
  4. The **static analysis gate** (`java-qa-gate`: Checkstyle/PMD/SpotBugs/FindSecBugs)
410
471
  is NOT counted by hand. Run the gate so its XML reports are written, then ingest
411
472
  them — the ledger parses the reports, normalizes severities to the common gate
@@ -508,13 +569,53 @@ python3 $QL check-terminado # 0 = sealed · 1 = broken · 2 = UNMEASURE
508
569
  seal broke. **Exit 2** — the seal is UNMEASURED (no git, or no snapshot recorded): say so
509
570
  plainly; an answer nobody could measure is not a TERMINADO either.
510
571
  - Ensure conventional-commit history is clean.
572
+ - **List the agent-origin items that are still unconfirmed** in the PR body, under
573
+ **Agent-origin, unconfirmed** (principle 6). `spec-check` names them with file:line;
574
+ copy that list. An empty list is written as "none" — silence and "none" are not the
575
+ same fact.
576
+ - **Cite the operability line** in the PR body, under **Operability** (kit 2.2.0, ADR-048):
577
+ copy `readiness`'s `--- operability: ...` line verbatim, whichever four verdicts it
578
+ carries. A reviewer should not have to ask whether this change ships with a release
579
+ path, a reset and a RUNBOOK — and a red line in the PR body is a conversation, which is
580
+ exactly the conversation two projects never had until their last week.
511
581
  - Open the PR(s). Confirm CI is green.
512
582
  - **STOP.** Present the PR link(s) and wait for the human to merge.
513
583
 
514
- ## Phase 7 — Smoke list
584
+ ## Phase 7 — Smoke run (measured, kit 2.2.0, ADR-047)
585
+
586
+ **Evidence is executed, not narrated.** This phase used to end in a prose checklist, and
587
+ prose is exactly where a smoke run goes to die: "the jar served /admin", "the simulator
588
+ answered 200 in 6 ms" are an agent's sentences, believed because they are written
589
+ confidently. A real one cost a field team a release — every simulator run returned an
590
+ empty list because the database had no rows, and a smoke narrated as "verified" would
591
+ have hidden it behind a paragraph. So RUN the smoke paths (real endpoints, real user
592
+ flows, real device paths for this change) with the project's own tool, have that tool
593
+ write `reports/smoke.json`, and INGEST it:
594
+
595
+ ```json
596
+ {"checks": [{"name": "AC-28 the jar serves /admin", "ok": true, "status": 200,
597
+ "latency_ms": 6, "evidence": "curl -sS localhost:8080/admin | head -1"}]}
598
+ ```
599
+
600
+ ```bash
601
+ python3 $QL smoke-ingest --repo <REPO> --report reports/smoke.json # exit 1 = a check failed
602
+ ```
515
603
 
516
- Produce a concrete manual smoke-test checklist (real user paths / endpoints / device
517
- flows for this change), so the human can verify the system behaves as intended.
604
+ `name` and a boolean `ok` are the whole mandatory contract; `status`, `latency_ms` and
605
+ `evidence` are optional and never invented. A missing `checks`, an EMPTY list, or a check
606
+ without a name or a boolean `ok` is exit 2 naming it — a run that verified nothing is not
607
+ a clean gate. A failed check is a BLOCKER like any other fact gate: readiness ≤65,
608
+ convergence blocked, cleared by a later clean report. `smoke` is a FACT kind and never
609
+ runs advisory — a check either answered or it did not. Name a check `AC-nn ...` (the same
610
+ tag grammar a JUnit testcase name uses) and a green check inside a passing report closes
611
+ that criterion MEASURED; a failed one vetoes it, exactly like a red test.
612
+ `log-gate --kind smoke --verdict pass|fail|not-run` is the parity door for a smoke
613
+ measured elsewhere. `uscha-kit/templates/scripts/smoke-report-example.json` is the
614
+ reference report. If the human runs the smoke by hand, they still write the report: a
615
+ checklist a human ticked is evidence, a checklist an agent narrated is not.
616
+
617
+ **The PR body cites the gate** (phase 6): paste the `smoke-ingest` verdict line —
618
+ `<repo>/gate:smoke: PASS — 8/8 checks ok` — not a prose claim that the smoke passed.
518
619
 
519
620
  ## Phase 8 — Hand off to docs + retrospective
520
621
 
@@ -523,6 +624,33 @@ python3 $QL summary # human-readable
523
624
  python3 $QL summary --json # machine-readable, includes post_merge_calibration
524
625
  ```
525
626
 
627
+ **Operability is MEASURED here, not promised (kit 2.2.0, ADR-048).** Release by CI, the
628
+ reset/seed script and the RUNBOOK used to live in this phase as PROSE, and prose is what
629
+ lets "we do that at the end" survive every gate the kit has. Run it, per repo, BEFORE
630
+ readiness so the record is in the ledger when the KPI renders:
631
+
632
+ ```bash
633
+ python3 $QL operability --repo <REPO> # exit 0 always; the gate is the profile's
634
+ ```
635
+
636
+ It reads four FACTS out of the tree and never runs anything: a `.github/workflows` step
637
+ that runs the repo's **configured** test command, a workflow that publishes or attaches a
638
+ release asset, a `RUNBOOK.md` (`docs/` first) naming start/config/rollback/smoke in EN or
639
+ ES, and a `seed_command` the config declares whose script is on disk. Repo path first,
640
+ CONFIG ROOT second, and it NAMES which it read. A CI system it does not read (GitLab,
641
+ Jenkins, Azure) is reported `unknown` — never green, never red.
642
+
643
+ The POSTURE is the risk profile's, not the kit's: on **A/B** the record is `advisory`
644
+ (caps nothing, blocks nothing, never counted as `ok`); on **C/D/E** — where
645
+ `defaults.operability.gate` is `true` — a missing check is a BLOCKER that caps readiness
646
+ ≤65, blocks convergence, and makes `phase --require pr-ready` refuse NAMING the missing
647
+ one. Do NOT hand-wave a missing check: write the workflow, write the RUNBOOK section,
648
+ declare the seed command. If the project genuinely does not want the gate, that is a
649
+ declaration (`defaults.operability.gate: false`), not a silence.
650
+
651
+ `readiness` then prints one conditional line — `--- operability: ci ok · release missing ·
652
+ runbook ok · seed missing (advisory)` — and that is the line you cite in the PR body.
653
+
526
654
  **Readiness KPI — show this after finishing ANY task, not only full runs.** It measures
527
655
  the STATE of the result (not effort spent), as a weighted score 0..100 with hard caps:
528
656
 
@@ -542,6 +670,11 @@ Right after readiness, run the spec-maintenance advisory (kit 1.66.0):
542
670
  python3 $QL spec-drift --repo <REPO>
543
671
  ```
544
672
 
673
+ It searches the repo's own path first and the CONFIG ROOT (where `uscha.config.json` and
674
+ the ledger live) second, and NAMES which of the two it read — in a monorepo the single
675
+ `SPEC.md` at the root is the spec of every repo, and "no spec documents" used to be
676
+ indistinguishable from "no drift" (kit 2.2.0).
677
+
545
678
  Milliseconds, deterministic, exit 0 always — it never gates, so running it every pass
546
679
  adds zero ceremony. What it adds is VISIBILITY: the run lands in the ledger, so the
547
680
  mirador card and `/uscha-status` show drift without anyone remembering the command —