@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
@@ -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 —