@andresmassello/uscha 1.91.0 → 1.93.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 +3 -3
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +20 -0
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +433 -16
- package/uscha-kit/.claude/skills/uscha-devloop/uscha_top.py +58 -4
- package/uscha-kit/.claude-plugin/plugin.json +2 -2
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +2 -2
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/skills/uscha-devloop/SKILL.md +20 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +433 -16
- package/uscha-kit/skills/uscha-devloop/uscha_top.py +58 -4
- package/uscha-kit/templates/esceptico-prompt.md +65 -0
- package/uscha-kit/uscha.config.json +1 -1
|
@@ -131,6 +131,17 @@ ACTIONS = {
|
|
|
131
131
|
"TAGGED": "machine: run the case",
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
# INV-TOP-06 (ADR-038): what a row that is green ON PAPER is actually waiting on when the
|
|
135
|
+
# seal is broken. Presentation, like ACTIONS above: the engine says WHAT broke (the reason),
|
|
136
|
+
# this says what the reader does about it, and the mapping is by reason prefix so a new
|
|
137
|
+
# reason class degrades to the generic line instead of to silence.
|
|
138
|
+
SEAL_ACTIONS = (
|
|
139
|
+
("stale seal", "seal: snapshot at HEAD"),
|
|
140
|
+
("repo subtree dirty", "seal: commit or discard, then snapshot"),
|
|
141
|
+
("evidence altered", "seal: re-run the suite, then snapshot"),
|
|
142
|
+
("evidence missing", "seal: re-run the suite, then snapshot"),
|
|
143
|
+
)
|
|
144
|
+
|
|
134
145
|
|
|
135
146
|
# --------------------------------------------------------------------------- #
|
|
136
147
|
# pure rendering #
|
|
@@ -216,7 +227,15 @@ def _pct_line(terminado):
|
|
|
216
227
|
"""INV-TOP-01: the DONE bar carries an explicit `N unmeasured` suffix whenever anything
|
|
217
228
|
is unmeasured, and the engine has already capped the percentage below 100 while any
|
|
218
229
|
obligation sits outside MEASURED_PASS -- the renderer republishes that fact, it never
|
|
219
|
-
recomputes it (AC-T-01, AC-T-04, AC-T-23).
|
|
230
|
+
recomputes it (AC-T-01, AC-T-04, AC-T-23).
|
|
231
|
+
|
|
232
|
+
INV-TOP-06 (ADR-038) rides on the same line: when the engine's seal is MEASURED broken
|
|
233
|
+
(`terminado.sealed.ok is False`) the bar says so and names the first reason -- and the
|
|
234
|
+
percentage beside it is already capped below 100, in the engine, for the same reason the
|
|
235
|
+
unmeasured cap is (single derivation, AC-T-24). An UNMEASURED seal (`ok is null`: no git
|
|
236
|
+
work tree, the state of every frozen fixture) adds NOTHING here: the seal is shown only
|
|
237
|
+
when it is measured, and decorating a header with the absence of a measurement would
|
|
238
|
+
turn INV-TOP-05's `—` into noise on every board."""
|
|
220
239
|
done = terminado.get("done")
|
|
221
240
|
total = terminado.get("total")
|
|
222
241
|
pct = terminado.get("pct")
|
|
@@ -224,9 +243,37 @@ def _pct_line(terminado):
|
|
|
224
243
|
line = "DONE %s/%s (%s%%)" % (_num(done), _num(total), _num(pct))
|
|
225
244
|
if unm:
|
|
226
245
|
line += " %s %d unmeasured" % (MID, unm)
|
|
246
|
+
# the state is a FILE a human can hand us (`--state`), so `sealed` is guarded by TYPE and
|
|
247
|
+
# not merely by truthiness: a string there would answer `.get` with an AttributeError, and a
|
|
248
|
+
# `reasons` that is a string is iterable -- the frame would name its first CHARACTER as the
|
|
249
|
+
# reason. Same guards `_top_spec_diff` applies on the engine side, for the same reason.
|
|
250
|
+
seal = terminado.get("sealed")
|
|
251
|
+
seal = seal if isinstance(seal, dict) else {}
|
|
252
|
+
if seal.get("ok") is False:
|
|
253
|
+
raw = seal.get("reasons")
|
|
254
|
+
reasons = [r for r in raw if isinstance(r, str) and r] if isinstance(raw, list) else []
|
|
255
|
+
line += " %s unsealed (%s)" % (MID, _safe(reasons[0]) if reasons
|
|
256
|
+
else "no reason recorded")
|
|
227
257
|
return line
|
|
228
258
|
|
|
229
259
|
|
|
260
|
+
def _seal_action(sealed):
|
|
261
|
+
"""The ACTION cell of a row that is green on paper while the seal is broken. Empty
|
|
262
|
+
whenever the seal is not MEASURED broken -- an unmeasured seal changes no row, and a
|
|
263
|
+
`sealed` of the wrong TYPE reads as no seal at all rather than raising mid-frame."""
|
|
264
|
+
seal = sealed if isinstance(sealed, dict) else {}
|
|
265
|
+
if seal.get("ok") is not False:
|
|
266
|
+
return ""
|
|
267
|
+
raw = seal.get("reasons")
|
|
268
|
+
for reason in (raw if isinstance(raw, list) else []):
|
|
269
|
+
if not isinstance(reason, str):
|
|
270
|
+
continue
|
|
271
|
+
for prefix, action in SEAL_ACTIONS:
|
|
272
|
+
if reason.startswith(prefix):
|
|
273
|
+
return action
|
|
274
|
+
return "seal: re-snapshot the current state"
|
|
275
|
+
|
|
276
|
+
|
|
230
277
|
def _burnup_line(burnup, cols):
|
|
231
278
|
"""The score trend, labelled as a score trend. v0.1 has no obligation-count history
|
|
232
279
|
(ADR-035/2), so calling this a burn-up of closed obligations would be a lie the label
|
|
@@ -264,15 +311,21 @@ def _cases_text(ob):
|
|
|
264
311
|
return "%s/%s" % (_num(ob.get("cases_pass")), total)
|
|
265
312
|
|
|
266
313
|
|
|
267
|
-
def _row(ob, selected):
|
|
314
|
+
def _row(ob, selected, seal_action=""):
|
|
268
315
|
# the three left columns are cut and padded in COLUMNS: an id or state carrying wide
|
|
269
316
|
# characters used to eat its neighbour's field and walk every column after it.
|
|
270
317
|
gutter = "> " if selected else " "
|
|
318
|
+
action = ACTIONS.get(ob.get("state"), DASH)
|
|
319
|
+
# INV-TOP-06: only the rows that CLAIM to be done change, and only while the seal is
|
|
320
|
+
# measured broken. A failing or unmeasured row already names its own debtor; telling it
|
|
321
|
+
# about the seal too would bury the thing it is actually waiting for.
|
|
322
|
+
if seal_action and ob.get("state") == "MEASURED_PASS":
|
|
323
|
+
action = seal_action
|
|
271
324
|
return "%s%s%s%s%7s%5s %s" % (
|
|
272
325
|
gutter, _pad(_cut(_safe(ob.get("id") or "?"), 8), 8),
|
|
273
326
|
_pad(_cut(_safe(ob.get("gate") or DASH), 8), 9),
|
|
274
327
|
_pad(_cut(_safe(ob.get("state") or "?"), 14), 15), _cases_text(ob),
|
|
275
|
-
_num(ob.get("age_hours")),
|
|
328
|
+
_num(ob.get("age_hours")), action)
|
|
276
329
|
|
|
277
330
|
|
|
278
331
|
def _safe(text):
|
|
@@ -382,8 +435,9 @@ def _render_board(state, size, sel, plain, status=""):
|
|
|
382
435
|
top = max(0, top)
|
|
383
436
|
|
|
384
437
|
table = []
|
|
438
|
+
seal_action = _seal_action(terminado.get("sealed"))
|
|
385
439
|
for i, ob in enumerate(obligations[top:top + body], start=top):
|
|
386
|
-
line = _fit(_row(ob, i == sel), cols)
|
|
440
|
+
line = _fit(_row(ob, i == sel, seal_action), cols)
|
|
387
441
|
table.append(line if plain else _colorize(line, ob.get("state")))
|
|
388
442
|
hidden = len(obligations) - len(table)
|
|
389
443
|
if hidden > 0:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.93.0",
|
|
5
5
|
"displayName": "Uscha",
|
|
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,
|
|
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, 53 subcommands + universal installer + npm/npx router). Facts block, guesses advise; the human approves.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Andres Massello",
|
|
9
9
|
"url": "https://github.com/andresmassello"
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v1.
|
|
3
|
+
**Kit version:** v1.93.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`,
|
|
@@ -591,7 +591,7 @@ python3 $QL simplicity-check --diff changes.diff --json # consumed by usc
|
|
|
591
591
|
|
|
592
592
|
## Ledger subcommands
|
|
593
593
|
|
|
594
|
-
`bench - bench-curate - bench-r2 - bench-roundtrip - bootstrap-oracle - bootstrap-variance - check-coverage - cleanroom - compile-ingest - compile-validate - converged - curate - curation-check - dashboard - discover - doctor - escalate - execution-policy - facts - fastpath-eval - fidelity - flag-blocker - gate-check - golden-coverage - golden-diff - ingest-gate - init - ir-extract - ir-render - lang-compare - log-gate - log-step - oscillation - phase - pit-check - production-finding - promote - readiness - rebuild - regression-check - resolve-escalation - roundtrip - rubric-ingest - simplicity-check - snapshot - spec-change-request - spec-check - spec-doubt - spec-drift - summary - top - waste-check` - the exact current `qa_ledger.py` parser surface (
|
|
594
|
+
`bench - bench-curate - bench-r2 - bench-roundtrip - bootstrap-oracle - bootstrap-variance - check-coverage - check-terminado - cleanroom - compile-ingest - compile-validate - converged - curate - curation-check - dashboard - discover - doctor - escalate - execution-policy - facts - fastpath-eval - fidelity - flag-blocker - gate-check - golden-coverage - golden-diff - ingest-gate - init - ir-extract - ir-render - lang-compare - log-gate - log-step - oscillation - phase - pit-check - production-finding - promote - readiness - rebuild - regression-check - resolve-escalation - roundtrip - rubric-ingest - simplicity-check - snapshot - spec-change-request - spec-check - spec-doubt - spec-drift - summary - top - waste-check` - the exact current `qa_ledger.py` parser surface (53 subcommands, derived from `SYSTEM-FACTS.json`, itself introspected from `build_parser()`); each supports `--help`.
|
|
595
595
|
|
|
596
596
|
The **fact gates** (golden-diff, gate-check, pit-check, simplicity) are PERSISTED with
|
|
597
597
|
`log-gate`: a fail blocks convergence and caps readiness ≤65 via the ledger. A CONSTITUTION
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 1.
|
|
1
|
+
uscha-kit 1.93.0
|
|
@@ -459,6 +459,20 @@ BLOCKER/CRITICAL + no open escalation), never self-declared — if it exits 1, t
|
|
|
459
459
|
output lists exactly which facts are missing; do NOT open the PR, close the gap.
|
|
460
460
|
A `spike/*` branch NEVER passes this gate (kit 1.19.0): spike code is disposable
|
|
461
461
|
by contract — its only legitimate output is an ADR with lessons, never a merge.
|
|
462
|
+
- **Before declaring TERMINADO, run the seal (kit 1.92.0, INV-T1 / ADR-038):**
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
python3 $QL check-terminado # 0 = sealed · 1 = broken · 2 = UNMEASURED
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
It recomputes, from the ledger and the tree, whether the recorded evidence still belongs to
|
|
469
|
+
the code on disk: the repo subtree clean, no source-relevant change since the last
|
|
470
|
+
snapshot's commit (a non-source difference — docs, the ledger, the reports themselves —
|
|
471
|
+
seals with a `note` naming what moved; ADR-039), every ingested report still hashing to
|
|
472
|
+
what was recorded. **Exit 1** — do not declare TERMINADO:
|
|
473
|
+
re-snapshot on the CURRENT state (`snapshot --repo <REPO> --phase post`) and record why the
|
|
474
|
+
seal broke. **Exit 2** — the seal is UNMEASURED (no git, or no snapshot recorded): say so
|
|
475
|
+
plainly; an answer nobody could measure is not a TERMINADO either.
|
|
462
476
|
- Ensure conventional-commit history is clean.
|
|
463
477
|
- Open the PR(s). Confirm CI is green.
|
|
464
478
|
- **STOP.** Present the PR link(s) and wait for the human to merge.
|
|
@@ -544,6 +558,12 @@ up as `narrated_only` and does NOT close (measured beats narrated, per criterion
|
|
|
544
558
|
A JUnit report older than the repo's source code is treated as STALE (the code changed
|
|
545
559
|
after the tests ran) and is DISCARDED — a criterion backed only by stale reports stays
|
|
546
560
|
UNMEASURED, never falsely closed or vetoed (kit 1.31.0; surfaced as `stale_reports`).
|
|
561
|
+
Since 1.93.0 (ADR-039) that clock rule is not the only one: a report the clock rejects is still
|
|
562
|
+
FRESH when its `sha256` matches what the last `snapshot` recorded for it AND git shows no
|
|
563
|
+
source-relevant change since that snapshot's commit — so a clone, a `git worktree add`, a merge
|
|
564
|
+
or a CI checkout, which re-date every file without changing a byte, no longer un-measure green
|
|
565
|
+
evidence. Either rule suffices; with no git, no recorded commit or no recorded hash the clock
|
|
566
|
+
rule decides alone, exactly as before.
|
|
547
567
|
So: when you write the tests for a criterion, put its AC-n in the test name; run
|
|
548
568
|
`spec-check --acceptance ACCEPTANCE.md` up front (zero traceable criteria / duplicate
|
|
549
569
|
IDs block as structural FACTS). Files without IDs fall back to the checkbox ratio
|