@andresmassello/uscha 2.2.0 → 2.4.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.
- package/README.md +8 -2
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +9 -1
- package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +9 -1
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +41 -5
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +30 -8
- package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +17 -2
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +5 -1
- package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +9 -1
- package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +9 -1
- package/uscha-kit/.claude/skills/uscha-status/SKILL.md +5 -1
- package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +9 -1
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +6 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +52 -0
- package/uscha-kit/skills/uscha-adr-refine/SKILL.md +9 -1
- package/uscha-kit/skills/uscha-characterize/SKILL.md +9 -1
- package/uscha-kit/skills/uscha-devloop/SKILL.md +41 -5
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +30 -8
- package/uscha-kit/skills/uscha-discovery/SKILL.md +17 -2
- package/uscha-kit/skills/uscha-mirador/SKILL.md +5 -1
- package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +9 -1
- package/uscha-kit/skills/uscha-rubric/SKILL.md +9 -1
- package/uscha-kit/skills/uscha-status/SKILL.md +5 -1
- package/uscha-kit/skills/uscha-sysdoc/SKILL.md +9 -1
- package/uscha-kit/uscha.config.json +1 -1
package/README.md
CHANGED
|
@@ -42,9 +42,10 @@ npx --yes @andresmassello/uscha@latest init
|
|
|
42
42
|
|
|
43
43
|
Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip installs, no
|
|
44
44
|
runtime dependencies). The npm package is a thin router; the canonical installer is
|
|
45
|
-
`uscha-kit/install-uscha.py`.
|
|
45
|
+
`uscha-kit/install-uscha.py`. `init` also writes a minimal, per-repo-type `.gitignore` when the
|
|
46
|
+
project has none (kit 2.4.0) — it never lists `reports/`, the ledger's own evidence.
|
|
46
47
|
|
|
47
|
-
**Kit v2.
|
|
48
|
+
**Kit v2.4.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
|
|
48
49
|
[changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
|
|
49
50
|
(the per-release changelogs live in the repo, not in the npm tarball)
|
|
50
51
|
|
|
@@ -218,6 +219,11 @@ that same-model reruns differ structurally about as much as different models do
|
|
|
218
219
|
`NOISY`) — so one earlier variance narrative was **retracted**. Every claim above is a subcommand
|
|
219
220
|
you can run; every unmeasured part is labeled. That honesty is the method applied to itself.
|
|
220
221
|
|
|
222
|
+
Every verdict above is tied to a named criterion, and every one a human judged is tied to the
|
|
223
|
+
person who signed it — the ledger itself, `bench-curate --human` for the compiled artifacts, and
|
|
224
|
+
the `origin: agent` markers that record which specification items the agent proposed versus the
|
|
225
|
+
human decided.
|
|
226
|
+
|
|
221
227
|
→ The full thesis, with before/after diagrams and the REAL vs VISION vs REJECTED table:
|
|
222
228
|
**[uscha.dev/diamond](https://uscha.dev/diamond)** · the mechanism, in three diagrams:
|
|
223
229
|
**[uscha.dev/how](https://uscha.dev/how)**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.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,7 +19,7 @@ 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.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · adr-refine · <step> → <target>]`
|
|
@@ -19,7 +19,7 @@ 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.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · characterize · <step> → <target>]`
|
|
@@ -27,7 +27,7 @@ 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.
|
|
30
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
31
31
|
|
|
32
32
|
## First contact (show ONCE, then never again)
|
|
33
33
|
|
|
@@ -62,6 +62,14 @@ block onward, derived state wins.
|
|
|
62
62
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
63
63
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
64
64
|
|
|
65
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
66
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
67
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
68
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
69
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
70
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
71
|
+
below.
|
|
72
|
+
|
|
65
73
|
**Open every turn with a breadcrumb**, then the content:
|
|
66
74
|
|
|
67
75
|
`[uscha · devloop · <step> → <target>]`
|
|
@@ -239,15 +247,22 @@ For each repo, decide whether a safety net exists before any refactoring:
|
|
|
239
247
|
|
|
240
248
|
```bash
|
|
241
249
|
python3 $QL snapshot --repo <REPO> --phase pre
|
|
242
|
-
python3 $QL check-coverage --repo <REPO>
|
|
250
|
+
python3 $QL check-coverage --repo <REPO>
|
|
251
|
+
# exit 0 = OK, exit 1 = BELOW threshold (a real report), exit 2 = UNMEASURED (no report)
|
|
243
252
|
```
|
|
244
253
|
|
|
245
254
|
- **Coverage >= threshold:** the existing suite is the guardrail. Skip to Phase 2.
|
|
246
|
-
- **Coverage < threshold (
|
|
247
|
-
at the boundary** (public API, endpoints, input→output behavior) — NOT internals.
|
|
255
|
+
- **Coverage < threshold (a real report, exit 1):** write **characterization / contract
|
|
256
|
+
tests at the boundary** (public API, endpoints, input→output behavior) — NOT internals.
|
|
248
257
|
These must survive refactoring. The ADR acceptance criteria are the spec for these.
|
|
249
258
|
**Have the human review these tests before trusting them as a gate** — a test that
|
|
250
|
-
passes for the wrong reason poisons the whole loop.
|
|
259
|
+
passes for the wrong reason poisons the whole loop. Characterization tests are for
|
|
260
|
+
EXISTING code nobody has tested yet, not for code that has not been run at all.
|
|
261
|
+
- **UNMEASURED (exit 2, no report found):** on a greenfield repo this means the test
|
|
262
|
+
command has never been run with coverage — not that the code failed a test. Produce
|
|
263
|
+
the report first: run the repo's test command with coverage, then re-run
|
|
264
|
+
`check-coverage`. Only if a real report then shows coverage below threshold does the
|
|
265
|
+
characterization path above apply.
|
|
251
266
|
- **Migration/legacy (profile E): capture the golden BEFORE touching anything.** Run
|
|
252
267
|
the `uscha-characterize` skill (or `uscha-reverse-discovery` for a whole-system map first): it
|
|
253
268
|
executes the ORIGINAL code against a real input corpus, emits `.received` fixtures,
|
|
@@ -521,6 +536,13 @@ python3 $QL snapshot --repo <REPO> --phase post # for every repo + integration
|
|
|
521
536
|
|
|
522
537
|
Full suite must be green and coverage at/above threshold before proceeding.
|
|
523
538
|
|
|
539
|
+
**`reports/` is EVIDENCE, not build noise (kit 2.4.0).** JUnit XML, coverage reports
|
|
540
|
+
(JaCoCo/Cobertura/lcov/go cover), and `reports/smoke.json` are what `snapshot`,
|
|
541
|
+
`check-coverage` and `smoke-ingest` read to turn "we ran tests" into a measured fact.
|
|
542
|
+
Keep it, commit it, and never delete or `.gitignore` it to get a clean-looking commit —
|
|
543
|
+
a repo with no `reports/` reads UNMEASURED, not "clean". (`uscha init`'s generated
|
|
544
|
+
`.gitignore`, kit 2.4.0, never lists `reports/` for exactly this reason.)
|
|
545
|
+
|
|
524
546
|
## Phase 5b — Rebuild test (optional; risk profile C+/E or periodic CI)
|
|
525
547
|
|
|
526
548
|
Completeness of the SPEC, not correctness of the build: is the spec package enough to
|
|
@@ -549,6 +571,10 @@ is a spec gap, not a code bug.
|
|
|
549
571
|
python3 $QL phase --repo <REPO> --require pr-ready # exit 1 = the facts say no
|
|
550
572
|
```
|
|
551
573
|
|
|
574
|
+
`pr-ready` is a PHASE VALUE, not a subcommand — there is no `qa_ledger.py pr-ready`.
|
|
575
|
+
It is always read through `phase --repo <REPO> --require pr-ready` as shown above
|
|
576
|
+
(`qa_ledger.py pr-ready` alone is an argparse error).
|
|
577
|
+
|
|
552
578
|
The state is COMPUTED from the ledger (converged + green tests + zero
|
|
553
579
|
BLOCKER/CRITICAL + no open escalation), never self-declared — if it exits 1, the
|
|
554
580
|
output lists exactly which facts are missing; do NOT open the PR, close the gap.
|
|
@@ -664,6 +690,16 @@ count, plateau flag per repo — to `ledger["measured"]` (what the statusline re
|
|
|
664
690
|
Without it the trail starves: the mirador shows "no history yet" and the statusline
|
|
665
691
|
falls back to counting checkboxes. Recording is append-only facts, never a gate.
|
|
666
692
|
|
|
693
|
+
**Tick measured-but-unchecked boxes before closing (kit 2.4.0).** `readiness` reports
|
|
694
|
+
`measured_unchecked` (`--json`) / a `· measured but unticked: AC-...` line (default view):
|
|
695
|
+
AC-IDs the ledger already closed with a green, name-tagged test but whose `ACCEPTANCE.md`
|
|
696
|
+
checkbox is still `[ ]`. For every ID it lists, flip that box to `[x]` in `ACCEPTANCE.md` —
|
|
697
|
+
the engine measured it; ticking is bookkeeping, never the other way round. Then re-run
|
|
698
|
+
`readiness` and confirm the list is empty before the close block. This is ONE-DIRECTIONAL:
|
|
699
|
+
never tick a box the ledger has not closed (that would be narrating, and `readiness`
|
|
700
|
+
already reports narrated-but-unmeasured boxes separately as `narrated_only`) — a tick
|
|
701
|
+
without a green test stays narrated-only, not measured.
|
|
702
|
+
|
|
667
703
|
Right after readiness, run the spec-maintenance advisory (kit 1.66.0):
|
|
668
704
|
|
|
669
705
|
```bash
|
|
@@ -19,7 +19,8 @@ Stdlib only. Python 3.8+.
|
|
|
19
19
|
Usage (see `--help` on each subcommand):
|
|
20
20
|
qa_ledger.py init --config uscha.config.json [--out QA-LEDGER.json]
|
|
21
21
|
qa_ledger.py snapshot --repo backend-api [--phase pre|post]
|
|
22
|
-
qa_ledger.py check-coverage --repo backend-api [--threshold 60]
|
|
22
|
+
qa_ledger.py check-coverage --repo backend-api [--threshold 60]
|
|
23
|
+
# exit 0 >= threshold, 1 < threshold, 2 UNMEASURED (no report)
|
|
23
24
|
qa_ledger.py log-step --repo backend-api --tool code-review --iteration 1 \
|
|
24
25
|
--reported 12 --gated-reported 4 --fixed 9 \
|
|
25
26
|
--deferred 2 --suppressed 1 --tests-passed true \
|
|
@@ -399,6 +400,24 @@ def coverage(repo_path, repo_type):
|
|
|
399
400
|
return flutter_coverage(repo_path)
|
|
400
401
|
|
|
401
402
|
|
|
403
|
+
_COVERAGE_REPORT_HINT = {
|
|
404
|
+
"ant": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
405
|
+
"maven": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
406
|
+
"gradle": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
407
|
+
"python": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
408
|
+
"rust": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
409
|
+
"dotnet": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
410
|
+
"cpp": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
411
|
+
"go": "coverage.out, cover.out or reports/coverage.out",
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _coverage_report_hint(repo_type):
|
|
416
|
+
"""Human-readable hint of where `coverage()` looked, for an UNMEASURED message --
|
|
417
|
+
never invents a single path, since several report types glob multiple locations."""
|
|
418
|
+
return _COVERAGE_REPORT_HINT.get(repo_type, "coverage/lcov.info")
|
|
419
|
+
|
|
420
|
+
|
|
402
421
|
# --------------------------------------------------------------------------- #
|
|
403
422
|
# measurement: test counts
|
|
404
423
|
# --------------------------------------------------------------------------- #
|
|
@@ -2404,15 +2423,18 @@ def cmd_check_coverage(args):
|
|
|
2404
2423
|
threshold = ledger["config"].get("defaults", {}).get("coverage_threshold", 60)
|
|
2405
2424
|
cov = coverage(cfg["path"], cfg["type"])
|
|
2406
2425
|
pct = cov["pct"]
|
|
2426
|
+
if not cov["report_found"]:
|
|
2427
|
+
# No coverage report at all is UNMEASURED, not a below-threshold verdict: on a
|
|
2428
|
+
# greenfield repo pct 0.0 + report_found False means no report was ever produced,
|
|
2429
|
+
# never that the code was tested and failed. Fail closed (exit 2) -- distinct from
|
|
2430
|
+
# exit 1 (a real report that reads below threshold) -- so callers do not confuse
|
|
2431
|
+
# "never measured" with "measured and failing".
|
|
2432
|
+
print(f"[qa_ledger] {args.repo}: coverage UNMEASURED -- no coverage report found "
|
|
2433
|
+
f"(looked for {_coverage_report_hint(cfg['type'])}). "
|
|
2434
|
+
f"Run the test command with coverage first to produce a report.")
|
|
2435
|
+
sys.exit(2)
|
|
2407
2436
|
below = pct < threshold
|
|
2408
2437
|
state = "BELOW" if below else "OK"
|
|
2409
|
-
if not cov["report_found"]:
|
|
2410
|
-
# No coverage report at all == treat as below threshold (needs char tests),
|
|
2411
|
-
# but flag loudly so the skill knows tests simply haven't been run yet.
|
|
2412
|
-
print(f"[qa_ledger] {args.repo}: NO coverage report found "
|
|
2413
|
-
f"(threshold {threshold}%) -> treat as BELOW. "
|
|
2414
|
-
f"Run the test command first if a report was expected.")
|
|
2415
|
-
sys.exit(1)
|
|
2416
2438
|
print(f"[qa_ledger] {args.repo}: coverage {pct}% vs threshold {threshold}% -> {state}")
|
|
2417
2439
|
sys.exit(1 if below else 0)
|
|
2418
2440
|
|
|
@@ -19,7 +19,7 @@ shape.** Your job is to interrogate until there is a shared system shape, and to
|
|
|
19
19
|
the documents as you go — not to ask the human to design the system for you.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · discovery · <step> → <target>]`
|
|
@@ -112,6 +120,12 @@ Keep the CONTENT in the conversation's language, but keep the labels (`CLOSED`,
|
|
|
112
120
|
4. **Grill, don't agree.** Surface contradictions, fuzzy/overloaded terms, missing
|
|
113
121
|
failure modes and unstated constraints. A discovery where you agreed with everything
|
|
114
122
|
failed.
|
|
123
|
+
Two habits, both advisory (unmeasured, kept because they are cheap): **every magnitude
|
|
124
|
+
carries a number or a range** -- "many tasks", "fast", "large files" are not scope;
|
|
125
|
+
"20 to 100 tasks", "under 200 ms", "up to 50 MB" are -- and **rules are written as
|
|
126
|
+
constraints, not wishes**: "no partial implementations, no TODO left in the diff" is
|
|
127
|
+
checkable; "remember to finish things" is not. A magnitude without a number and a rule
|
|
128
|
+
without a boundary are questions you still owe the human.
|
|
115
129
|
5. **Write files lazily and inline.** Create a file only when you have something real to
|
|
116
130
|
write, and update it the moment a decision crystallizes — don't batch to the end.
|
|
117
131
|
6. **Mark what YOU decided: `origin: agent`.** Any acceptance criterion, ADR decision item
|
|
@@ -238,7 +252,8 @@ answer.
|
|
|
238
252
|
you proposed and the human approved). Distinct from the glossary: this is the model, not
|
|
239
253
|
the vocabulary.
|
|
240
254
|
- **`SPEC.md`** — objective/value, risk, scope/out-of-scope, behavior,
|
|
241
|
-
inputs/outputs/errors, acceptance, test plan, operation, rollback.
|
|
255
|
+
inputs/outputs/errors, acceptance, test plan, operation, rollback. Magnitudes with numbers
|
|
256
|
+
or ranges; rules as constraints with a boundary, never as reminders.
|
|
242
257
|
- **`docs/adr/ADR-NNN-<slug>.md`** — one per durable decision. Format: Status
|
|
243
258
|
(proposed/accepted/**experiment**/deprecated/superseded) · Context · Alternatives · Decision ·
|
|
244
259
|
Consequences · **Implementation Plan** (affected paths, patterns to follow, tests to
|
|
@@ -18,7 +18,7 @@ Paints the REAL state of the project at a glance. It does not narrate or estimat
|
|
|
18
18
|
wires the JSON the engine emits into the template. Read-only.
|
|
19
19
|
|
|
20
20
|
<!-- uscha:orientation-block:begin -->
|
|
21
|
-
<!-- uscha kit: 2.
|
|
21
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
22
22
|
|
|
23
23
|
## Orientation markers (non-negotiable)
|
|
24
24
|
|
|
@@ -28,6 +28,10 @@ This skill is a **one-shot read-only readout**: its block IS the answer. It ther
|
|
|
28
28
|
take the conversational close block — that would be exactly the padding this skill forbids.
|
|
29
29
|
It carries the two minimal markers instead.
|
|
30
30
|
|
|
31
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
32
|
+
statusline — Codex, pi, a plain terminal — this readout IS the operator's only signal, so it
|
|
33
|
+
MUST appear in the visible reply text, never only inside a collapsed tool-call log.
|
|
34
|
+
|
|
31
35
|
**Open with a breadcrumb:**
|
|
32
36
|
|
|
33
37
|
`[uscha · mirador · step <n> → <target>]`
|
|
@@ -24,7 +24,7 @@ evidence-classed, content-addressed, and promoted to the contract only by a per-
|
|
|
24
24
|
human verdict (ADR-013).**
|
|
25
25
|
|
|
26
26
|
<!-- uscha:orientation-block:begin -->
|
|
27
|
-
<!-- uscha kit: 2.
|
|
27
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
28
28
|
|
|
29
29
|
## First contact (show ONCE, then never again)
|
|
30
30
|
|
|
@@ -61,6 +61,14 @@ block onward, derived state wins.
|
|
|
61
61
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
62
62
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
63
63
|
|
|
64
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
65
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
66
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
67
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
68
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
69
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
70
|
+
below.
|
|
71
|
+
|
|
64
72
|
**Open every turn with a breadcrumb**, then the content:
|
|
65
73
|
|
|
66
74
|
`[uscha · reverse-discovery · <step> → <target>]`
|
|
@@ -22,7 +22,7 @@ the grader — this skill just wraps the neutral prompt so Claude Code users get
|
|
|
22
22
|
in one command. Never add Claude-specific behavior to the contract.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -57,6 +57,14 @@ block onward, derived state wins.
|
|
|
57
57
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
58
58
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
59
59
|
|
|
60
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
61
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
62
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
63
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
64
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
65
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
66
|
+
below.
|
|
67
|
+
|
|
60
68
|
**Open every turn with a breadcrumb**, then the content:
|
|
61
69
|
|
|
62
70
|
`[uscha · rubric · <step> → <target>]`
|
|
@@ -58,7 +58,7 @@ that surface the warning cannot come from the skill itself. The `doctor` seam is
|
|
|
58
58
|
that still works there — it runs from any kit checkout and reads the installs from outside.
|
|
59
59
|
|
|
60
60
|
<!-- uscha:orientation-block:begin -->
|
|
61
|
-
<!-- uscha kit: 2.
|
|
61
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
62
62
|
|
|
63
63
|
## Orientation markers (non-negotiable)
|
|
64
64
|
|
|
@@ -68,6 +68,10 @@ This skill is a **one-shot read-only readout**: its block IS the answer. It ther
|
|
|
68
68
|
take the conversational close block — that would be exactly the padding this skill forbids.
|
|
69
69
|
It carries the two minimal markers instead.
|
|
70
70
|
|
|
71
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
72
|
+
statusline — Codex, pi, a plain terminal — this readout IS the operator's only signal, so it
|
|
73
|
+
MUST appear in the visible reply text, never only inside a collapsed tool-call log.
|
|
74
|
+
|
|
71
75
|
**Open with a breadcrumb:**
|
|
72
76
|
|
|
73
77
|
`[uscha · status · step <n> → <target>]`
|
|
@@ -22,7 +22,7 @@ switch between at any time:
|
|
|
22
22
|
coverage, known deferred issues.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -57,6 +57,14 @@ block onward, derived state wins.
|
|
|
57
57
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
58
58
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
59
59
|
|
|
60
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
61
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
62
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
63
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
64
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
65
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
66
|
+
below.
|
|
67
|
+
|
|
60
68
|
**Open every turn with a breadcrumb**, then the content:
|
|
61
69
|
|
|
62
70
|
`[uscha · sysdoc · <step> → <target>]`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"displayName": "Uscha",
|
|
6
6
|
"description": "Spec-driven development for LLM coding agents: 9 skills (discovery, adr-refine, reverse-discovery, characterize, devloop, sysdoc, rubric, mirador, status) + a stdlib measurement engine (qa_ledger.py, 56 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
|
|
7
7
|
"author": {
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v2.
|
|
3
|
+
**Kit version:** v2.4.0 <!-- uscha:version --> · **[uscha.dev](https://uscha.dev)**
|
|
4
4
|
|
|
5
5
|
Spec-driven orchestrator + multi-repo QA for Claude Code, with a deterministic ledger.
|
|
6
6
|
**Nine skills** (`uscha-discovery`, `uscha-adr-refine`, `uscha-devloop`, `uscha-sysdoc`, `uscha-reverse-discovery`,
|
|
@@ -532,6 +532,11 @@ of six. Every other knob is absent on purpose and resolves to the engine default
|
|
|
532
532
|
`uscha-kit/uscha.config.json` is the **comprehensive reference** — every knob at the kit's
|
|
533
533
|
value, to read and copy from — and is no longer copied into projects.
|
|
534
534
|
|
|
535
|
+
`uscha init` also writes a minimal `.gitignore` (kit 2.4.0), scoped to the detected repo type
|
|
536
|
+
(`__pycache__/`, `*.pyc`, `.pytest_cache/`, `.coverage` for python; `node_modules/` for node; and
|
|
537
|
+
so on) — never `reports/`, which is the ledger's own evidence (JUnit, coverage, smoke). An
|
|
538
|
+
existing `.gitignore` is always left untouched, even with `--force`.
|
|
539
|
+
|
|
535
540
|
That matters because of one rule: **a knob you declare > the preset named by
|
|
536
541
|
`defaults.risk_profile` > the engine default**. A copied default is an explicit declaration, so
|
|
537
542
|
a project holding the whole reference leaves its risk profile nothing to decide.
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 2.
|
|
1
|
+
uscha-kit 2.4.0
|
|
@@ -805,6 +805,37 @@ def project_config_bytes(repo):
|
|
|
805
805
|
return (json.dumps(config, indent=2, ensure_ascii=False) + "\n").encode("utf-8")
|
|
806
806
|
|
|
807
807
|
|
|
808
|
+
# Minimal, per repo type (kit 2.4.0). Deliberately tiny: build/cache artifacts a fresh
|
|
809
|
+
# clone regenerates, never source. `reports/` (JUnit, coverage, smoke) is EVIDENCE the
|
|
810
|
+
# ledger reads -- it must NEVER appear here, in this list or any other repo's.
|
|
811
|
+
GITIGNORE_PATTERNS = {
|
|
812
|
+
"python": ("__pycache__/", "*.pyc", ".pytest_cache/", ".coverage"),
|
|
813
|
+
"node": ("node_modules/",),
|
|
814
|
+
"flutter": (".dart_tool/", "build/"),
|
|
815
|
+
"maven": ("target/",),
|
|
816
|
+
"gradle": ("build/", ".gradle/"),
|
|
817
|
+
"rust": ("target/",),
|
|
818
|
+
"swift": (".build/",),
|
|
819
|
+
"cpp": ("build/",),
|
|
820
|
+
"dotnet": ("bin/", "obj/"),
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def gitignore_bytes(repo_type):
|
|
825
|
+
"""The project's .gitignore, generated for a RECOGNISED repo type only -- like
|
|
826
|
+
`detect_repo_type`, this never guesses. `None` means `init` writes nothing (the
|
|
827
|
+
human declares one by hand). LF explicitly, like every other artifact `init` writes,
|
|
828
|
+
so the same bytes are produced on every run for the same repo type."""
|
|
829
|
+
patterns = GITIGNORE_PATTERNS.get(repo_type)
|
|
830
|
+
if not patterns:
|
|
831
|
+
return None
|
|
832
|
+
lines = ["# Minimal .gitignore, generated by `uscha init` for a %s repo." % repo_type,
|
|
833
|
+
"# reports/ is evidence the ledger reads (JUnit, coverage, smoke) -- never "
|
|
834
|
+
"ignored here."]
|
|
835
|
+
lines.extend(patterns)
|
|
836
|
+
return ("\n".join(lines) + "\n").encode("utf-8")
|
|
837
|
+
|
|
838
|
+
|
|
808
839
|
def cmd_init(args):
|
|
809
840
|
repo, operations, conflicts = Path(args.repo).expanduser().resolve(), [], []
|
|
810
841
|
# (source path or None, payload bytes or None, target): the config is generated, the rest
|
|
@@ -845,6 +876,25 @@ def cmd_init(args):
|
|
|
845
876
|
target.write_bytes(want)
|
|
846
877
|
else:
|
|
847
878
|
shutil.copy2(source, target)
|
|
879
|
+
# .gitignore (kit 2.4.0): written ONLY when the repo has none yet, for a recognised
|
|
880
|
+
# repo type. Unlike the templates above, an EXISTING .gitignore is never a conflict and
|
|
881
|
+
# is never touched even with --force -- it is the human's file, `init` only fills the
|
|
882
|
+
# gap on a repo that has none. That also makes a second `init` report "unchanged".
|
|
883
|
+
gi_target = repo / ".gitignore"
|
|
884
|
+
gi_bytes = gitignore_bytes(detect_repo_type(repo))
|
|
885
|
+
gitignore_wrote = False
|
|
886
|
+
if gi_bytes is not None:
|
|
887
|
+
if gi_target.exists():
|
|
888
|
+
operations.append({"action": "unchanged", "path": str(gi_target),
|
|
889
|
+
"note": "existing .gitignore left untouched"})
|
|
890
|
+
elif args.dry_run:
|
|
891
|
+
operations.append({"action": "copy-file", "path": str(gi_target),
|
|
892
|
+
"source": "<generated>"})
|
|
893
|
+
else:
|
|
894
|
+
gi_target.write_bytes(gi_bytes)
|
|
895
|
+
operations.append({"action": "copy-file", "path": str(gi_target),
|
|
896
|
+
"source": "<generated>"})
|
|
897
|
+
gitignore_wrote = True
|
|
848
898
|
# wire the statusline (kit 1.46.0): merge statusLine + Stop hook into the project's
|
|
849
899
|
# settings.json so the user never edits it by hand -- never clobbering existing keys.
|
|
850
900
|
sl_ops, sl_wrote, sl_conflicts = _wire_statusline_settings(repo, args.force, args.dry_run)
|
|
@@ -856,6 +906,8 @@ def cmd_init(args):
|
|
|
856
906
|
else:
|
|
857
907
|
status = "planned" if args.dry_run else "initialized"
|
|
858
908
|
wrote = [str(t) for _, _, t in copies] if not args.dry_run else []
|
|
909
|
+
if gitignore_wrote:
|
|
910
|
+
wrote.append(str(gi_target))
|
|
859
911
|
if sl_wrote:
|
|
860
912
|
wrote.append(str(repo / ".claude" / "settings.json"))
|
|
861
913
|
emit({"status": status, "dry_run": args.dry_run, "repo": str(repo),
|
|
@@ -19,7 +19,7 @@ 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.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · adr-refine · <step> → <target>]`
|
|
@@ -19,7 +19,7 @@ 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.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · characterize · <step> → <target>]`
|
|
@@ -27,7 +27,7 @@ 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.
|
|
30
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
31
31
|
|
|
32
32
|
## First contact (show ONCE, then never again)
|
|
33
33
|
|
|
@@ -62,6 +62,14 @@ block onward, derived state wins.
|
|
|
62
62
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
63
63
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
64
64
|
|
|
65
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
66
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
67
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
68
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
69
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
70
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
71
|
+
below.
|
|
72
|
+
|
|
65
73
|
**Open every turn with a breadcrumb**, then the content:
|
|
66
74
|
|
|
67
75
|
`[uscha · devloop · <step> → <target>]`
|
|
@@ -239,15 +247,22 @@ For each repo, decide whether a safety net exists before any refactoring:
|
|
|
239
247
|
|
|
240
248
|
```bash
|
|
241
249
|
python3 $QL snapshot --repo <REPO> --phase pre
|
|
242
|
-
python3 $QL check-coverage --repo <REPO>
|
|
250
|
+
python3 $QL check-coverage --repo <REPO>
|
|
251
|
+
# exit 0 = OK, exit 1 = BELOW threshold (a real report), exit 2 = UNMEASURED (no report)
|
|
243
252
|
```
|
|
244
253
|
|
|
245
254
|
- **Coverage >= threshold:** the existing suite is the guardrail. Skip to Phase 2.
|
|
246
|
-
- **Coverage < threshold (
|
|
247
|
-
at the boundary** (public API, endpoints, input→output behavior) — NOT internals.
|
|
255
|
+
- **Coverage < threshold (a real report, exit 1):** write **characterization / contract
|
|
256
|
+
tests at the boundary** (public API, endpoints, input→output behavior) — NOT internals.
|
|
248
257
|
These must survive refactoring. The ADR acceptance criteria are the spec for these.
|
|
249
258
|
**Have the human review these tests before trusting them as a gate** — a test that
|
|
250
|
-
passes for the wrong reason poisons the whole loop.
|
|
259
|
+
passes for the wrong reason poisons the whole loop. Characterization tests are for
|
|
260
|
+
EXISTING code nobody has tested yet, not for code that has not been run at all.
|
|
261
|
+
- **UNMEASURED (exit 2, no report found):** on a greenfield repo this means the test
|
|
262
|
+
command has never been run with coverage — not that the code failed a test. Produce
|
|
263
|
+
the report first: run the repo's test command with coverage, then re-run
|
|
264
|
+
`check-coverage`. Only if a real report then shows coverage below threshold does the
|
|
265
|
+
characterization path above apply.
|
|
251
266
|
- **Migration/legacy (profile E): capture the golden BEFORE touching anything.** Run
|
|
252
267
|
the `uscha-characterize` skill (or `uscha-reverse-discovery` for a whole-system map first): it
|
|
253
268
|
executes the ORIGINAL code against a real input corpus, emits `.received` fixtures,
|
|
@@ -521,6 +536,13 @@ python3 $QL snapshot --repo <REPO> --phase post # for every repo + integration
|
|
|
521
536
|
|
|
522
537
|
Full suite must be green and coverage at/above threshold before proceeding.
|
|
523
538
|
|
|
539
|
+
**`reports/` is EVIDENCE, not build noise (kit 2.4.0).** JUnit XML, coverage reports
|
|
540
|
+
(JaCoCo/Cobertura/lcov/go cover), and `reports/smoke.json` are what `snapshot`,
|
|
541
|
+
`check-coverage` and `smoke-ingest` read to turn "we ran tests" into a measured fact.
|
|
542
|
+
Keep it, commit it, and never delete or `.gitignore` it to get a clean-looking commit —
|
|
543
|
+
a repo with no `reports/` reads UNMEASURED, not "clean". (`uscha init`'s generated
|
|
544
|
+
`.gitignore`, kit 2.4.0, never lists `reports/` for exactly this reason.)
|
|
545
|
+
|
|
524
546
|
## Phase 5b — Rebuild test (optional; risk profile C+/E or periodic CI)
|
|
525
547
|
|
|
526
548
|
Completeness of the SPEC, not correctness of the build: is the spec package enough to
|
|
@@ -549,6 +571,10 @@ is a spec gap, not a code bug.
|
|
|
549
571
|
python3 $QL phase --repo <REPO> --require pr-ready # exit 1 = the facts say no
|
|
550
572
|
```
|
|
551
573
|
|
|
574
|
+
`pr-ready` is a PHASE VALUE, not a subcommand — there is no `qa_ledger.py pr-ready`.
|
|
575
|
+
It is always read through `phase --repo <REPO> --require pr-ready` as shown above
|
|
576
|
+
(`qa_ledger.py pr-ready` alone is an argparse error).
|
|
577
|
+
|
|
552
578
|
The state is COMPUTED from the ledger (converged + green tests + zero
|
|
553
579
|
BLOCKER/CRITICAL + no open escalation), never self-declared — if it exits 1, the
|
|
554
580
|
output lists exactly which facts are missing; do NOT open the PR, close the gap.
|
|
@@ -664,6 +690,16 @@ count, plateau flag per repo — to `ledger["measured"]` (what the statusline re
|
|
|
664
690
|
Without it the trail starves: the mirador shows "no history yet" and the statusline
|
|
665
691
|
falls back to counting checkboxes. Recording is append-only facts, never a gate.
|
|
666
692
|
|
|
693
|
+
**Tick measured-but-unchecked boxes before closing (kit 2.4.0).** `readiness` reports
|
|
694
|
+
`measured_unchecked` (`--json`) / a `· measured but unticked: AC-...` line (default view):
|
|
695
|
+
AC-IDs the ledger already closed with a green, name-tagged test but whose `ACCEPTANCE.md`
|
|
696
|
+
checkbox is still `[ ]`. For every ID it lists, flip that box to `[x]` in `ACCEPTANCE.md` —
|
|
697
|
+
the engine measured it; ticking is bookkeeping, never the other way round. Then re-run
|
|
698
|
+
`readiness` and confirm the list is empty before the close block. This is ONE-DIRECTIONAL:
|
|
699
|
+
never tick a box the ledger has not closed (that would be narrating, and `readiness`
|
|
700
|
+
already reports narrated-but-unmeasured boxes separately as `narrated_only`) — a tick
|
|
701
|
+
without a green test stays narrated-only, not measured.
|
|
702
|
+
|
|
667
703
|
Right after readiness, run the spec-maintenance advisory (kit 1.66.0):
|
|
668
704
|
|
|
669
705
|
```bash
|
|
@@ -19,7 +19,8 @@ Stdlib only. Python 3.8+.
|
|
|
19
19
|
Usage (see `--help` on each subcommand):
|
|
20
20
|
qa_ledger.py init --config uscha.config.json [--out QA-LEDGER.json]
|
|
21
21
|
qa_ledger.py snapshot --repo backend-api [--phase pre|post]
|
|
22
|
-
qa_ledger.py check-coverage --repo backend-api [--threshold 60]
|
|
22
|
+
qa_ledger.py check-coverage --repo backend-api [--threshold 60]
|
|
23
|
+
# exit 0 >= threshold, 1 < threshold, 2 UNMEASURED (no report)
|
|
23
24
|
qa_ledger.py log-step --repo backend-api --tool code-review --iteration 1 \
|
|
24
25
|
--reported 12 --gated-reported 4 --fixed 9 \
|
|
25
26
|
--deferred 2 --suppressed 1 --tests-passed true \
|
|
@@ -399,6 +400,24 @@ def coverage(repo_path, repo_type):
|
|
|
399
400
|
return flutter_coverage(repo_path)
|
|
400
401
|
|
|
401
402
|
|
|
403
|
+
_COVERAGE_REPORT_HINT = {
|
|
404
|
+
"ant": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
405
|
+
"maven": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
406
|
+
"gradle": "target/site/jacoco/jacoco.xml (or -aggregate)",
|
|
407
|
+
"python": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
408
|
+
"rust": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
409
|
+
"dotnet": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
410
|
+
"cpp": "coverage.xml or reports/coverage.xml (Cobertura)",
|
|
411
|
+
"go": "coverage.out, cover.out or reports/coverage.out",
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _coverage_report_hint(repo_type):
|
|
416
|
+
"""Human-readable hint of where `coverage()` looked, for an UNMEASURED message --
|
|
417
|
+
never invents a single path, since several report types glob multiple locations."""
|
|
418
|
+
return _COVERAGE_REPORT_HINT.get(repo_type, "coverage/lcov.info")
|
|
419
|
+
|
|
420
|
+
|
|
402
421
|
# --------------------------------------------------------------------------- #
|
|
403
422
|
# measurement: test counts
|
|
404
423
|
# --------------------------------------------------------------------------- #
|
|
@@ -2404,15 +2423,18 @@ def cmd_check_coverage(args):
|
|
|
2404
2423
|
threshold = ledger["config"].get("defaults", {}).get("coverage_threshold", 60)
|
|
2405
2424
|
cov = coverage(cfg["path"], cfg["type"])
|
|
2406
2425
|
pct = cov["pct"]
|
|
2426
|
+
if not cov["report_found"]:
|
|
2427
|
+
# No coverage report at all is UNMEASURED, not a below-threshold verdict: on a
|
|
2428
|
+
# greenfield repo pct 0.0 + report_found False means no report was ever produced,
|
|
2429
|
+
# never that the code was tested and failed. Fail closed (exit 2) -- distinct from
|
|
2430
|
+
# exit 1 (a real report that reads below threshold) -- so callers do not confuse
|
|
2431
|
+
# "never measured" with "measured and failing".
|
|
2432
|
+
print(f"[qa_ledger] {args.repo}: coverage UNMEASURED -- no coverage report found "
|
|
2433
|
+
f"(looked for {_coverage_report_hint(cfg['type'])}). "
|
|
2434
|
+
f"Run the test command with coverage first to produce a report.")
|
|
2435
|
+
sys.exit(2)
|
|
2407
2436
|
below = pct < threshold
|
|
2408
2437
|
state = "BELOW" if below else "OK"
|
|
2409
|
-
if not cov["report_found"]:
|
|
2410
|
-
# No coverage report at all == treat as below threshold (needs char tests),
|
|
2411
|
-
# but flag loudly so the skill knows tests simply haven't been run yet.
|
|
2412
|
-
print(f"[qa_ledger] {args.repo}: NO coverage report found "
|
|
2413
|
-
f"(threshold {threshold}%) -> treat as BELOW. "
|
|
2414
|
-
f"Run the test command first if a report was expected.")
|
|
2415
|
-
sys.exit(1)
|
|
2416
2438
|
print(f"[qa_ledger] {args.repo}: coverage {pct}% vs threshold {threshold}% -> {state}")
|
|
2417
2439
|
sys.exit(1 if below else 0)
|
|
2418
2440
|
|
|
@@ -19,7 +19,7 @@ shape.** Your job is to interrogate until there is a shared system shape, and to
|
|
|
19
19
|
the documents as you go — not to ask the human to design the system for you.
|
|
20
20
|
|
|
21
21
|
<!-- uscha:orientation-block:begin -->
|
|
22
|
-
<!-- uscha kit: 2.
|
|
22
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
23
23
|
|
|
24
24
|
## First contact (show ONCE, then never again)
|
|
25
25
|
|
|
@@ -54,6 +54,14 @@ block onward, derived state wins.
|
|
|
54
54
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
55
55
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
56
56
|
|
|
57
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
58
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
59
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
60
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
61
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
62
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
63
|
+
below.
|
|
64
|
+
|
|
57
65
|
**Open every turn with a breadcrumb**, then the content:
|
|
58
66
|
|
|
59
67
|
`[uscha · discovery · <step> → <target>]`
|
|
@@ -112,6 +120,12 @@ Keep the CONTENT in the conversation's language, but keep the labels (`CLOSED`,
|
|
|
112
120
|
4. **Grill, don't agree.** Surface contradictions, fuzzy/overloaded terms, missing
|
|
113
121
|
failure modes and unstated constraints. A discovery where you agreed with everything
|
|
114
122
|
failed.
|
|
123
|
+
Two habits, both advisory (unmeasured, kept because they are cheap): **every magnitude
|
|
124
|
+
carries a number or a range** -- "many tasks", "fast", "large files" are not scope;
|
|
125
|
+
"20 to 100 tasks", "under 200 ms", "up to 50 MB" are -- and **rules are written as
|
|
126
|
+
constraints, not wishes**: "no partial implementations, no TODO left in the diff" is
|
|
127
|
+
checkable; "remember to finish things" is not. A magnitude without a number and a rule
|
|
128
|
+
without a boundary are questions you still owe the human.
|
|
115
129
|
5. **Write files lazily and inline.** Create a file only when you have something real to
|
|
116
130
|
write, and update it the moment a decision crystallizes — don't batch to the end.
|
|
117
131
|
6. **Mark what YOU decided: `origin: agent`.** Any acceptance criterion, ADR decision item
|
|
@@ -238,7 +252,8 @@ answer.
|
|
|
238
252
|
you proposed and the human approved). Distinct from the glossary: this is the model, not
|
|
239
253
|
the vocabulary.
|
|
240
254
|
- **`SPEC.md`** — objective/value, risk, scope/out-of-scope, behavior,
|
|
241
|
-
inputs/outputs/errors, acceptance, test plan, operation, rollback.
|
|
255
|
+
inputs/outputs/errors, acceptance, test plan, operation, rollback. Magnitudes with numbers
|
|
256
|
+
or ranges; rules as constraints with a boundary, never as reminders.
|
|
242
257
|
- **`docs/adr/ADR-NNN-<slug>.md`** — one per durable decision. Format: Status
|
|
243
258
|
(proposed/accepted/**experiment**/deprecated/superseded) · Context · Alternatives · Decision ·
|
|
244
259
|
Consequences · **Implementation Plan** (affected paths, patterns to follow, tests to
|
|
@@ -18,7 +18,7 @@ Paints the REAL state of the project at a glance. It does not narrate or estimat
|
|
|
18
18
|
wires the JSON the engine emits into the template. Read-only.
|
|
19
19
|
|
|
20
20
|
<!-- uscha:orientation-block:begin -->
|
|
21
|
-
<!-- uscha kit: 2.
|
|
21
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
22
22
|
|
|
23
23
|
## Orientation markers (non-negotiable)
|
|
24
24
|
|
|
@@ -28,6 +28,10 @@ This skill is a **one-shot read-only readout**: its block IS the answer. It ther
|
|
|
28
28
|
take the conversational close block — that would be exactly the padding this skill forbids.
|
|
29
29
|
It carries the two minimal markers instead.
|
|
30
30
|
|
|
31
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
32
|
+
statusline — Codex, pi, a plain terminal — this readout IS the operator's only signal, so it
|
|
33
|
+
MUST appear in the visible reply text, never only inside a collapsed tool-call log.
|
|
34
|
+
|
|
31
35
|
**Open with a breadcrumb:**
|
|
32
36
|
|
|
33
37
|
`[uscha · mirador · step <n> → <target>]`
|
|
@@ -24,7 +24,7 @@ evidence-classed, content-addressed, and promoted to the contract only by a per-
|
|
|
24
24
|
human verdict (ADR-013).**
|
|
25
25
|
|
|
26
26
|
<!-- uscha:orientation-block:begin -->
|
|
27
|
-
<!-- uscha kit: 2.
|
|
27
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
28
28
|
|
|
29
29
|
## First contact (show ONCE, then never again)
|
|
30
30
|
|
|
@@ -61,6 +61,14 @@ block onward, derived state wins.
|
|
|
61
61
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
62
62
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
63
63
|
|
|
64
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
65
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
66
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
67
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
68
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
69
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
70
|
+
below.
|
|
71
|
+
|
|
64
72
|
**Open every turn with a breadcrumb**, then the content:
|
|
65
73
|
|
|
66
74
|
`[uscha · reverse-discovery · <step> → <target>]`
|
|
@@ -22,7 +22,7 @@ the grader — this skill just wraps the neutral prompt so Claude Code users get
|
|
|
22
22
|
in one command. Never add Claude-specific behavior to the contract.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -57,6 +57,14 @@ block onward, derived state wins.
|
|
|
57
57
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
58
58
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
59
59
|
|
|
60
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
61
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
62
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
63
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
64
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
65
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
66
|
+
below.
|
|
67
|
+
|
|
60
68
|
**Open every turn with a breadcrumb**, then the content:
|
|
61
69
|
|
|
62
70
|
`[uscha · rubric · <step> → <target>]`
|
|
@@ -58,7 +58,7 @@ that surface the warning cannot come from the skill itself. The `doctor` seam is
|
|
|
58
58
|
that still works there — it runs from any kit checkout and reads the installs from outside.
|
|
59
59
|
|
|
60
60
|
<!-- uscha:orientation-block:begin -->
|
|
61
|
-
<!-- uscha kit: 2.
|
|
61
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
62
62
|
|
|
63
63
|
## Orientation markers (non-negotiable)
|
|
64
64
|
|
|
@@ -68,6 +68,10 @@ This skill is a **one-shot read-only readout**: its block IS the answer. It ther
|
|
|
68
68
|
take the conversational close block — that would be exactly the padding this skill forbids.
|
|
69
69
|
It carries the two minimal markers instead.
|
|
70
70
|
|
|
71
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
72
|
+
statusline — Codex, pi, a plain terminal — this readout IS the operator's only signal, so it
|
|
73
|
+
MUST appear in the visible reply text, never only inside a collapsed tool-call log.
|
|
74
|
+
|
|
71
75
|
**Open with a breadcrumb:**
|
|
72
76
|
|
|
73
77
|
`[uscha · status · step <n> → <target>]`
|
|
@@ -22,7 +22,7 @@ switch between at any time:
|
|
|
22
22
|
coverage, known deferred issues.
|
|
23
23
|
|
|
24
24
|
<!-- uscha:orientation-block:begin -->
|
|
25
|
-
<!-- uscha kit: 2.
|
|
25
|
+
<!-- uscha kit: 2.4.0 -- generated region: edit tools/skill-blocks/, then run `python tools/gen-skill-blocks.py` (never this block by hand) -->
|
|
26
26
|
|
|
27
27
|
## First contact (show ONCE, then never again)
|
|
28
28
|
|
|
@@ -57,6 +57,14 @@ block onward, derived state wins.
|
|
|
57
57
|
The operator must never have to ask "where am I?" or "what happens now?". Two markers, always.
|
|
58
58
|
They are navigation, not ceremony: one line per turn, one block at the end.
|
|
59
59
|
|
|
60
|
+
**No statusline (kit 2.4.0): the visible reply IS the statusline.** On a surface with no live
|
|
61
|
+
statusline — Codex, pi, a plain terminal — these markers are the only signal the operator gets,
|
|
62
|
+
so they MUST appear in the visible reply text, never only inside a collapsed tool-call log the
|
|
63
|
+
operator may not expand. On such a surface, the FINAL message of every turn STARTS with the
|
|
64
|
+
compact `uscha-status` block (derived phase · loop · measured acceptance · next criterion, read
|
|
65
|
+
from the ledger — see the `uscha-status` skill), immediately before the breadcrumb/close marker
|
|
66
|
+
below.
|
|
67
|
+
|
|
60
68
|
**Open every turn with a breadcrumb**, then the content:
|
|
61
69
|
|
|
62
70
|
`[uscha · sysdoc · <step> → <target>]`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "COMPREHENSIVE REFERENCE, not a project config. Every knob the engine and the skills understand, at the kit's own value, so a human can read what can be declared. `uscha init` does NOT copy this file: it GENERATES a minimal project config, because a copied default is an explicit declaration and an explicit declaration outranks the preset named by defaults.risk_profile (ADR-001, as amended). Copy a block from here into your project only when you mean to override the engine default or the preset. NOTE: no version string may be written into this comment -- the release script requires exactly one occurrence of the version in this file (I3), and a second one refuses the next release.",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"project": null,
|
|
5
5
|
"defaults": {
|
|
6
6
|
"coverage_threshold": 60,
|