@andresmassello/uscha 1.88.0 → 1.89.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 +2 -2
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +69 -22
- package/uscha-kit/.claude/skills/uscha-devloop/uscha_top.py +419 -13
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +1 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +8 -1
- package/uscha-kit/reports/junit/.top-cases.json +1 -1
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +69 -22
- package/uscha-kit/skills/uscha-devloop/uscha_top.py +419 -13
- package/uscha-kit/uscha.config.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip
|
|
|
40
40
|
runtime dependencies). The npm package is a thin router; the canonical installer is
|
|
41
41
|
`uscha-kit/install-uscha.py`.
|
|
42
42
|
|
|
43
|
-
**Kit v1.
|
|
43
|
+
**Kit v1.89.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
|
|
44
44
|
[changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG.md)
|
|
45
45
|
(the per-release changelogs live in the repo, not in the npm tarball)
|
|
46
46
|
|
|
@@ -85,7 +85,7 @@ automatic tool can perform: a human verdict.
|
|
|
85
85
|
from the compiled code: 0.062 measured (12 archetypes) — names, not yet semantics
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
**What each arrow is, in the engine (kit 1.
|
|
88
|
+
**What each arrow is, in the engine (kit 1.89.0, 52 subcommands, all measured):**
|
|
89
89
|
|
|
90
90
|
| Leg | Subcommands | What it establishes |
|
|
91
91
|
|---|---|---|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.89.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",
|
|
@@ -8234,6 +8234,7 @@ def _top_ac_key(cid):
|
|
|
8234
8234
|
|
|
8235
8235
|
TOP_EVENTS_TAIL = 8 # how many steps the feed carries; the TUI shows what fits
|
|
8236
8236
|
TOP_EVENT_WIDTH = 72 # one feed line, short enough to survive the 80-column floor
|
|
8237
|
+
TOP_OBS_TITLE_WIDTH = 72 # the verdict queue's one-line label; the CLAIM is never capped
|
|
8237
8238
|
|
|
8238
8239
|
# kind -> level, the FIXED map ADR-032 (amended 1.88.0, M2) requires. `level` and `text` do
|
|
8239
8240
|
# not exist in `ledger["steps"]`; they are derived here, once, so the TUI renders a feed it
|
|
@@ -8281,20 +8282,66 @@ def _top_key(value):
|
|
|
8281
8282
|
return repr(value)
|
|
8282
8283
|
|
|
8283
8284
|
|
|
8284
|
-
def
|
|
8285
|
-
"""One
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
txt = "
|
|
8292
|
-
txt = "".join(" " if c in ("\t", "\n", "\r") else c for c in txt)
|
|
8285
|
+
def _top_clean(text):
|
|
8286
|
+
"""One state-supplied string with every control character gone and its whitespace
|
|
8287
|
+
collapsed. The contract's free text is ledger prose (an escalation reason, an
|
|
8288
|
+
observation statement, a tool name) -- human and CLI input -- so an ESC or a C0 byte
|
|
8289
|
+
inside one would be a control sequence the board obeys instead of prints. It dies HERE,
|
|
8290
|
+
in the engine, and the renderer drops it again on the way out: two cheap guards over one
|
|
8291
|
+
attack surface. What is filtered is exactly C0 and DEL (ADR-032)."""
|
|
8292
|
+
txt = "".join(" " if c in ("\t", "\n", "\r") else c for c in str(text))
|
|
8293
8293
|
txt = "".join(c for c in txt if ord(c) >= 32 and ord(c) != 127)
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8294
|
+
return " ".join(txt.split())
|
|
8295
|
+
|
|
8296
|
+
|
|
8297
|
+
def _top_cap(text, width):
|
|
8298
|
+
"""A capped string that SAYS it was cut. Never used on a claim the human has to judge --
|
|
8299
|
+
only on labels (a feed line, an observation title), whose full text the contract carries
|
|
8300
|
+
somewhere else (M3: `candidate[]` holds the whole claim, `title` only its head)."""
|
|
8301
|
+
return text if len(text) <= width else text[:width - 1] + "…"
|
|
8302
|
+
|
|
8303
|
+
|
|
8304
|
+
def _top_event_text(*parts):
|
|
8305
|
+
"""One feed line: free text of the contract that reaches a terminal, sanitized and
|
|
8306
|
+
capped to survive the 80-column floor."""
|
|
8307
|
+
return _top_cap(_top_clean(" · ".join(str(p) for p in parts
|
|
8308
|
+
if p not in (None, "", "?"))), TOP_EVENT_WIDTH)
|
|
8309
|
+
|
|
8310
|
+
|
|
8311
|
+
def _top_obs_view(o, repo):
|
|
8312
|
+
"""ONE uncurated observation as the verdicts queue reads it (ADR-032, amended 1.89.0 for
|
|
8313
|
+
M3). The delta record is the only source: `type`, `statement`, `provenance.files`,
|
|
8314
|
+
`evidence_class` -- nothing here is inferred, and a member the delta does not carry comes
|
|
8315
|
+
out empty rather than guessed.
|
|
8316
|
+
|
|
8317
|
+
- `repo` is the delta's OWN repo, and it is in the contract because the write path needs
|
|
8318
|
+
it: `curate` takes `--repo`, so a queue without it would force the TUI to pick one --
|
|
8319
|
+
the one derivation the TUI is never allowed to make (ADR-033).
|
|
8320
|
+
- `title` is the statement's HEAD, capped and marked with an ellipsis when cut. It is a
|
|
8321
|
+
label for the list line only. The claim the human judges is in `candidate[]` in full:
|
|
8322
|
+
a verdict recorded on half a sentence is the failure this split exists to prevent.
|
|
8323
|
+
- a delta is JSON on disk, so `provenance` can arrive as a list from a hand edit -- the
|
|
8324
|
+
read-only readout degrades that observation to no evidence line, it does not raise."""
|
|
8325
|
+
prov = o.get("provenance") if isinstance(o.get("provenance"), dict) else {}
|
|
8326
|
+
raw = prov.get("files") if isinstance(prov.get("files"), list) else []
|
|
8327
|
+
files = [f for f in (_top_clean(x) for x in raw) if f]
|
|
8328
|
+
statement = _top_clean(o.get("statement"))
|
|
8329
|
+
otype = _top_clean(o.get("type"))
|
|
8330
|
+
site = files[0] if files else ""
|
|
8331
|
+
candidate = ["type: %s%s" % (otype or "?", (" · site: %s" % site) if site else "")]
|
|
8332
|
+
if statement:
|
|
8333
|
+
candidate.append("claim: " + statement) # WHOLE, never capped
|
|
8334
|
+
evidence = list(files)
|
|
8335
|
+
cls, tool = _top_clean(o.get("evidence_class")), _top_clean(prov.get("tool"))
|
|
8336
|
+
if cls or tool:
|
|
8337
|
+
evidence.append("evidence_class: %s%s" % (cls or "?",
|
|
8338
|
+
(" · tool: %s" % tool) if tool else ""))
|
|
8339
|
+
return {"id": o.get("id"), "ac": o.get("canonical_match"), "repo": repo,
|
|
8340
|
+
"title": _top_cap(statement, TOP_OBS_TITLE_WIDTH) if statement else None,
|
|
8341
|
+
"candidate": candidate, "evidence": evidence,
|
|
8342
|
+
# no per-observation first-seen timestamp exists (ADR-032/035): the queue is
|
|
8343
|
+
# ordered by the criterion it anchors, not by an age nobody recorded.
|
|
8344
|
+
"age_hours": None}
|
|
8298
8345
|
|
|
8299
8346
|
|
|
8300
8347
|
def _top_events(ledger, limit=TOP_EVENTS_TAIL):
|
|
@@ -8444,15 +8491,15 @@ def cmd_top(args):
|
|
|
8444
8491
|
cid = o.get("canonical_match")
|
|
8445
8492
|
if cid and cid not in quarantine:
|
|
8446
8493
|
quarantine[cid] = o["id"]
|
|
8447
|
-
observations.append(
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8494
|
+
observations.append(_top_obs_view(o, rname))
|
|
8495
|
+
# the queue's order: the criterion each observation anchors first (by the same key the
|
|
8496
|
+
# board orders obligations with), the unanchored ones after, and the content-addressed
|
|
8497
|
+
# id as the tie-break. Deterministic given the delta -- which is what lets a golden frame
|
|
8498
|
+
# be the oracle for the verdicts pane. AGE-descending, which the SPEC drafted, is not
|
|
8499
|
+
# derivable: every age_hours is null (no first-seen timestamp), and ordering by a value
|
|
8500
|
+
# that does not exist would be the fabrication INV-TOP-05 forbids.
|
|
8501
|
+
observations.sort(key=lambda o: ((_top_ac_key(o["ac"]) if o.get("ac") else (3, "", 0)),
|
|
8502
|
+
o.get("id") or ""))
|
|
8456
8503
|
|
|
8457
8504
|
# obligations: one row per DISTINCT tagged criterion of the acceptance file. kind is
|
|
8458
8505
|
# "AC" for all of them -- there is no per-INV ledger in the general path (the mirador's
|