@andresmassello/uscha 1.97.0 → 1.99.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 +20 -15
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +116 -17
- 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/skills/uscha-devloop/qa_ledger.py +116 -17
- package/uscha-kit/templates/CONSTITUTION.md +1 -1
- 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.99.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
|
|
|
@@ -169,7 +169,7 @@ automatic tool can perform: a human verdict.
|
|
|
169
169
|
system (= 100% drift)
|
|
170
170
|
|
|
171
171
|
round trip · bench-roundtrip — how much of the asset the reverse organs re-anchor
|
|
172
|
-
from the compiled code: 0.
|
|
172
|
+
from the compiled code: 0.815 measured (12 archetypes) — names AND behaviour
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
**What each arrow is, in the engine (kit 1.96.0, 53 subcommands, all measured):**
|
|
@@ -178,25 +178,30 @@ automatic tool can perform: a human verdict.
|
|
|
178
178
|
|---|---|---|
|
|
179
179
|
| Asset → typed graph | `ir-extract`, `ir-render` | the whole package becomes one canonical IR (M2, ADR-015) — deterministic, `UNTYPED` is a measurement not an error |
|
|
180
180
|
| Forward, the compiler | `compile-validate`, `compile-ingest` | any model produces code; the engine validates the output contract and never compiles (M3, ADR-016) |
|
|
181
|
-
| Forward, is it the *same* system? | `bootstrap-oracle`, `bootstrap-variance`, `bench` | a withheld oracle judges blind compilations — **12 archetypes,
|
|
181
|
+
| Forward, is it the *same* system? | `bootstrap-oracle`, `bootstrap-variance`, `bench` | a withheld oracle judges blind compilations — **12 archetypes, 8 PASS · 4 PARTIAL**, four blind compilers across two vendors (Haiku · Sonnet · Opus · OpenAI Codex `gpt-5.5`), JS included (M4/M5, ADR-017/018/028/029/042) |
|
|
182
182
|
| Reverse, facts | `discover`, `golden-diff` (+ the `/uscha-characterize` skill) | system map + mechanically captured golden; typed candidate observations with evidence class (M1, ADR-013) |
|
|
183
183
|
| Reverse, the human gate | `curate`, `promote`, `curation-check`, `bench-curate` | one verdict per candidate, append-only ledger verified against git; unjudged → `pr-ready` blocked naming it (ADR-009/010, INV-CURATION-01) |
|
|
184
|
-
| Fidelity, honestly | `fidelity`, `roundtrip`, `bench-roundtrip`, `bench-r2` | per-compiler fidelity vector, id-level round trip, recoverability **0.
|
|
185
|
-
|
|
186
|
-
**Read the numbers the way the repo does.**
|
|
187
|
-
under an oracle the compilers never saw — that is the closed loop working.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
184
|
+
| Fidelity, honestly | `fidelity`, `roundtrip`, `bench-roundtrip`, `bench-r2` | per-compiler fidelity vector, id-level round trip, recoverability **0.815**, and the **noise floor** under every variance claim (ADR-014/022/027/030) |
|
|
185
|
+
|
|
186
|
+
**Read the numbers the way the repo does.** 8 of 12 archetypes regenerate to the same system
|
|
187
|
+
under an oracle the compilers never saw — that is the closed loop working. It was 9 of 12 until
|
|
188
|
+
1.99.0, when a fourth compiler from a second vendor read one genuinely ambiguous sentence in
|
|
189
|
+
`transformer` the other way and lost a case the three Claude-family models had agreed on
|
|
190
|
+
(ADR-042). The number went down because the bench got harder, and the whole point of the arm was
|
|
191
|
+
to let it. 0.815 is the mean *recoverability* of the asset from compiled code counting only
|
|
192
|
+
static and behavioural footing. It read **0.062** until 1.90.0, with the behaviour dimension
|
|
193
|
+
`UNMEASURED`, because no oracle case carried an AC tag — a named absence, not a zero. The 12
|
|
194
|
+
bench oracles are now curated per case (`ORACLE-TAGS-CURATED.json`, human-authored; payloads and
|
|
195
|
+
expectations untouched), so the dimension is measured and the number says the reverse organs
|
|
196
|
+
anchor **names and behaviour**; it read 0.828 over three compilers and 0.815 over four, because
|
|
197
|
+
an entry's recoverability is the mean over its compilations. Every number is published rather
|
|
198
|
+
than smoothed, and what moved between them was the tagging and the arm, not the code under test.
|
|
199
|
+
And `bench-r2` measured
|
|
195
200
|
that same-model reruns differ structurally about as much as different models do (aggregate
|
|
196
201
|
`NOISY`) — so one earlier variance narrative was **retracted**. Every claim above is a subcommand
|
|
197
202
|
you can run; every unmeasured part is labeled. That honesty is the method applied to itself.
|
|
198
203
|
|
|
199
|
-
→ The full thesis, with before/after diagrams and the REAL vs VISION table:
|
|
204
|
+
→ The full thesis, with before/after diagrams and the REAL vs VISION vs REJECTED table:
|
|
200
205
|
**[uscha.dev/diamond](https://uscha.dev/diamond)** · the mechanism, in three diagrams:
|
|
201
206
|
**[uscha.dev/how](https://uscha.dev/how)**
|
|
202
207
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.99.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",
|
|
@@ -4140,6 +4140,47 @@ CANDIDATE_DELTA_FILE = "CANDIDATE-DELTA.json" # under discovery/, machine-can
|
|
|
4140
4140
|
CANDIDATE_DELTA_TWIN = "CANDIDATE-DELTA.md" # rendered view, regenerated, never a source
|
|
4141
4141
|
CANONICAL_FILE = "CANONICAL.json" # under discovery/: the promoted package
|
|
4142
4142
|
ISSUES_DEFERRED_FILE = "ISSUES-DEFERRED.md"
|
|
4143
|
+
# The shape of the work item `promote` writes for a `fix` verdict, and the shape it recognises
|
|
4144
|
+
# again on the next run. The dedupe used to be `o["id"] not in existing` -- a RAW SUBSTRING over
|
|
4145
|
+
# the whole file, so it suppressed the work item for any id merely MENTIONED in prose there and,
|
|
4146
|
+
# because the ids are a hash prefix, for any id that is a prefix of an already-written one
|
|
4147
|
+
# (1.69.0 fresh review, LOW, resolved 1.98.0). The question is not "does this text occur"; it is
|
|
4148
|
+
# "does this file already carry the WORK ITEM for this observation", and only the line shape
|
|
4149
|
+
# answers it. The word boundaries are what separate OBS-1 from OBS-10, and the `.*` between
|
|
4150
|
+
# the checkbox and the id is what lets a HUMAN reword the item -- prefix it with a date, a
|
|
4151
|
+
# severity, an owner -- without `promote` appending a second copy of it on the next run.
|
|
4152
|
+
_DEFERRED_ITEM_LINE = r"(?m)^[ \t]*[-*][ \t]*\[[ xX]\].*\b%s\b"
|
|
4153
|
+
|
|
4154
|
+
|
|
4155
|
+
def _deferred_carries(existing, oid):
|
|
4156
|
+
"""True when ISSUES-DEFERRED.md already holds the `- [ ] <oid>` work item for this OBS."""
|
|
4157
|
+
return re.search(_DEFERRED_ITEM_LINE % re.escape(oid), existing) is not None
|
|
4158
|
+
|
|
4159
|
+
|
|
4160
|
+
def _config_beside_ledger(config, ledger_path):
|
|
4161
|
+
"""Where to LOOK for a config whose default is a bare relative name.
|
|
4162
|
+
|
|
4163
|
+
`--config` defaulted to the literal `uscha.config.json`, which `os.path.isfile` resolves
|
|
4164
|
+
against the CWD. Run `fidelity --ledger /repo/QA-LEDGER.json` from anywhere but /repo and
|
|
4165
|
+
the config beside the ledger was never opened -- so `defaults.fidelity.gate` was silently
|
|
4166
|
+
not declared, and the INV-ADVISORY-01 refusal that reads it never fired. An UNNAMED absence
|
|
4167
|
+
(1.69.0 fresh review, LOW, resolved 1.98.0): the command printed a full vector and exit 0,
|
|
4168
|
+
exactly as it does when there genuinely is no config.
|
|
4169
|
+
|
|
4170
|
+
The cwd still WINS when it holds the file -- an explicit `--config` next to you is what you
|
|
4171
|
+
meant, and no existing invocation changes behaviour. Only when it does not is the ledger's
|
|
4172
|
+
own directory tried. Returns (path, found): callers report `path` so the answer to "which
|
|
4173
|
+
file did you read" is in the output rather than in the reader's head.
|
|
4174
|
+
"""
|
|
4175
|
+
if os.path.isfile(config):
|
|
4176
|
+
return config, True
|
|
4177
|
+
if not os.path.isabs(config):
|
|
4178
|
+
beside = os.path.join(os.path.dirname(os.path.abspath(ledger_path)), config)
|
|
4179
|
+
if os.path.isfile(beside):
|
|
4180
|
+
return beside, True
|
|
4181
|
+
return config, False
|
|
4182
|
+
|
|
4183
|
+
|
|
4143
4184
|
OBS_TYPES = ("behavior", "invariant", "contract", "config", "dependency", "decision_trace")
|
|
4144
4185
|
EVIDENCE_CLASSES = ("measured", "static", "narrated")
|
|
4145
4186
|
# ADR-014 / INV-ADVISORY-01: dimensions an LLM judges can only advise. The QUARANTINE is an
|
|
@@ -4445,6 +4486,17 @@ def _match_canonical(statement, canon_ids):
|
|
|
4445
4486
|
return None
|
|
4446
4487
|
|
|
4447
4488
|
|
|
4489
|
+
def _md_cell(text):
|
|
4490
|
+
"""One markdown TABLE cell. A `|` is escaped, and any CR/LF inside the value collapses to
|
|
4491
|
+
a space -- a markdown row ENDS at a newline, so a statement or a provenance ref carrying
|
|
4492
|
+
one used to split its observation across two rows and corrupt every column after it
|
|
4493
|
+
(1.69.0 fresh review, LOW, resolved 1.98.0). The JSON and the OBS id always survived; only
|
|
4494
|
+
this rendered view broke, and it is the artifact the human curates from. A space rather
|
|
4495
|
+
than `<br>`: every other cell is plain text, and a lone HTML tag in one column would be
|
|
4496
|
+
the only markup in the table."""
|
|
4497
|
+
return re.sub(r"[\r\n]+", " ", "%s" % text).replace("|", "\\|")
|
|
4498
|
+
|
|
4499
|
+
|
|
4448
4500
|
def _render_delta_md(delta, verdicts):
|
|
4449
4501
|
lines = ["<!-- %s -->" % _DELTA_BANNER, "",
|
|
4450
4502
|
"# CANDIDATE-DELTA (rendered view)", ""]
|
|
@@ -4460,9 +4512,10 @@ def _render_delta_md(delta, verdicts):
|
|
|
4460
4512
|
for o in delta["observations"]:
|
|
4461
4513
|
v = verdicts.get(o["id"], "(uncurated)")
|
|
4462
4514
|
files = ", ".join(o["provenance"].get("files") or []) or "-"
|
|
4463
|
-
stmt = o["statement"].replace("|", "\\|")
|
|
4464
4515
|
lines.append("| %s | %s | %s | %s | %s | %s |"
|
|
4465
|
-
% (o["id"], o["type"],
|
|
4516
|
+
% tuple(_md_cell(c) for c in (o["id"], o["type"],
|
|
4517
|
+
o["evidence_class"], v,
|
|
4518
|
+
o["statement"], files)))
|
|
4466
4519
|
lines.append("")
|
|
4467
4520
|
return "\n".join(lines)
|
|
4468
4521
|
|
|
@@ -4771,15 +4824,18 @@ def cmd_promote(args):
|
|
|
4771
4824
|
if os.path.isfile(dpath):
|
|
4772
4825
|
with open(dpath, encoding="utf-8-sig", errors="replace") as fh:
|
|
4773
4826
|
existing = fh.read()
|
|
4774
|
-
add = [o for o in fixes if o["id"]
|
|
4827
|
+
add = [o for o in fixes if not _deferred_carries(existing, o["id"])]
|
|
4775
4828
|
if add:
|
|
4776
4829
|
with open(dpath, "a", encoding="utf-8", newline="\n") as fh:
|
|
4777
4830
|
if existing and not existing.endswith("\n"):
|
|
4778
4831
|
fh.write("\n")
|
|
4779
4832
|
for o in add:
|
|
4833
|
+
# a markdown checklist item ends at a newline just as a table row
|
|
4834
|
+
# does: a multi-line statement would split the work item in two and
|
|
4835
|
+
# leave the half _deferred_carries recognises without its text.
|
|
4780
4836
|
fh.write("- [ ] %s (curated `fix`): %s -- observed behavior the human "
|
|
4781
4837
|
"ruled a defect; NEVER canonical (ADR-013)\n"
|
|
4782
|
-
% (o["id"], o["statement"]))
|
|
4838
|
+
% (o["id"], _md_cell(o["statement"])))
|
|
4783
4839
|
new_fix = [o["id"] for o in add]
|
|
4784
4840
|
ledger["candidate_delta"] = {"repo": args.repo, "total": len(obs),
|
|
4785
4841
|
"curated": len(obs),
|
|
@@ -4817,16 +4873,20 @@ def cmd_fidelity(args):
|
|
|
4817
4873
|
node = _repo_node(ledger, args.repo)
|
|
4818
4874
|
repo_path = _scope_path(ledger, args.repo)
|
|
4819
4875
|
cfg = {}
|
|
4820
|
-
|
|
4876
|
+
# WHERE the config was looked for is part of the answer: a gate declared in a file the
|
|
4877
|
+
# command never opened is a gate that does not exist, and the old cwd-only resolution said
|
|
4878
|
+
# nothing about it (1.69.0 fresh review, resolved 1.98.0).
|
|
4879
|
+
cfg_path, cfg_found = _config_beside_ledger(args.config, args.ledger)
|
|
4880
|
+
if cfg_found:
|
|
4821
4881
|
try:
|
|
4822
|
-
with open(
|
|
4882
|
+
with open(cfg_path, encoding="utf-8-sig") as fh:
|
|
4823
4883
|
cfg = json.load(fh)
|
|
4824
4884
|
except (OSError, ValueError) as exc:
|
|
4825
4885
|
# a config that cannot be parsed cannot declare gates -- swallowing the error
|
|
4826
4886
|
# would DISABLE the INV-ADVISORY-01 refusal on a syntax slip (fresh-review
|
|
4827
4887
|
# HIGH, reproduced). Malformation is exit 2, never a silent degrade.
|
|
4828
4888
|
print("[qa_ledger] fidelity: %s unreadable: %s -- refusing to guess what it "
|
|
4829
|
-
"declares." % (
|
|
4889
|
+
"declares." % (cfg_path, exc), file=sys.stderr)
|
|
4830
4890
|
sys.exit(2)
|
|
4831
4891
|
declared = ((cfg.get("defaults") or {}).get("fidelity") or {}).get("gate") or []
|
|
4832
4892
|
for dim in (ledger.get("config", {}).get("defaults", {}).get("fidelity")
|
|
@@ -4979,6 +5039,7 @@ def cmd_fidelity(args):
|
|
|
4979
5039
|
dims["semantic"] = _fid_dim(None, "not wired: an LLM-judged comparison enters as "
|
|
4980
5040
|
"advisory only and can NEVER gate (INV-ADVISORY-01)")
|
|
4981
5041
|
out = {"repo": args.repo,
|
|
5042
|
+
"config": cfg_path if cfg_found else None,
|
|
4982
5043
|
**({"path": bound} if bound else {}),
|
|
4983
5044
|
"dimensions": {k: dict(dims[k], **{"class": FIDELITY_DIMENSIONS[k]})
|
|
4984
5045
|
for k in ("traceability", "behavior", "contracts",
|
|
@@ -4990,6 +5051,11 @@ def cmd_fidelity(args):
|
|
|
4990
5051
|
else:
|
|
4991
5052
|
print("FIDELITY %s%s (vector -- no blend; each number stands on its own evidence):"
|
|
4992
5053
|
% (args.repo, " [bounded to %s]" % bound if bound else ""))
|
|
5054
|
+
if cfg_found:
|
|
5055
|
+
print(" config %s" % cfg_path)
|
|
5056
|
+
else:
|
|
5057
|
+
print(" config none (looked for %r in the cwd and beside the ledger) "
|
|
5058
|
+
"-- no fidelity gate declared from a file" % args.config)
|
|
4993
5059
|
for k, d in out["dimensions"].items():
|
|
4994
5060
|
val = "UNMEASURED" if d["value"] is None else "%.2f" % d["value"]
|
|
4995
5061
|
print(" %-17s %-10s [%s] %s" % (k, val, d["class"], d["provenance"]))
|
|
@@ -5211,8 +5277,8 @@ def _render_ir_md(graph):
|
|
|
5211
5277
|
"|----|------|-----------|--------|"]
|
|
5212
5278
|
for nd in graph.get("nodes") or []:
|
|
5213
5279
|
src = "%s:%s" % (nd["source"]["file"], nd["source"]["line"])
|
|
5214
|
-
|
|
5215
|
-
|
|
5280
|
+
lines.append("| %s | %s | %s | %s |"
|
|
5281
|
+
% (nd["id"], nd["type"], _md_cell(nd.get("statement") or ""), src))
|
|
5216
5282
|
lines += ["", "## Edges", "", "| from | type | to |", "|------|------|----|"]
|
|
5217
5283
|
for e in graph.get("edges") or []:
|
|
5218
5284
|
lines.append("| %s | %s | %s |" % (e["from"], e["type"], e["to"]))
|
|
@@ -5221,8 +5287,11 @@ def _render_ir_md(graph):
|
|
|
5221
5287
|
"| text | source | reason |", "|------|--------|--------|"]
|
|
5222
5288
|
for u in graph["untyped"]:
|
|
5223
5289
|
src = "%s:%s" % (u["source"]["file"], u["source"]["line"])
|
|
5224
|
-
|
|
5225
|
-
|
|
5290
|
+
# truncate AFTER the cell is flattened: slicing raw text can cut mid-newline
|
|
5291
|
+
# and leave the break inside the 80 characters that reach the row.
|
|
5292
|
+
txt = _md_cell(u.get("text") or "")[:80]
|
|
5293
|
+
lines.append("| %s | %s | %s |"
|
|
5294
|
+
% (txt, src, _md_cell(u.get("reason", ""))))
|
|
5226
5295
|
lines.append("")
|
|
5227
5296
|
return "\n".join(lines)
|
|
5228
5297
|
|
|
@@ -11189,18 +11258,33 @@ def _lc_valid_date(s):
|
|
|
11189
11258
|
return False
|
|
11190
11259
|
_LC_KEY = re.compile(r"^lifecycle\s*:\s*(.*)$")
|
|
11191
11260
|
_LC_GOLIVE_FM = re.compile(r"^go[-_ ]?live\s*:\s*(.+?)\s*$", re.I)
|
|
11192
|
-
|
|
11261
|
+
# the body form. A list marker or a blockquote prefix in front of the bold label is still the
|
|
11262
|
+
# same declaration -- a SPEC that writes it as a bullet used to read as "no go-live declared"
|
|
11263
|
+
# (1.98.1, field report). The prefix is markdown-CORRECT, not merely permissive: at most three
|
|
11264
|
+
# leading spaces and never a tab, because four spaces or a tab opens an INDENTED CODE BLOCK --
|
|
11265
|
+
# an example, not a declaration -- and a list marker must be FOLLOWED by whitespace, so the
|
|
11266
|
+
# glued `-**Go-live:**` is not a list item. Fenced blocks are skipped by the caller for the
|
|
11267
|
+
# same reason. The trailing \b keeps the date a WHOLE token: it must be followed by a
|
|
11268
|
+
# non-digit or the end of the line, so a longer number can never be truncated into a date,
|
|
11269
|
+
# while ordinary prose after it ("(delivery 1). ...") is allowed.
|
|
11270
|
+
_LC_GOLIVE_LINE = re.compile(r"^ {0,3}(?:> ?)*(?:(?:[-*+]|\d+[.)])\s+)?"
|
|
11271
|
+
r"\*\*\s*go[-_ ]?live\s*:?\s*\*\*\s*:?\s*"
|
|
11193
11272
|
r"(\d{4}-\d{2}-\d{2})\b", re.I)
|
|
11194
11273
|
_LC_FIELDS = ("component", "version", "eol", "source", "checked")
|
|
11195
11274
|
|
|
11196
11275
|
|
|
11197
11276
|
def _lc_frontmatter(lines):
|
|
11198
11277
|
"""The lines INSIDE a leading `---` frontmatter block, or None when there is none.
|
|
11278
|
+
Leading blank lines and a BOM are skipped before the fence is looked for: a file whose
|
|
11279
|
+
text reached us with anything glued in front still opens its block at the top (1.98.1).
|
|
11199
11280
|
An unterminated fence is not data: a half-written block reads as absent."""
|
|
11200
|
-
|
|
11281
|
+
i = 0
|
|
11282
|
+
while i < len(lines) and not lines[i].lstrip("\ufeff").strip():
|
|
11283
|
+
i += 1
|
|
11284
|
+
if i >= len(lines) or lines[i].lstrip("\ufeff").strip() != "---":
|
|
11201
11285
|
return None
|
|
11202
11286
|
body = []
|
|
11203
|
-
for ln in lines[1:]:
|
|
11287
|
+
for ln in lines[i + 1:]:
|
|
11204
11288
|
if ln.strip() == "---":
|
|
11205
11289
|
return body
|
|
11206
11290
|
body.append(ln)
|
|
@@ -11252,7 +11336,8 @@ def _lc_parse(lines):
|
|
|
11252
11336
|
|
|
11253
11337
|
def _lc_go_live(text):
|
|
11254
11338
|
"""The declared go-live of a SPEC: frontmatter `go_live: YYYY-MM-DD`, or a
|
|
11255
|
-
`**Go-live:** YYYY-MM-DD` line anywhere in the body
|
|
11339
|
+
`**Go-live:** YYYY-MM-DD` line anywhere in the body OUTSIDE a fenced block.
|
|
11340
|
+
None = not declared."""
|
|
11256
11341
|
lines = [ln.rstrip("\r") for ln in (text or "").split("\n")]
|
|
11257
11342
|
fm = _lc_frontmatter(lines)
|
|
11258
11343
|
if fm:
|
|
@@ -11264,7 +11349,14 @@ def _lc_go_live(text):
|
|
|
11264
11349
|
v = m.group(1).strip().strip("\x27\x22")
|
|
11265
11350
|
if _lc_valid_date(v):
|
|
11266
11351
|
return v
|
|
11352
|
+
in_fence = False
|
|
11267
11353
|
for ln in lines:
|
|
11354
|
+
st = ln.strip()
|
|
11355
|
+
if st.startswith("```") or st.startswith("~~~"):
|
|
11356
|
+
in_fence = not in_fence
|
|
11357
|
+
continue
|
|
11358
|
+
if in_fence: # a SPEC that QUOTES the form is showing it, not declaring a date --
|
|
11359
|
+
continue # and the quoted line comes FIRST (the fence idiom of _spec_check_text)
|
|
11268
11360
|
m = _LC_GOLIVE_LINE.match(ln)
|
|
11269
11361
|
if m and _lc_valid_date(m.group(1)):
|
|
11270
11362
|
return m.group(1)
|
|
@@ -11376,7 +11468,12 @@ def _lifecycle_for(root, adr_dir=None, spec_text=None, fallback=True):
|
|
|
11376
11468
|
if os.path.isfile(sp):
|
|
11377
11469
|
try:
|
|
11378
11470
|
with open(sp, "r", encoding="utf-8", errors="replace") as fh:
|
|
11379
|
-
|
|
11471
|
+
body = fh.read()
|
|
11472
|
+
# join only when there is something to join TO. Gluing a newline in front of
|
|
11473
|
+
# the file pushed its `---` off line 0, so the frontmatter went invisible and
|
|
11474
|
+
# `readiness` read "no go-live declared" on a SPEC that `spec-check` measured
|
|
11475
|
+
# (1.98.1, field report).
|
|
11476
|
+
spec_text = (spec_text + "\n" + body) if spec_text else body
|
|
11380
11477
|
except OSError:
|
|
11381
11478
|
pass
|
|
11382
11479
|
return _lifecycle_report(adr_dir or os.path.join(root, "docs", "adr"), spec_text)
|
|
@@ -12500,7 +12597,9 @@ def build_parser():
|
|
|
12500
12597
|
pfv.add_argument("--ledger", default="QA-LEDGER.json")
|
|
12501
12598
|
pfv.add_argument("--repo", required=True)
|
|
12502
12599
|
pfv.add_argument("--config", default="uscha.config.json",
|
|
12503
|
-
help="checked for defaults.fidelity.gate -- advisory there is a
|
|
12600
|
+
help="checked for defaults.fidelity.gate -- advisory there is a "
|
|
12601
|
+
"refusal. A relative name is resolved against the cwd first, "
|
|
12602
|
+
"then beside --ledger; the path actually read is reported")
|
|
12504
12603
|
pfv.add_argument("--ir", action="store_true",
|
|
12505
12604
|
help="answer curation_closure as a path query over the IR graph "
|
|
12506
12605
|
"(ADR-015); reproduces v0 from the derived index")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.99.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, 53 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:** v1.
|
|
3
|
+
**Kit version:** v1.99.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`,
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 1.
|
|
1
|
+
uscha-kit 1.99.0
|
|
@@ -4140,6 +4140,47 @@ CANDIDATE_DELTA_FILE = "CANDIDATE-DELTA.json" # under discovery/, machine-can
|
|
|
4140
4140
|
CANDIDATE_DELTA_TWIN = "CANDIDATE-DELTA.md" # rendered view, regenerated, never a source
|
|
4141
4141
|
CANONICAL_FILE = "CANONICAL.json" # under discovery/: the promoted package
|
|
4142
4142
|
ISSUES_DEFERRED_FILE = "ISSUES-DEFERRED.md"
|
|
4143
|
+
# The shape of the work item `promote` writes for a `fix` verdict, and the shape it recognises
|
|
4144
|
+
# again on the next run. The dedupe used to be `o["id"] not in existing` -- a RAW SUBSTRING over
|
|
4145
|
+
# the whole file, so it suppressed the work item for any id merely MENTIONED in prose there and,
|
|
4146
|
+
# because the ids are a hash prefix, for any id that is a prefix of an already-written one
|
|
4147
|
+
# (1.69.0 fresh review, LOW, resolved 1.98.0). The question is not "does this text occur"; it is
|
|
4148
|
+
# "does this file already carry the WORK ITEM for this observation", and only the line shape
|
|
4149
|
+
# answers it. The word boundaries are what separate OBS-1 from OBS-10, and the `.*` between
|
|
4150
|
+
# the checkbox and the id is what lets a HUMAN reword the item -- prefix it with a date, a
|
|
4151
|
+
# severity, an owner -- without `promote` appending a second copy of it on the next run.
|
|
4152
|
+
_DEFERRED_ITEM_LINE = r"(?m)^[ \t]*[-*][ \t]*\[[ xX]\].*\b%s\b"
|
|
4153
|
+
|
|
4154
|
+
|
|
4155
|
+
def _deferred_carries(existing, oid):
|
|
4156
|
+
"""True when ISSUES-DEFERRED.md already holds the `- [ ] <oid>` work item for this OBS."""
|
|
4157
|
+
return re.search(_DEFERRED_ITEM_LINE % re.escape(oid), existing) is not None
|
|
4158
|
+
|
|
4159
|
+
|
|
4160
|
+
def _config_beside_ledger(config, ledger_path):
|
|
4161
|
+
"""Where to LOOK for a config whose default is a bare relative name.
|
|
4162
|
+
|
|
4163
|
+
`--config` defaulted to the literal `uscha.config.json`, which `os.path.isfile` resolves
|
|
4164
|
+
against the CWD. Run `fidelity --ledger /repo/QA-LEDGER.json` from anywhere but /repo and
|
|
4165
|
+
the config beside the ledger was never opened -- so `defaults.fidelity.gate` was silently
|
|
4166
|
+
not declared, and the INV-ADVISORY-01 refusal that reads it never fired. An UNNAMED absence
|
|
4167
|
+
(1.69.0 fresh review, LOW, resolved 1.98.0): the command printed a full vector and exit 0,
|
|
4168
|
+
exactly as it does when there genuinely is no config.
|
|
4169
|
+
|
|
4170
|
+
The cwd still WINS when it holds the file -- an explicit `--config` next to you is what you
|
|
4171
|
+
meant, and no existing invocation changes behaviour. Only when it does not is the ledger's
|
|
4172
|
+
own directory tried. Returns (path, found): callers report `path` so the answer to "which
|
|
4173
|
+
file did you read" is in the output rather than in the reader's head.
|
|
4174
|
+
"""
|
|
4175
|
+
if os.path.isfile(config):
|
|
4176
|
+
return config, True
|
|
4177
|
+
if not os.path.isabs(config):
|
|
4178
|
+
beside = os.path.join(os.path.dirname(os.path.abspath(ledger_path)), config)
|
|
4179
|
+
if os.path.isfile(beside):
|
|
4180
|
+
return beside, True
|
|
4181
|
+
return config, False
|
|
4182
|
+
|
|
4183
|
+
|
|
4143
4184
|
OBS_TYPES = ("behavior", "invariant", "contract", "config", "dependency", "decision_trace")
|
|
4144
4185
|
EVIDENCE_CLASSES = ("measured", "static", "narrated")
|
|
4145
4186
|
# ADR-014 / INV-ADVISORY-01: dimensions an LLM judges can only advise. The QUARANTINE is an
|
|
@@ -4445,6 +4486,17 @@ def _match_canonical(statement, canon_ids):
|
|
|
4445
4486
|
return None
|
|
4446
4487
|
|
|
4447
4488
|
|
|
4489
|
+
def _md_cell(text):
|
|
4490
|
+
"""One markdown TABLE cell. A `|` is escaped, and any CR/LF inside the value collapses to
|
|
4491
|
+
a space -- a markdown row ENDS at a newline, so a statement or a provenance ref carrying
|
|
4492
|
+
one used to split its observation across two rows and corrupt every column after it
|
|
4493
|
+
(1.69.0 fresh review, LOW, resolved 1.98.0). The JSON and the OBS id always survived; only
|
|
4494
|
+
this rendered view broke, and it is the artifact the human curates from. A space rather
|
|
4495
|
+
than `<br>`: every other cell is plain text, and a lone HTML tag in one column would be
|
|
4496
|
+
the only markup in the table."""
|
|
4497
|
+
return re.sub(r"[\r\n]+", " ", "%s" % text).replace("|", "\\|")
|
|
4498
|
+
|
|
4499
|
+
|
|
4448
4500
|
def _render_delta_md(delta, verdicts):
|
|
4449
4501
|
lines = ["<!-- %s -->" % _DELTA_BANNER, "",
|
|
4450
4502
|
"# CANDIDATE-DELTA (rendered view)", ""]
|
|
@@ -4460,9 +4512,10 @@ def _render_delta_md(delta, verdicts):
|
|
|
4460
4512
|
for o in delta["observations"]:
|
|
4461
4513
|
v = verdicts.get(o["id"], "(uncurated)")
|
|
4462
4514
|
files = ", ".join(o["provenance"].get("files") or []) or "-"
|
|
4463
|
-
stmt = o["statement"].replace("|", "\\|")
|
|
4464
4515
|
lines.append("| %s | %s | %s | %s | %s | %s |"
|
|
4465
|
-
% (o["id"], o["type"],
|
|
4516
|
+
% tuple(_md_cell(c) for c in (o["id"], o["type"],
|
|
4517
|
+
o["evidence_class"], v,
|
|
4518
|
+
o["statement"], files)))
|
|
4466
4519
|
lines.append("")
|
|
4467
4520
|
return "\n".join(lines)
|
|
4468
4521
|
|
|
@@ -4771,15 +4824,18 @@ def cmd_promote(args):
|
|
|
4771
4824
|
if os.path.isfile(dpath):
|
|
4772
4825
|
with open(dpath, encoding="utf-8-sig", errors="replace") as fh:
|
|
4773
4826
|
existing = fh.read()
|
|
4774
|
-
add = [o for o in fixes if o["id"]
|
|
4827
|
+
add = [o for o in fixes if not _deferred_carries(existing, o["id"])]
|
|
4775
4828
|
if add:
|
|
4776
4829
|
with open(dpath, "a", encoding="utf-8", newline="\n") as fh:
|
|
4777
4830
|
if existing and not existing.endswith("\n"):
|
|
4778
4831
|
fh.write("\n")
|
|
4779
4832
|
for o in add:
|
|
4833
|
+
# a markdown checklist item ends at a newline just as a table row
|
|
4834
|
+
# does: a multi-line statement would split the work item in two and
|
|
4835
|
+
# leave the half _deferred_carries recognises without its text.
|
|
4780
4836
|
fh.write("- [ ] %s (curated `fix`): %s -- observed behavior the human "
|
|
4781
4837
|
"ruled a defect; NEVER canonical (ADR-013)\n"
|
|
4782
|
-
% (o["id"], o["statement"]))
|
|
4838
|
+
% (o["id"], _md_cell(o["statement"])))
|
|
4783
4839
|
new_fix = [o["id"] for o in add]
|
|
4784
4840
|
ledger["candidate_delta"] = {"repo": args.repo, "total": len(obs),
|
|
4785
4841
|
"curated": len(obs),
|
|
@@ -4817,16 +4873,20 @@ def cmd_fidelity(args):
|
|
|
4817
4873
|
node = _repo_node(ledger, args.repo)
|
|
4818
4874
|
repo_path = _scope_path(ledger, args.repo)
|
|
4819
4875
|
cfg = {}
|
|
4820
|
-
|
|
4876
|
+
# WHERE the config was looked for is part of the answer: a gate declared in a file the
|
|
4877
|
+
# command never opened is a gate that does not exist, and the old cwd-only resolution said
|
|
4878
|
+
# nothing about it (1.69.0 fresh review, resolved 1.98.0).
|
|
4879
|
+
cfg_path, cfg_found = _config_beside_ledger(args.config, args.ledger)
|
|
4880
|
+
if cfg_found:
|
|
4821
4881
|
try:
|
|
4822
|
-
with open(
|
|
4882
|
+
with open(cfg_path, encoding="utf-8-sig") as fh:
|
|
4823
4883
|
cfg = json.load(fh)
|
|
4824
4884
|
except (OSError, ValueError) as exc:
|
|
4825
4885
|
# a config that cannot be parsed cannot declare gates -- swallowing the error
|
|
4826
4886
|
# would DISABLE the INV-ADVISORY-01 refusal on a syntax slip (fresh-review
|
|
4827
4887
|
# HIGH, reproduced). Malformation is exit 2, never a silent degrade.
|
|
4828
4888
|
print("[qa_ledger] fidelity: %s unreadable: %s -- refusing to guess what it "
|
|
4829
|
-
"declares." % (
|
|
4889
|
+
"declares." % (cfg_path, exc), file=sys.stderr)
|
|
4830
4890
|
sys.exit(2)
|
|
4831
4891
|
declared = ((cfg.get("defaults") or {}).get("fidelity") or {}).get("gate") or []
|
|
4832
4892
|
for dim in (ledger.get("config", {}).get("defaults", {}).get("fidelity")
|
|
@@ -4979,6 +5039,7 @@ def cmd_fidelity(args):
|
|
|
4979
5039
|
dims["semantic"] = _fid_dim(None, "not wired: an LLM-judged comparison enters as "
|
|
4980
5040
|
"advisory only and can NEVER gate (INV-ADVISORY-01)")
|
|
4981
5041
|
out = {"repo": args.repo,
|
|
5042
|
+
"config": cfg_path if cfg_found else None,
|
|
4982
5043
|
**({"path": bound} if bound else {}),
|
|
4983
5044
|
"dimensions": {k: dict(dims[k], **{"class": FIDELITY_DIMENSIONS[k]})
|
|
4984
5045
|
for k in ("traceability", "behavior", "contracts",
|
|
@@ -4990,6 +5051,11 @@ def cmd_fidelity(args):
|
|
|
4990
5051
|
else:
|
|
4991
5052
|
print("FIDELITY %s%s (vector -- no blend; each number stands on its own evidence):"
|
|
4992
5053
|
% (args.repo, " [bounded to %s]" % bound if bound else ""))
|
|
5054
|
+
if cfg_found:
|
|
5055
|
+
print(" config %s" % cfg_path)
|
|
5056
|
+
else:
|
|
5057
|
+
print(" config none (looked for %r in the cwd and beside the ledger) "
|
|
5058
|
+
"-- no fidelity gate declared from a file" % args.config)
|
|
4993
5059
|
for k, d in out["dimensions"].items():
|
|
4994
5060
|
val = "UNMEASURED" if d["value"] is None else "%.2f" % d["value"]
|
|
4995
5061
|
print(" %-17s %-10s [%s] %s" % (k, val, d["class"], d["provenance"]))
|
|
@@ -5211,8 +5277,8 @@ def _render_ir_md(graph):
|
|
|
5211
5277
|
"|----|------|-----------|--------|"]
|
|
5212
5278
|
for nd in graph.get("nodes") or []:
|
|
5213
5279
|
src = "%s:%s" % (nd["source"]["file"], nd["source"]["line"])
|
|
5214
|
-
|
|
5215
|
-
|
|
5280
|
+
lines.append("| %s | %s | %s | %s |"
|
|
5281
|
+
% (nd["id"], nd["type"], _md_cell(nd.get("statement") or ""), src))
|
|
5216
5282
|
lines += ["", "## Edges", "", "| from | type | to |", "|------|------|----|"]
|
|
5217
5283
|
for e in graph.get("edges") or []:
|
|
5218
5284
|
lines.append("| %s | %s | %s |" % (e["from"], e["type"], e["to"]))
|
|
@@ -5221,8 +5287,11 @@ def _render_ir_md(graph):
|
|
|
5221
5287
|
"| text | source | reason |", "|------|--------|--------|"]
|
|
5222
5288
|
for u in graph["untyped"]:
|
|
5223
5289
|
src = "%s:%s" % (u["source"]["file"], u["source"]["line"])
|
|
5224
|
-
|
|
5225
|
-
|
|
5290
|
+
# truncate AFTER the cell is flattened: slicing raw text can cut mid-newline
|
|
5291
|
+
# and leave the break inside the 80 characters that reach the row.
|
|
5292
|
+
txt = _md_cell(u.get("text") or "")[:80]
|
|
5293
|
+
lines.append("| %s | %s | %s |"
|
|
5294
|
+
% (txt, src, _md_cell(u.get("reason", ""))))
|
|
5226
5295
|
lines.append("")
|
|
5227
5296
|
return "\n".join(lines)
|
|
5228
5297
|
|
|
@@ -11189,18 +11258,33 @@ def _lc_valid_date(s):
|
|
|
11189
11258
|
return False
|
|
11190
11259
|
_LC_KEY = re.compile(r"^lifecycle\s*:\s*(.*)$")
|
|
11191
11260
|
_LC_GOLIVE_FM = re.compile(r"^go[-_ ]?live\s*:\s*(.+?)\s*$", re.I)
|
|
11192
|
-
|
|
11261
|
+
# the body form. A list marker or a blockquote prefix in front of the bold label is still the
|
|
11262
|
+
# same declaration -- a SPEC that writes it as a bullet used to read as "no go-live declared"
|
|
11263
|
+
# (1.98.1, field report). The prefix is markdown-CORRECT, not merely permissive: at most three
|
|
11264
|
+
# leading spaces and never a tab, because four spaces or a tab opens an INDENTED CODE BLOCK --
|
|
11265
|
+
# an example, not a declaration -- and a list marker must be FOLLOWED by whitespace, so the
|
|
11266
|
+
# glued `-**Go-live:**` is not a list item. Fenced blocks are skipped by the caller for the
|
|
11267
|
+
# same reason. The trailing \b keeps the date a WHOLE token: it must be followed by a
|
|
11268
|
+
# non-digit or the end of the line, so a longer number can never be truncated into a date,
|
|
11269
|
+
# while ordinary prose after it ("(delivery 1). ...") is allowed.
|
|
11270
|
+
_LC_GOLIVE_LINE = re.compile(r"^ {0,3}(?:> ?)*(?:(?:[-*+]|\d+[.)])\s+)?"
|
|
11271
|
+
r"\*\*\s*go[-_ ]?live\s*:?\s*\*\*\s*:?\s*"
|
|
11193
11272
|
r"(\d{4}-\d{2}-\d{2})\b", re.I)
|
|
11194
11273
|
_LC_FIELDS = ("component", "version", "eol", "source", "checked")
|
|
11195
11274
|
|
|
11196
11275
|
|
|
11197
11276
|
def _lc_frontmatter(lines):
|
|
11198
11277
|
"""The lines INSIDE a leading `---` frontmatter block, or None when there is none.
|
|
11278
|
+
Leading blank lines and a BOM are skipped before the fence is looked for: a file whose
|
|
11279
|
+
text reached us with anything glued in front still opens its block at the top (1.98.1).
|
|
11199
11280
|
An unterminated fence is not data: a half-written block reads as absent."""
|
|
11200
|
-
|
|
11281
|
+
i = 0
|
|
11282
|
+
while i < len(lines) and not lines[i].lstrip("\ufeff").strip():
|
|
11283
|
+
i += 1
|
|
11284
|
+
if i >= len(lines) or lines[i].lstrip("\ufeff").strip() != "---":
|
|
11201
11285
|
return None
|
|
11202
11286
|
body = []
|
|
11203
|
-
for ln in lines[1:]:
|
|
11287
|
+
for ln in lines[i + 1:]:
|
|
11204
11288
|
if ln.strip() == "---":
|
|
11205
11289
|
return body
|
|
11206
11290
|
body.append(ln)
|
|
@@ -11252,7 +11336,8 @@ def _lc_parse(lines):
|
|
|
11252
11336
|
|
|
11253
11337
|
def _lc_go_live(text):
|
|
11254
11338
|
"""The declared go-live of a SPEC: frontmatter `go_live: YYYY-MM-DD`, or a
|
|
11255
|
-
`**Go-live:** YYYY-MM-DD` line anywhere in the body
|
|
11339
|
+
`**Go-live:** YYYY-MM-DD` line anywhere in the body OUTSIDE a fenced block.
|
|
11340
|
+
None = not declared."""
|
|
11256
11341
|
lines = [ln.rstrip("\r") for ln in (text or "").split("\n")]
|
|
11257
11342
|
fm = _lc_frontmatter(lines)
|
|
11258
11343
|
if fm:
|
|
@@ -11264,7 +11349,14 @@ def _lc_go_live(text):
|
|
|
11264
11349
|
v = m.group(1).strip().strip("\x27\x22")
|
|
11265
11350
|
if _lc_valid_date(v):
|
|
11266
11351
|
return v
|
|
11352
|
+
in_fence = False
|
|
11267
11353
|
for ln in lines:
|
|
11354
|
+
st = ln.strip()
|
|
11355
|
+
if st.startswith("```") or st.startswith("~~~"):
|
|
11356
|
+
in_fence = not in_fence
|
|
11357
|
+
continue
|
|
11358
|
+
if in_fence: # a SPEC that QUOTES the form is showing it, not declaring a date --
|
|
11359
|
+
continue # and the quoted line comes FIRST (the fence idiom of _spec_check_text)
|
|
11268
11360
|
m = _LC_GOLIVE_LINE.match(ln)
|
|
11269
11361
|
if m and _lc_valid_date(m.group(1)):
|
|
11270
11362
|
return m.group(1)
|
|
@@ -11376,7 +11468,12 @@ def _lifecycle_for(root, adr_dir=None, spec_text=None, fallback=True):
|
|
|
11376
11468
|
if os.path.isfile(sp):
|
|
11377
11469
|
try:
|
|
11378
11470
|
with open(sp, "r", encoding="utf-8", errors="replace") as fh:
|
|
11379
|
-
|
|
11471
|
+
body = fh.read()
|
|
11472
|
+
# join only when there is something to join TO. Gluing a newline in front of
|
|
11473
|
+
# the file pushed its `---` off line 0, so the frontmatter went invisible and
|
|
11474
|
+
# `readiness` read "no go-live declared" on a SPEC that `spec-check` measured
|
|
11475
|
+
# (1.98.1, field report).
|
|
11476
|
+
spec_text = (spec_text + "\n" + body) if spec_text else body
|
|
11380
11477
|
except OSError:
|
|
11381
11478
|
pass
|
|
11382
11479
|
return _lifecycle_report(adr_dir or os.path.join(root, "docs", "adr"), spec_text)
|
|
@@ -12500,7 +12597,9 @@ def build_parser():
|
|
|
12500
12597
|
pfv.add_argument("--ledger", default="QA-LEDGER.json")
|
|
12501
12598
|
pfv.add_argument("--repo", required=True)
|
|
12502
12599
|
pfv.add_argument("--config", default="uscha.config.json",
|
|
12503
|
-
help="checked for defaults.fidelity.gate -- advisory there is a
|
|
12600
|
+
help="checked for defaults.fidelity.gate -- advisory there is a "
|
|
12601
|
+
"refusal. A relative name is resolved against the cwd first, "
|
|
12602
|
+
"then beside --ledger; the path actually read is reported")
|
|
12504
12603
|
pfv.add_argument("--ir", action="store_true",
|
|
12505
12604
|
help="answer curation_closure as a path query over the IR graph "
|
|
12506
12605
|
"(ADR-015); reproduces v0 from the derived index")
|
|
@@ -121,7 +121,7 @@ enforcing the record is the engine's job. It is never resolved by "working aroun
|
|
|
121
121
|
- [ ] **Runs without the human typing anything** — a script/agent autocompletes it; no routine forms
|
|
122
122
|
- [ ] **Speaks only when it matters** — a failure, or a high-risk profile; if it always speaks, it is silenced by default
|
|
123
123
|
- [ ] **Collapses into `readiness`** — one number + one line, not another screen (`--verbose` opens the detail)
|
|
124
|
-
- [ ] **A trivial change skips it** — gated by risk profile <!-- principle: profiles A–E NOT
|
|
124
|
+
- [ ] **A trivial change skips it** — gated by risk profile <!-- principle: profiles A–E are NOT mechanized in the engine, by design -- the paragraph above says why: this is design discipline and review judgment, not a check -->
|
|
125
125
|
|
|
126
126
|
## How it is enforced
|
|
127
127
|
|