@andresmassello/uscha 1.69.0 → 1.71.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 +1 -1
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +90 -20
- 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/reports/junit/.delta-cases.json +1 -1
- package/uscha-kit/reports/junit/.fidelity-cases.json +1 -1
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +90 -20
- 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.71.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
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.71.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",
|
|
@@ -58,6 +58,7 @@ import hashlib
|
|
|
58
58
|
import json
|
|
59
59
|
import math
|
|
60
60
|
import os
|
|
61
|
+
import posixpath
|
|
61
62
|
import re
|
|
62
63
|
import shutil
|
|
63
64
|
import subprocess
|
|
@@ -3913,6 +3914,14 @@ _DELTA_BANNER = ("GENERATED by qa_ledger.py discover (ADR-013). Rendered view of
|
|
|
3913
3914
|
+ CANDIDATE_DELTA_FILE + " -- hand edits are overwritten on regeneration.")
|
|
3914
3915
|
|
|
3915
3916
|
|
|
3917
|
+
def _delta_seal(observations, repo, path):
|
|
3918
|
+
"""The seal covers everything semantically load-bearing that the content-addressed OBS
|
|
3919
|
+
ids do NOT: the full observation set, the repo, and the bound. `path` changes what the
|
|
3920
|
+
delta MEANS (a partial discovery), so it must be sealed -- a hand edit of any of these is
|
|
3921
|
+
a named malformation (fresh-review MEDIUM)."""
|
|
3922
|
+
return _integrity_hash({"observations": observations, "repo": repo, "path": path})
|
|
3923
|
+
|
|
3924
|
+
|
|
3916
3925
|
def _obs_id(otype, statement, primary_prov):
|
|
3917
3926
|
"""Content-addressed: OBS-sha256(type + LF + normalized statement + LF + primary
|
|
3918
3927
|
provenance)[:12]. Normalization = lowercase + whitespace collapse. Re-running discovery
|
|
@@ -3998,7 +4007,11 @@ def _extract_static_py(repo_path, tracked):
|
|
|
3998
4007
|
return obs, unsupported
|
|
3999
4008
|
|
|
4000
4009
|
|
|
4001
|
-
def
|
|
4010
|
+
def _under_bound(rel, bound):
|
|
4011
|
+
return bound is None or rel == bound or rel.startswith(bound + "/")
|
|
4012
|
+
|
|
4013
|
+
|
|
4014
|
+
def _golden_backed_obs(ledger, repo, repo_path, bound=None):
|
|
4002
4015
|
"""Measured observations: one per approved golden fixture, backed by the LATEST ingested
|
|
4003
4016
|
golden-diff gate record (AC-DD-03). Only source: real, ledger-ingested execution. No
|
|
4004
4017
|
ingested run -> no measured OBS; a fixture on disk that nothing executed is not evidence."""
|
|
@@ -4016,6 +4029,8 @@ def _golden_backed_obs(ledger, repo, repo_path):
|
|
|
4016
4029
|
if ".approved." not in f:
|
|
4017
4030
|
continue
|
|
4018
4031
|
rel = os.path.relpath(os.path.join(root, f), repo_path).replace(os.sep, "/")
|
|
4032
|
+
if not _under_bound(rel, bound):
|
|
4033
|
+
continue
|
|
4019
4034
|
stmt = "behavior frozen by golden fixture %s matches the approved baseline" % rel
|
|
4020
4035
|
obs.append({"id": _obs_id("behavior", stmt, rel), "type": "behavior",
|
|
4021
4036
|
"statement": stmt, "evidence_class": "measured",
|
|
@@ -4108,9 +4123,16 @@ def _match_canonical(statement, canon_ids):
|
|
|
4108
4123
|
|
|
4109
4124
|
def _render_delta_md(delta, verdicts):
|
|
4110
4125
|
lines = ["<!-- %s -->" % _DELTA_BANNER, "",
|
|
4111
|
-
"# CANDIDATE-DELTA (rendered view)", ""
|
|
4112
|
-
|
|
4113
|
-
|
|
4126
|
+
"# CANDIDATE-DELTA (rendered view)", ""]
|
|
4127
|
+
if delta.get("path"):
|
|
4128
|
+
# the bound is the artifact the HUMAN curates from -- a partial discovery must not
|
|
4129
|
+
# read as a complete one, or the shrunk INV-CURATION-01 surface is undisclosed
|
|
4130
|
+
# (fresh-review HIGH). The JSON recorded it; the human-facing view must too.
|
|
4131
|
+
lines += ["> **BOUNDED discovery** — mechanical scans were restricted to "
|
|
4132
|
+
"`%s`. Files outside this path were NOT scanned; this delta is PARTIAL "
|
|
4133
|
+
"by construction." % delta["path"], ""]
|
|
4134
|
+
lines += ["| id | type | class | verdict | statement | provenance |",
|
|
4135
|
+
"|----|------|-------|---------|-----------|------------|"]
|
|
4114
4136
|
for o in delta["observations"]:
|
|
4115
4137
|
v = verdicts.get(o["id"], "(uncurated)")
|
|
4116
4138
|
files = ", ".join(o["provenance"].get("files") or []) or "-"
|
|
@@ -4177,10 +4199,10 @@ def _load_delta(repo_path):
|
|
|
4177
4199
|
# launders narrated inference into measured evidence (fresh-review MEDIUM, reproduced)
|
|
4178
4200
|
if not errors:
|
|
4179
4201
|
seal = delta.get("_integrity")
|
|
4180
|
-
want =
|
|
4202
|
+
want = _delta_seal(obs, delta.get("repo"), delta.get("path"))
|
|
4181
4203
|
if seal != want:
|
|
4182
|
-
errors.append("integrity seal %s does not match the
|
|
4183
|
-
"
|
|
4204
|
+
errors.append("integrity seal %s does not match the delta content -- observations, "
|
|
4205
|
+
"repo or path was hand-edited (regenerate via `discover`)"
|
|
4184
4206
|
% ("missing" if seal is None else repr(seal)))
|
|
4185
4207
|
return delta, errors
|
|
4186
4208
|
|
|
@@ -4224,8 +4246,36 @@ def cmd_discover(args):
|
|
|
4224
4246
|
"from tracked files and cannot proceed without it." % repo_path,
|
|
4225
4247
|
file=sys.stderr)
|
|
4226
4248
|
sys.exit(2)
|
|
4249
|
+
bound = None
|
|
4250
|
+
if args.path is not None:
|
|
4251
|
+
# the bound restricts the MECHANICAL scans (static, measured); narrated input stays
|
|
4252
|
+
# the skill's to scope. Field-found before the first run (AC-DD-07): "real, bounded"
|
|
4253
|
+
# is unimplementable without it.
|
|
4254
|
+
raw = args.path.replace("\\", "/").strip()
|
|
4255
|
+
if not raw:
|
|
4256
|
+
# an empty --path is the silent-degrade trap (a wrapper passing an unset var):
|
|
4257
|
+
# falsy would mean "no bound" and quietly scan the whole repo (fresh-review MED)
|
|
4258
|
+
print("[qa_ledger] discover: --path is empty -- omit --path to scan the whole "
|
|
4259
|
+
"repo; an empty bound is not a bound.", file=sys.stderr)
|
|
4260
|
+
sys.exit(2)
|
|
4261
|
+
bound = posixpath.normpath(raw).strip("/") # ./src, src/ , /src -> src
|
|
4262
|
+
if bound in (".", "") or bound == ".." or bound.startswith("../"):
|
|
4263
|
+
print("[qa_ledger] discover: --path %r does not name a subtree inside the repo."
|
|
4264
|
+
% args.path, file=sys.stderr)
|
|
4265
|
+
sys.exit(2)
|
|
4266
|
+
if _gc_rel(os.path.join(repo_path, bound.replace("/", os.sep)), repo_path) is None:
|
|
4267
|
+
print("[qa_ledger] discover: --path %r escapes the repo tree." % args.path,
|
|
4268
|
+
file=sys.stderr)
|
|
4269
|
+
sys.exit(2)
|
|
4270
|
+
tracked = [f for f in tracked if _under_bound(f, bound)]
|
|
4271
|
+
if not tracked:
|
|
4272
|
+
# a typo'd bound silently emitting an empty delta is the silent-degrade trap
|
|
4273
|
+
print("[qa_ledger] discover: --path %r matches no tracked file -- refusing to "
|
|
4274
|
+
"emit an empty delta for a bound that points at nothing." % args.path,
|
|
4275
|
+
file=sys.stderr)
|
|
4276
|
+
sys.exit(2)
|
|
4227
4277
|
static_obs, unsupported = _extract_static_py(repo_path, tracked)
|
|
4228
|
-
measured_obs = _golden_backed_obs(ledger, args.repo, repo_path)
|
|
4278
|
+
measured_obs = _golden_backed_obs(ledger, args.repo, repo_path, bound)
|
|
4229
4279
|
narrated_obs, nerrs = ([], [])
|
|
4230
4280
|
if args.narrated:
|
|
4231
4281
|
narrated_obs, nerrs = _load_narrated(args.narrated, repo_path)
|
|
@@ -4244,10 +4294,11 @@ def cmd_discover(args):
|
|
|
4244
4294
|
o["canonical_match"] = _match_canonical(o["statement"], canon)
|
|
4245
4295
|
observations = sorted(by_id.values(), key=lambda o: o["id"])
|
|
4246
4296
|
delta = {"_generated_by": "qa_ledger.py discover (ADR-013) -- machine-canonical; "
|
|
4247
|
-
"never hand-edit (ids are content-addressed
|
|
4248
|
-
"covers
|
|
4249
|
-
"_integrity":
|
|
4297
|
+
"never hand-edit (ids are content-addressed; the seal "
|
|
4298
|
+
"covers observations, repo and path)",
|
|
4299
|
+
"_integrity": _delta_seal(observations, args.repo, bound),
|
|
4250
4300
|
"repo": args.repo,
|
|
4301
|
+
**({"path": bound} if bound else {}),
|
|
4251
4302
|
"observations": observations,
|
|
4252
4303
|
"static_unsupported": {"files": unsupported,
|
|
4253
4304
|
"note": "static extractors are Python-only in v0 "
|
|
@@ -4482,6 +4533,10 @@ def cmd_fidelity(args):
|
|
|
4482
4533
|
sys.exit(2)
|
|
4483
4534
|
obs = (delta.get("observations") or []) if delta else []
|
|
4484
4535
|
verdicts = _curation_verdicts(ledger, args.repo)
|
|
4536
|
+
# fidelity respects the SAME bound that produced the delta (user decision, FR-001): a
|
|
4537
|
+
# bounded discovery is measured over its own subtree, so unexplained_code and the other
|
|
4538
|
+
# mechanical dimensions never mix the delta's scope with the whole repo's.
|
|
4539
|
+
bound = delta.get("path") if delta else None
|
|
4485
4540
|
dims = {}
|
|
4486
4541
|
# traceability: canonical items reachable in code via the uscha-spec id machinery
|
|
4487
4542
|
canon_items = []
|
|
@@ -4492,7 +4547,17 @@ def cmd_fidelity(args):
|
|
|
4492
4547
|
canon_items = json.load(fh).get("items") or []
|
|
4493
4548
|
except (OSError, ValueError):
|
|
4494
4549
|
canon_items = []
|
|
4495
|
-
tracked = _tracked_files(repo_path) or []
|
|
4550
|
+
tracked = [f for f in (_tracked_files(repo_path) or []) if _under_bound(f, bound)]
|
|
4551
|
+
_scope = " (bounded to %s)" % bound if bound else ""
|
|
4552
|
+
if bound:
|
|
4553
|
+
# scope the DENOMINATOR too, not just the file scan: promote MERGES into CANONICAL
|
|
4554
|
+
# repo-wide, so an earlier unbounded promote would otherwise count out-of-bound
|
|
4555
|
+
# items as "no longer derives" -- the exact scope-mixing this release kills, half-
|
|
4556
|
+
# done if only the numerator moves (fresh-review LOW). An item is under the bound
|
|
4557
|
+
# when its primary provenance file is.
|
|
4558
|
+
canon_items = [it for it in canon_items
|
|
4559
|
+
if any(_under_bound(f.split(":")[0].split("#")[0], bound)
|
|
4560
|
+
for f in (it.get("provenance") or {}).get("files") or [])]
|
|
4496
4561
|
marked, marker_files = set(), set()
|
|
4497
4562
|
for f, mid in _scan_spec_markers(repo_path, tracked):
|
|
4498
4563
|
marked.add(mid)
|
|
@@ -4501,8 +4566,8 @@ def cmd_fidelity(args):
|
|
|
4501
4566
|
traced = sum(1 for it in canon_items if (it.get("derived_from") or "") in marked)
|
|
4502
4567
|
dims["traceability"] = _fid_dim(round(traced / len(canon_items), 4),
|
|
4503
4568
|
"uscha-spec marker scan over %d git-tracked "
|
|
4504
|
-
"files vs %d canonical item(s)"
|
|
4505
|
-
% (len(tracked), len(canon_items)))
|
|
4569
|
+
"files%s vs %d canonical item(s)"
|
|
4570
|
+
% (len(tracked), _scope, len(canon_items)))
|
|
4506
4571
|
else:
|
|
4507
4572
|
dims["traceability"] = _fid_dim(None, "UNMEASURED: no canonical items promoted yet")
|
|
4508
4573
|
# behavior: the latest ingested golden-diff gate verdict
|
|
@@ -4528,9 +4593,9 @@ def cmd_fidelity(args):
|
|
|
4528
4593
|
cur_ids = {o["id"] for o in cur_static}
|
|
4529
4594
|
okc = sum(1 for it in static_canon if it.get("derived_from") in cur_ids)
|
|
4530
4595
|
dims["contracts"] = _fid_dim(round(okc / len(static_canon), 4),
|
|
4531
|
-
"re-ran static extractors; %d/%d canonical static "
|
|
4596
|
+
"re-ran static extractors%s; %d/%d canonical static "
|
|
4532
4597
|
"item(s) still derive from the code"
|
|
4533
|
-
% (okc, len(static_canon)))
|
|
4598
|
+
% (_scope, okc, len(static_canon)))
|
|
4534
4599
|
else:
|
|
4535
4600
|
dims["contracts"] = _fid_dim(None, "UNMEASURED: no static-class canonical items")
|
|
4536
4601
|
# curation_closure: curated OBS / total OBS in the active delta
|
|
@@ -4565,14 +4630,15 @@ def cmd_fidelity(args):
|
|
|
4565
4630
|
unex = [f for f in prod if f not in lineage]
|
|
4566
4631
|
dims["unexplained_code"] = _fid_dim(
|
|
4567
4632
|
round(len(unex) / len(prod), 4),
|
|
4568
|
-
"%d/%d tracked prod source file(s) with no path to a canonical item or "
|
|
4569
|
-
"preserved OBS (v0 granularity: FILE)" % (len(unex), len(prod)),
|
|
4633
|
+
"%d/%d tracked prod source file(s)%s with no path to a canonical item or "
|
|
4634
|
+
"preserved OBS (v0 granularity: FILE)" % (len(unex), len(prod), _scope),
|
|
4570
4635
|
files=unex[:10] + (["(+%d)" % (len(unex) - 10)] if len(unex) > 10 else []))
|
|
4571
4636
|
else:
|
|
4572
4637
|
dims["unexplained_code"] = _fid_dim(None, "UNMEASURED: no tracked prod source files")
|
|
4573
4638
|
dims["semantic"] = _fid_dim(None, "not wired: an LLM-judged comparison enters as "
|
|
4574
4639
|
"advisory only and can NEVER gate (INV-ADVISORY-01)")
|
|
4575
4640
|
out = {"repo": args.repo,
|
|
4641
|
+
**({"path": bound} if bound else {}),
|
|
4576
4642
|
"dimensions": {k: dict(dims[k], **{"class": FIDELITY_DIMENSIONS[k]})
|
|
4577
4643
|
for k in ("traceability", "behavior", "contracts",
|
|
4578
4644
|
"curation_closure", "unexplained_code", "semantic")}}
|
|
@@ -4581,8 +4647,8 @@ def cmd_fidelity(args):
|
|
|
4581
4647
|
if args.json:
|
|
4582
4648
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
4583
4649
|
else:
|
|
4584
|
-
print("FIDELITY %s (vector -- no blend; each number stands on its own evidence):"
|
|
4585
|
-
% args.repo)
|
|
4650
|
+
print("FIDELITY %s%s (vector -- no blend; each number stands on its own evidence):"
|
|
4651
|
+
% (args.repo, " [bounded to %s]" % bound if bound else ""))
|
|
4586
4652
|
for k, d in out["dimensions"].items():
|
|
4587
4653
|
val = "UNMEASURED" if d["value"] is None else "%.2f" % d["value"]
|
|
4588
4654
|
print(" %-17s %-10s [%s] %s" % (k, val, d["class"], d["provenance"]))
|
|
@@ -8596,6 +8662,10 @@ def build_parser():
|
|
|
8596
8662
|
pdd.add_argument("--narrated", default=None,
|
|
8597
8663
|
help="JSON list of skill-supplied observations {type, statement, files}; "
|
|
8598
8664
|
"the engine classifies them narrated -- it never calls an LLM")
|
|
8665
|
+
pdd.add_argument("--path", default=None,
|
|
8666
|
+
help="bound the mechanical scans to one subtree/file (repo-relative); "
|
|
8667
|
+
"a bound matching nothing is a refusal, and it is recorded in "
|
|
8668
|
+
"the delta")
|
|
8599
8669
|
pdd.add_argument("--acceptance", default=None,
|
|
8600
8670
|
help="acceptance file for canonical_match (default: config "
|
|
8601
8671
|
"defaults.acceptance_file, else ACCEPTANCE.md)")
|
|
@@ -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.71.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, 40 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.71.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.71.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"AC-DD-01": true, "AC-DD-02": true, "AC-DD-03": true, "AC-DD-04": true, "AC-DD-05": true, "AC-DD-06": true, "AC-CU-06": true, "AC-CU-01": true, "AC-CU-02": true, "AC-CU-03": true, "AC-CU-04": true, "AC-CU-05": true, "review-m1": true, "review-m3": true, "review-h1": true}
|
|
1
|
+
{"AC-DD-01": true, "AC-DD-02": true, "AC-DD-03": true, "AC-DD-04": true, "AC-DD-05": true, "AC-DD-06": true, "AC-CU-06": true, "AC-CU-01": true, "AC-CU-02": true, "AC-CU-03": true, "AC-CU-04": true, "AC-CU-05": true, "AC-DD-07": true, "review-m1": true, "review-m3": true, "review-h1": true}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"AC-FV-01": true, "AC-FV-02": true, "AC-FV-04": true, "AC-FV-05": true, "AC-FV-03": true, "review-h2": true, "review-m4": true}
|
|
1
|
+
{"AC-FV-01": true, "AC-FV-02": true, "AC-FV-04": true, "AC-FV-05": true, "AC-FV-03": true, "review-h2": true, "AC-FV-06": true, "review-m4": true}
|
|
@@ -58,6 +58,7 @@ import hashlib
|
|
|
58
58
|
import json
|
|
59
59
|
import math
|
|
60
60
|
import os
|
|
61
|
+
import posixpath
|
|
61
62
|
import re
|
|
62
63
|
import shutil
|
|
63
64
|
import subprocess
|
|
@@ -3913,6 +3914,14 @@ _DELTA_BANNER = ("GENERATED by qa_ledger.py discover (ADR-013). Rendered view of
|
|
|
3913
3914
|
+ CANDIDATE_DELTA_FILE + " -- hand edits are overwritten on regeneration.")
|
|
3914
3915
|
|
|
3915
3916
|
|
|
3917
|
+
def _delta_seal(observations, repo, path):
|
|
3918
|
+
"""The seal covers everything semantically load-bearing that the content-addressed OBS
|
|
3919
|
+
ids do NOT: the full observation set, the repo, and the bound. `path` changes what the
|
|
3920
|
+
delta MEANS (a partial discovery), so it must be sealed -- a hand edit of any of these is
|
|
3921
|
+
a named malformation (fresh-review MEDIUM)."""
|
|
3922
|
+
return _integrity_hash({"observations": observations, "repo": repo, "path": path})
|
|
3923
|
+
|
|
3924
|
+
|
|
3916
3925
|
def _obs_id(otype, statement, primary_prov):
|
|
3917
3926
|
"""Content-addressed: OBS-sha256(type + LF + normalized statement + LF + primary
|
|
3918
3927
|
provenance)[:12]. Normalization = lowercase + whitespace collapse. Re-running discovery
|
|
@@ -3998,7 +4007,11 @@ def _extract_static_py(repo_path, tracked):
|
|
|
3998
4007
|
return obs, unsupported
|
|
3999
4008
|
|
|
4000
4009
|
|
|
4001
|
-
def
|
|
4010
|
+
def _under_bound(rel, bound):
|
|
4011
|
+
return bound is None or rel == bound or rel.startswith(bound + "/")
|
|
4012
|
+
|
|
4013
|
+
|
|
4014
|
+
def _golden_backed_obs(ledger, repo, repo_path, bound=None):
|
|
4002
4015
|
"""Measured observations: one per approved golden fixture, backed by the LATEST ingested
|
|
4003
4016
|
golden-diff gate record (AC-DD-03). Only source: real, ledger-ingested execution. No
|
|
4004
4017
|
ingested run -> no measured OBS; a fixture on disk that nothing executed is not evidence."""
|
|
@@ -4016,6 +4029,8 @@ def _golden_backed_obs(ledger, repo, repo_path):
|
|
|
4016
4029
|
if ".approved." not in f:
|
|
4017
4030
|
continue
|
|
4018
4031
|
rel = os.path.relpath(os.path.join(root, f), repo_path).replace(os.sep, "/")
|
|
4032
|
+
if not _under_bound(rel, bound):
|
|
4033
|
+
continue
|
|
4019
4034
|
stmt = "behavior frozen by golden fixture %s matches the approved baseline" % rel
|
|
4020
4035
|
obs.append({"id": _obs_id("behavior", stmt, rel), "type": "behavior",
|
|
4021
4036
|
"statement": stmt, "evidence_class": "measured",
|
|
@@ -4108,9 +4123,16 @@ def _match_canonical(statement, canon_ids):
|
|
|
4108
4123
|
|
|
4109
4124
|
def _render_delta_md(delta, verdicts):
|
|
4110
4125
|
lines = ["<!-- %s -->" % _DELTA_BANNER, "",
|
|
4111
|
-
"# CANDIDATE-DELTA (rendered view)", ""
|
|
4112
|
-
|
|
4113
|
-
|
|
4126
|
+
"# CANDIDATE-DELTA (rendered view)", ""]
|
|
4127
|
+
if delta.get("path"):
|
|
4128
|
+
# the bound is the artifact the HUMAN curates from -- a partial discovery must not
|
|
4129
|
+
# read as a complete one, or the shrunk INV-CURATION-01 surface is undisclosed
|
|
4130
|
+
# (fresh-review HIGH). The JSON recorded it; the human-facing view must too.
|
|
4131
|
+
lines += ["> **BOUNDED discovery** — mechanical scans were restricted to "
|
|
4132
|
+
"`%s`. Files outside this path were NOT scanned; this delta is PARTIAL "
|
|
4133
|
+
"by construction." % delta["path"], ""]
|
|
4134
|
+
lines += ["| id | type | class | verdict | statement | provenance |",
|
|
4135
|
+
"|----|------|-------|---------|-----------|------------|"]
|
|
4114
4136
|
for o in delta["observations"]:
|
|
4115
4137
|
v = verdicts.get(o["id"], "(uncurated)")
|
|
4116
4138
|
files = ", ".join(o["provenance"].get("files") or []) or "-"
|
|
@@ -4177,10 +4199,10 @@ def _load_delta(repo_path):
|
|
|
4177
4199
|
# launders narrated inference into measured evidence (fresh-review MEDIUM, reproduced)
|
|
4178
4200
|
if not errors:
|
|
4179
4201
|
seal = delta.get("_integrity")
|
|
4180
|
-
want =
|
|
4202
|
+
want = _delta_seal(obs, delta.get("repo"), delta.get("path"))
|
|
4181
4203
|
if seal != want:
|
|
4182
|
-
errors.append("integrity seal %s does not match the
|
|
4183
|
-
"
|
|
4204
|
+
errors.append("integrity seal %s does not match the delta content -- observations, "
|
|
4205
|
+
"repo or path was hand-edited (regenerate via `discover`)"
|
|
4184
4206
|
% ("missing" if seal is None else repr(seal)))
|
|
4185
4207
|
return delta, errors
|
|
4186
4208
|
|
|
@@ -4224,8 +4246,36 @@ def cmd_discover(args):
|
|
|
4224
4246
|
"from tracked files and cannot proceed without it." % repo_path,
|
|
4225
4247
|
file=sys.stderr)
|
|
4226
4248
|
sys.exit(2)
|
|
4249
|
+
bound = None
|
|
4250
|
+
if args.path is not None:
|
|
4251
|
+
# the bound restricts the MECHANICAL scans (static, measured); narrated input stays
|
|
4252
|
+
# the skill's to scope. Field-found before the first run (AC-DD-07): "real, bounded"
|
|
4253
|
+
# is unimplementable without it.
|
|
4254
|
+
raw = args.path.replace("\\", "/").strip()
|
|
4255
|
+
if not raw:
|
|
4256
|
+
# an empty --path is the silent-degrade trap (a wrapper passing an unset var):
|
|
4257
|
+
# falsy would mean "no bound" and quietly scan the whole repo (fresh-review MED)
|
|
4258
|
+
print("[qa_ledger] discover: --path is empty -- omit --path to scan the whole "
|
|
4259
|
+
"repo; an empty bound is not a bound.", file=sys.stderr)
|
|
4260
|
+
sys.exit(2)
|
|
4261
|
+
bound = posixpath.normpath(raw).strip("/") # ./src, src/ , /src -> src
|
|
4262
|
+
if bound in (".", "") or bound == ".." or bound.startswith("../"):
|
|
4263
|
+
print("[qa_ledger] discover: --path %r does not name a subtree inside the repo."
|
|
4264
|
+
% args.path, file=sys.stderr)
|
|
4265
|
+
sys.exit(2)
|
|
4266
|
+
if _gc_rel(os.path.join(repo_path, bound.replace("/", os.sep)), repo_path) is None:
|
|
4267
|
+
print("[qa_ledger] discover: --path %r escapes the repo tree." % args.path,
|
|
4268
|
+
file=sys.stderr)
|
|
4269
|
+
sys.exit(2)
|
|
4270
|
+
tracked = [f for f in tracked if _under_bound(f, bound)]
|
|
4271
|
+
if not tracked:
|
|
4272
|
+
# a typo'd bound silently emitting an empty delta is the silent-degrade trap
|
|
4273
|
+
print("[qa_ledger] discover: --path %r matches no tracked file -- refusing to "
|
|
4274
|
+
"emit an empty delta for a bound that points at nothing." % args.path,
|
|
4275
|
+
file=sys.stderr)
|
|
4276
|
+
sys.exit(2)
|
|
4227
4277
|
static_obs, unsupported = _extract_static_py(repo_path, tracked)
|
|
4228
|
-
measured_obs = _golden_backed_obs(ledger, args.repo, repo_path)
|
|
4278
|
+
measured_obs = _golden_backed_obs(ledger, args.repo, repo_path, bound)
|
|
4229
4279
|
narrated_obs, nerrs = ([], [])
|
|
4230
4280
|
if args.narrated:
|
|
4231
4281
|
narrated_obs, nerrs = _load_narrated(args.narrated, repo_path)
|
|
@@ -4244,10 +4294,11 @@ def cmd_discover(args):
|
|
|
4244
4294
|
o["canonical_match"] = _match_canonical(o["statement"], canon)
|
|
4245
4295
|
observations = sorted(by_id.values(), key=lambda o: o["id"])
|
|
4246
4296
|
delta = {"_generated_by": "qa_ledger.py discover (ADR-013) -- machine-canonical; "
|
|
4247
|
-
"never hand-edit (ids are content-addressed
|
|
4248
|
-
"covers
|
|
4249
|
-
"_integrity":
|
|
4297
|
+
"never hand-edit (ids are content-addressed; the seal "
|
|
4298
|
+
"covers observations, repo and path)",
|
|
4299
|
+
"_integrity": _delta_seal(observations, args.repo, bound),
|
|
4250
4300
|
"repo": args.repo,
|
|
4301
|
+
**({"path": bound} if bound else {}),
|
|
4251
4302
|
"observations": observations,
|
|
4252
4303
|
"static_unsupported": {"files": unsupported,
|
|
4253
4304
|
"note": "static extractors are Python-only in v0 "
|
|
@@ -4482,6 +4533,10 @@ def cmd_fidelity(args):
|
|
|
4482
4533
|
sys.exit(2)
|
|
4483
4534
|
obs = (delta.get("observations") or []) if delta else []
|
|
4484
4535
|
verdicts = _curation_verdicts(ledger, args.repo)
|
|
4536
|
+
# fidelity respects the SAME bound that produced the delta (user decision, FR-001): a
|
|
4537
|
+
# bounded discovery is measured over its own subtree, so unexplained_code and the other
|
|
4538
|
+
# mechanical dimensions never mix the delta's scope with the whole repo's.
|
|
4539
|
+
bound = delta.get("path") if delta else None
|
|
4485
4540
|
dims = {}
|
|
4486
4541
|
# traceability: canonical items reachable in code via the uscha-spec id machinery
|
|
4487
4542
|
canon_items = []
|
|
@@ -4492,7 +4547,17 @@ def cmd_fidelity(args):
|
|
|
4492
4547
|
canon_items = json.load(fh).get("items") or []
|
|
4493
4548
|
except (OSError, ValueError):
|
|
4494
4549
|
canon_items = []
|
|
4495
|
-
tracked = _tracked_files(repo_path) or []
|
|
4550
|
+
tracked = [f for f in (_tracked_files(repo_path) or []) if _under_bound(f, bound)]
|
|
4551
|
+
_scope = " (bounded to %s)" % bound if bound else ""
|
|
4552
|
+
if bound:
|
|
4553
|
+
# scope the DENOMINATOR too, not just the file scan: promote MERGES into CANONICAL
|
|
4554
|
+
# repo-wide, so an earlier unbounded promote would otherwise count out-of-bound
|
|
4555
|
+
# items as "no longer derives" -- the exact scope-mixing this release kills, half-
|
|
4556
|
+
# done if only the numerator moves (fresh-review LOW). An item is under the bound
|
|
4557
|
+
# when its primary provenance file is.
|
|
4558
|
+
canon_items = [it for it in canon_items
|
|
4559
|
+
if any(_under_bound(f.split(":")[0].split("#")[0], bound)
|
|
4560
|
+
for f in (it.get("provenance") or {}).get("files") or [])]
|
|
4496
4561
|
marked, marker_files = set(), set()
|
|
4497
4562
|
for f, mid in _scan_spec_markers(repo_path, tracked):
|
|
4498
4563
|
marked.add(mid)
|
|
@@ -4501,8 +4566,8 @@ def cmd_fidelity(args):
|
|
|
4501
4566
|
traced = sum(1 for it in canon_items if (it.get("derived_from") or "") in marked)
|
|
4502
4567
|
dims["traceability"] = _fid_dim(round(traced / len(canon_items), 4),
|
|
4503
4568
|
"uscha-spec marker scan over %d git-tracked "
|
|
4504
|
-
"files vs %d canonical item(s)"
|
|
4505
|
-
% (len(tracked), len(canon_items)))
|
|
4569
|
+
"files%s vs %d canonical item(s)"
|
|
4570
|
+
% (len(tracked), _scope, len(canon_items)))
|
|
4506
4571
|
else:
|
|
4507
4572
|
dims["traceability"] = _fid_dim(None, "UNMEASURED: no canonical items promoted yet")
|
|
4508
4573
|
# behavior: the latest ingested golden-diff gate verdict
|
|
@@ -4528,9 +4593,9 @@ def cmd_fidelity(args):
|
|
|
4528
4593
|
cur_ids = {o["id"] for o in cur_static}
|
|
4529
4594
|
okc = sum(1 for it in static_canon if it.get("derived_from") in cur_ids)
|
|
4530
4595
|
dims["contracts"] = _fid_dim(round(okc / len(static_canon), 4),
|
|
4531
|
-
"re-ran static extractors; %d/%d canonical static "
|
|
4596
|
+
"re-ran static extractors%s; %d/%d canonical static "
|
|
4532
4597
|
"item(s) still derive from the code"
|
|
4533
|
-
% (okc, len(static_canon)))
|
|
4598
|
+
% (_scope, okc, len(static_canon)))
|
|
4534
4599
|
else:
|
|
4535
4600
|
dims["contracts"] = _fid_dim(None, "UNMEASURED: no static-class canonical items")
|
|
4536
4601
|
# curation_closure: curated OBS / total OBS in the active delta
|
|
@@ -4565,14 +4630,15 @@ def cmd_fidelity(args):
|
|
|
4565
4630
|
unex = [f for f in prod if f not in lineage]
|
|
4566
4631
|
dims["unexplained_code"] = _fid_dim(
|
|
4567
4632
|
round(len(unex) / len(prod), 4),
|
|
4568
|
-
"%d/%d tracked prod source file(s) with no path to a canonical item or "
|
|
4569
|
-
"preserved OBS (v0 granularity: FILE)" % (len(unex), len(prod)),
|
|
4633
|
+
"%d/%d tracked prod source file(s)%s with no path to a canonical item or "
|
|
4634
|
+
"preserved OBS (v0 granularity: FILE)" % (len(unex), len(prod), _scope),
|
|
4570
4635
|
files=unex[:10] + (["(+%d)" % (len(unex) - 10)] if len(unex) > 10 else []))
|
|
4571
4636
|
else:
|
|
4572
4637
|
dims["unexplained_code"] = _fid_dim(None, "UNMEASURED: no tracked prod source files")
|
|
4573
4638
|
dims["semantic"] = _fid_dim(None, "not wired: an LLM-judged comparison enters as "
|
|
4574
4639
|
"advisory only and can NEVER gate (INV-ADVISORY-01)")
|
|
4575
4640
|
out = {"repo": args.repo,
|
|
4641
|
+
**({"path": bound} if bound else {}),
|
|
4576
4642
|
"dimensions": {k: dict(dims[k], **{"class": FIDELITY_DIMENSIONS[k]})
|
|
4577
4643
|
for k in ("traceability", "behavior", "contracts",
|
|
4578
4644
|
"curation_closure", "unexplained_code", "semantic")}}
|
|
@@ -4581,8 +4647,8 @@ def cmd_fidelity(args):
|
|
|
4581
4647
|
if args.json:
|
|
4582
4648
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
4583
4649
|
else:
|
|
4584
|
-
print("FIDELITY %s (vector -- no blend; each number stands on its own evidence):"
|
|
4585
|
-
% args.repo)
|
|
4650
|
+
print("FIDELITY %s%s (vector -- no blend; each number stands on its own evidence):"
|
|
4651
|
+
% (args.repo, " [bounded to %s]" % bound if bound else ""))
|
|
4586
4652
|
for k, d in out["dimensions"].items():
|
|
4587
4653
|
val = "UNMEASURED" if d["value"] is None else "%.2f" % d["value"]
|
|
4588
4654
|
print(" %-17s %-10s [%s] %s" % (k, val, d["class"], d["provenance"]))
|
|
@@ -8596,6 +8662,10 @@ def build_parser():
|
|
|
8596
8662
|
pdd.add_argument("--narrated", default=None,
|
|
8597
8663
|
help="JSON list of skill-supplied observations {type, statement, files}; "
|
|
8598
8664
|
"the engine classifies them narrated -- it never calls an LLM")
|
|
8665
|
+
pdd.add_argument("--path", default=None,
|
|
8666
|
+
help="bound the mechanical scans to one subtree/file (repo-relative); "
|
|
8667
|
+
"a bound matching nothing is a refusal, and it is recorded in "
|
|
8668
|
+
"the delta")
|
|
8599
8669
|
pdd.add_argument("--acceptance", default=None,
|
|
8600
8670
|
help="acceptance file for canonical_match (default: config "
|
|
8601
8671
|
"defaults.acceptance_file, else ACCEPTANCE.md)")
|