@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 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.88.0** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
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.0, 52 subcommands, all measured):**
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.88.0",
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 _top_event_text(*parts):
8285
- """One feed line: the only free text of the whole contract that reaches a terminal.
8286
-
8287
- Its ingredients are ledger prose (an escalation reason, a tool name) -- human and CLI
8288
- input -- so an ESC or a C0 byte inside one would be a control sequence the board prints
8289
- verbatim. Every control character is dropped HERE, in the engine, and the renderer drops
8290
- them again on the way out: two cheap guards over one attack surface."""
8291
- txt = " · ".join(str(p) for p in parts if p not in (None, "", "?"))
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
- txt = " ".join(txt.split())
8295
- if len(txt) > TOP_EVENT_WIDTH:
8296
- txt = txt[:TOP_EVENT_WIDTH - 1] + "…"
8297
- return txt
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
- "id": o.get("id"), "ac": cid,
8449
- # no separate short label exists on an observation; `statement` is the only
8450
- # prose field, so `title` is null and the TUI may head-truncate candidate[0]
8451
- "title": None,
8452
- "candidate": [o.get("statement")],
8453
- "evidence": list((o.get("provenance") or {}).get("files") or []),
8454
- "age_hours": None})
8455
- observations.sort(key=lambda o: o.get("id") or "")
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