@andresmassello/uscha 1.62.0 → 1.63.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 +169 -0
- package/uscha-kit/.claude-plugin/plugin.json +2 -2
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/README.md +30 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/reports/junit/.cleanroom-cases.json +1 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +169 -0
- package/uscha-kit/uscha.config.json +5 -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.63.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
|
|
|
@@ -76,7 +76,7 @@ and see which file, which test, and when.
|
|
|
76
76
|
| `/uscha-mirador` | Bird's-eye HTML dashboard: readiness, trail, acceptance, loops |
|
|
77
77
|
| `/uscha-status` | One-line progress readout, in chat |
|
|
78
78
|
|
|
79
|
-
**A measurement engine** (`qa_ledger.py`,
|
|
79
|
+
**A measurement engine** (`qa_ledger.py`, 33 subcommands, Python stdlib) that ingests
|
|
80
80
|
evidence from **11 language stacks** — maven, gradle, ant, python, node, go, rust, dotnet,
|
|
81
81
|
cpp, swift, flutter — and computes a readiness score with hard caps and visible provenance.
|
|
82
82
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.63.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",
|
|
@@ -2629,6 +2629,35 @@ def _derive_phase(ledger, name, node, k, qa_order):
|
|
|
2629
2629
|
reasons.append("la evidencia medida no contiene tests ejecutados")
|
|
2630
2630
|
_go, sev = _gate_open_and_sev(node)
|
|
2631
2631
|
blk = sev.get("BLOCKER", 0) + sev.get("CRITICAL", 0)
|
|
2632
|
+
# clean-room gate (ADR-008). OPT-IN: no clean_room block, or mode "off", and this does not
|
|
2633
|
+
# exist -- behavior identical to earlier releases. Declared "final": pr-ready additionally
|
|
2634
|
+
# requires clean-room evidence that is GREEN and pinned to the CURRENT HEAD. A new commit
|
|
2635
|
+
# makes the previous run stale for the gate, the same staleness posture the rest of the
|
|
2636
|
+
# engine takes: evidence certifies the thing it was measured against, nothing later.
|
|
2637
|
+
_cr = _cr_cfg(ledger)
|
|
2638
|
+
if _cr and _cr.get("mode") == "final":
|
|
2639
|
+
_head = None
|
|
2640
|
+
_hr = None
|
|
2641
|
+
_crcfg = (_repo_cfg(ledger, name) if name != "integration"
|
|
2642
|
+
else {"path": "."}) # synthetic scope: never in config["repos"]
|
|
2643
|
+
try:
|
|
2644
|
+
_hr = subprocess.run(["git", "rev-parse", "HEAD"],
|
|
2645
|
+
cwd=_crcfg.get("path", "."),
|
|
2646
|
+
capture_output=True, text=True, encoding="utf-8",
|
|
2647
|
+
errors="replace")
|
|
2648
|
+
except OSError:
|
|
2649
|
+
_hr = None
|
|
2650
|
+
if _hr is not None and _hr.returncode == 0:
|
|
2651
|
+
_head = _hr.stdout.strip()
|
|
2652
|
+
_run = _cr_latest(ledger, name, _head) if _head else None
|
|
2653
|
+
if not _head:
|
|
2654
|
+
reasons.append("clean-room declarado pero no se pudo resolver HEAD "
|
|
2655
|
+
"(no se mide, no se aprueba)")
|
|
2656
|
+
conv = False
|
|
2657
|
+
elif not _run or not _run.get("ok"):
|
|
2658
|
+
reasons.append("falta clean-room verde para %s (evidencia del arbol no "
|
|
2659
|
+
"certifica el commit)" % _head[:8])
|
|
2660
|
+
conv = False
|
|
2632
2661
|
if conv and tests_measured_green and not tests_red and blk == 0:
|
|
2633
2662
|
evidence = ["ciclo de agente limpio", "tests verdes (medidos)",
|
|
2634
2663
|
"0 BLOCKER/CRITICAL abiertos"]
|
|
@@ -3355,6 +3384,132 @@ def cmd_golden_coverage(args):
|
|
|
3355
3384
|
|
|
3356
3385
|
|
|
3357
3386
|
|
|
3387
|
+
# --------------------------------------------------------------------------- #
|
|
3388
|
+
# cleanroom (ADR-008: verify the COMMIT, not the tree -- opt-in, human-supplied command)
|
|
3389
|
+
# --------------------------------------------------------------------------- #
|
|
3390
|
+
|
|
3391
|
+
CLEAN_ROOM_KEY = "clean_room"
|
|
3392
|
+
|
|
3393
|
+
|
|
3394
|
+
def _cr_cfg(ledger):
|
|
3395
|
+
cfg = ledger["config"].get("defaults", {}).get(CLEAN_ROOM_KEY)
|
|
3396
|
+
return cfg if isinstance(cfg, dict) else None
|
|
3397
|
+
|
|
3398
|
+
|
|
3399
|
+
def _cr_latest(ledger, repo, ref=None):
|
|
3400
|
+
"""Latest clean-room record for a repo, optionally pinned to one ref."""
|
|
3401
|
+
runs = [e for e in ledger.get(CLEAN_ROOM_KEY, [])
|
|
3402
|
+
if e.get("repo") == repo and (ref is None or e.get("ref") == ref)]
|
|
3403
|
+
return runs[-1] if runs else None
|
|
3404
|
+
|
|
3405
|
+
|
|
3406
|
+
def cmd_cleanroom(args):
|
|
3407
|
+
"""Run a command against a CLEAN CHECKOUT of one commit, in a throwaway worktree.
|
|
3408
|
+
|
|
3409
|
+
Evidence produced in the maker's tree is true of the TREE; this produces evidence true
|
|
3410
|
+
of the COMMIT. As a side effect maker != checker becomes physical: the worktree cannot
|
|
3411
|
+
see uncommitted state.
|
|
3412
|
+
|
|
3413
|
+
The engine does NOT decide what to run. The command arrives explicitly via --run, the
|
|
3414
|
+
same contract golden-coverage uses for --harness: the engine owns what it can guarantee
|
|
3415
|
+
(isolation, the SHA binding, cleanup) and never guesses what a project's suite is.
|
|
3416
|
+
Reading test_command_* from config and executing it would make the engine an executor
|
|
3417
|
+
of config-supplied shell, which it is not (ADR-008)."""
|
|
3418
|
+
import time
|
|
3419
|
+
ledger = _load(args.ledger)
|
|
3420
|
+
_repo_node(ledger, args.repo)
|
|
3421
|
+
repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
|
|
3422
|
+
|
|
3423
|
+
def git(*a, **kw):
|
|
3424
|
+
cwd = kw.pop("cwd", repo_path)
|
|
3425
|
+
try:
|
|
3426
|
+
return subprocess.run(["git"] + list(a), cwd=cwd, capture_output=True,
|
|
3427
|
+
text=True, encoding="utf-8", errors="replace")
|
|
3428
|
+
except OSError:
|
|
3429
|
+
return None
|
|
3430
|
+
|
|
3431
|
+
rev = git("rev-parse", "--verify", (args.ref or "HEAD") + "^{commit}")
|
|
3432
|
+
if rev is None or rev.returncode != 0 or not rev.stdout.strip():
|
|
3433
|
+
print("[qa_ledger] cannot resolve ref %r in %s - nothing to verify."
|
|
3434
|
+
% (args.ref or "HEAD", repo_path), file=sys.stderr)
|
|
3435
|
+
sys.exit(2)
|
|
3436
|
+
sha = rev.stdout.strip()
|
|
3437
|
+
|
|
3438
|
+
wt = tempfile.mkdtemp(prefix="uscha-cleanroom-")
|
|
3439
|
+
target = os.path.join(wt, "tree")
|
|
3440
|
+
started = time.time()
|
|
3441
|
+
record = {"repo": args.repo, "ref": sha, "at": _now(), "ok": False,
|
|
3442
|
+
"status": None, "wall_ms": None, "worktree_sha": None}
|
|
3443
|
+
keep = bool((_cr_cfg(ledger) or {}).get("keep_worktree_on_failure"))
|
|
3444
|
+
try:
|
|
3445
|
+
add = git("worktree", "add", "--detach", target, sha)
|
|
3446
|
+
if add is None or add.returncode != 0:
|
|
3447
|
+
record["status"] = "WORKTREE_FAILED"
|
|
3448
|
+
print("[qa_ledger] git worktree add failed:\n%s"
|
|
3449
|
+
% ((add.stderr if add else "git unavailable") or "")[-800:], file=sys.stderr)
|
|
3450
|
+
else:
|
|
3451
|
+
# a worktree of a commit must be clean by construction; if it is not, something
|
|
3452
|
+
# (a smudge filter, a hook, a stale index) intervened and the isolation claim is
|
|
3453
|
+
# already false. Say so rather than measure in it.
|
|
3454
|
+
st = git("status", "--porcelain", cwd=target)
|
|
3455
|
+
if st is None or st.returncode != 0 or st.stdout.strip():
|
|
3456
|
+
record["status"] = "WORKTREE_DIRTY"
|
|
3457
|
+
else:
|
|
3458
|
+
record["worktree_sha"] = sha
|
|
3459
|
+
if args.setup:
|
|
3460
|
+
r = subprocess.run(args.setup, cwd=target, shell=True,
|
|
3461
|
+
capture_output=True, text=True,
|
|
3462
|
+
encoding="utf-8", errors="replace")
|
|
3463
|
+
if r.returncode != 0:
|
|
3464
|
+
record["status"] = "SETUP_FAILED"
|
|
3465
|
+
print((r.stderr or "")[-800:], file=sys.stderr)
|
|
3466
|
+
if record["status"] is None:
|
|
3467
|
+
r = subprocess.run(args.run, cwd=target, shell=True,
|
|
3468
|
+
capture_output=True, text=True,
|
|
3469
|
+
encoding="utf-8", errors="replace")
|
|
3470
|
+
record["exit_code"] = r.returncode
|
|
3471
|
+
record["status"] = "GREEN" if r.returncode == 0 else "RED"
|
|
3472
|
+
record["ok"] = r.returncode == 0
|
|
3473
|
+
if r.returncode != 0:
|
|
3474
|
+
print((r.stdout or "")[-1500:], file=sys.stderr)
|
|
3475
|
+
finally:
|
|
3476
|
+
record["wall_ms"] = int((time.time() - started) * 1000)
|
|
3477
|
+
# Cleanup is unconditional unless the human asked to inspect a FAILURE: a zombie
|
|
3478
|
+
# worktree is a defect, and `git worktree remove` alone leaves the admin entry behind.
|
|
3479
|
+
if record["ok"] or not keep:
|
|
3480
|
+
git("worktree", "remove", "--force", target)
|
|
3481
|
+
git("worktree", "prune")
|
|
3482
|
+
shutil.rmtree(wt, ignore_errors=True)
|
|
3483
|
+
# VERIFY the removal instead of assuming it. rmtree(ignore_errors=True) and a
|
|
3484
|
+
# discarded git return code can both fail in silence -- typically on Windows,
|
|
3485
|
+
# where a handle the caller's command left open blocks removal.
|
|
3486
|
+
if os.path.exists(target):
|
|
3487
|
+
record["cleanup_failed"] = True
|
|
3488
|
+
record["worktree_kept_at"] = target
|
|
3489
|
+
print("[qa_ledger] WARNING: the clean-room worktree could not be removed and "
|
|
3490
|
+
"is still at %s (a process may still hold a handle). Remove it with: "
|
|
3491
|
+
"git worktree remove --force %s && git worktree prune"
|
|
3492
|
+
% (target, target), file=sys.stderr)
|
|
3493
|
+
else:
|
|
3494
|
+
record["worktree_kept_at"] = target
|
|
3495
|
+
|
|
3496
|
+
ledger.setdefault(CLEAN_ROOM_KEY, []).append(record)
|
|
3497
|
+
ledger["step_counter"] += 1
|
|
3498
|
+
ledger["steps"].append({"n": ledger["step_counter"], "at": _now(),
|
|
3499
|
+
"kind": "cleanroom", "repo": args.repo})
|
|
3500
|
+
_save(args.ledger, ledger)
|
|
3501
|
+
|
|
3502
|
+
if args.json:
|
|
3503
|
+
print(json.dumps(record, indent=2, ensure_ascii=False))
|
|
3504
|
+
else:
|
|
3505
|
+
print("CLEANROOM %s @ %s: %s (%.1fs)"
|
|
3506
|
+
% (args.repo, sha[:8], record["status"], (record["wall_ms"] or 0) / 1000.0))
|
|
3507
|
+
if record.get("worktree_kept_at"):
|
|
3508
|
+
print(" worktree kept for inspection: " + record["worktree_kept_at"])
|
|
3509
|
+
sys.exit(0 if record["ok"] else 1)
|
|
3510
|
+
|
|
3511
|
+
|
|
3512
|
+
|
|
3358
3513
|
def cmd_escalate(args):
|
|
3359
3514
|
ledger = _load(args.ledger)
|
|
3360
3515
|
_repo_node(ledger, args.repo)
|
|
@@ -4401,6 +4556,9 @@ def cmd_dashboard(args):
|
|
|
4401
4556
|
_org[_rn] = _snaps[-1]["origin"]
|
|
4402
4557
|
if _org:
|
|
4403
4558
|
out["evidence_origin"] = _org
|
|
4559
|
+
if ledger.get(CLEAN_ROOM_KEY):
|
|
4560
|
+
out["clean_room"] = {r: [e for e in ledger[CLEAN_ROOM_KEY] if e.get("repo") == r][-1]
|
|
4561
|
+
for r in {e.get("repo") for e in ledger[CLEAN_ROOM_KEY]}}
|
|
4404
4562
|
if getattr(args, "json", False):
|
|
4405
4563
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
4406
4564
|
return
|
|
@@ -7105,6 +7263,17 @@ def build_parser():
|
|
|
7105
7263
|
pfp.add_argument("--json", action="store_true")
|
|
7106
7264
|
pfp.set_defaults(func=cmd_fastpath_eval)
|
|
7107
7265
|
|
|
7266
|
+
pcr = sub.add_parser("cleanroom",
|
|
7267
|
+
help="run a command against a CLEAN checkout of one commit in a throwaway worktree (ADR-008)")
|
|
7268
|
+
pcr.add_argument("--ledger", default="QA-LEDGER.json")
|
|
7269
|
+
pcr.add_argument("--repo", required=True)
|
|
7270
|
+
pcr.add_argument("--ref", default=None, help="commit to verify; default HEAD")
|
|
7271
|
+
pcr.add_argument("--run", required=True,
|
|
7272
|
+
help="the command to run inside the worktree; the engine never guesses it")
|
|
7273
|
+
pcr.add_argument("--setup", default=None, help="optional bootstrap before --run (e.g. npm ci)")
|
|
7274
|
+
pcr.add_argument("--json", action="store_true")
|
|
7275
|
+
pcr.set_defaults(func=cmd_cleanroom)
|
|
7276
|
+
|
|
7108
7277
|
pgc = sub.add_parser("golden-coverage",
|
|
7109
7278
|
help="record the MEASURED source files a golden's harness exercises (ADR-006)")
|
|
7110
7279
|
pgc.add_argument("--harness", required=True, help="script that drives the subject")
|
|
@@ -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.63.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, 33 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.63.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`,
|
|
@@ -149,6 +149,35 @@ knowing a tree was dirty does not tell you the evidence is wrong, only that it w
|
|
|
149
149
|
produced from a commit alone. The git-worktree clean-room that would answer the stronger
|
|
150
150
|
question is deliberately deferred; ADR-007 records why.
|
|
151
151
|
|
|
152
|
+
## Clean-room (ADR-008) - verify the commit, not your tree
|
|
153
|
+
|
|
154
|
+
A suite can pass in your working tree *because of uncommitted state* and fail against the
|
|
155
|
+
commit alone. The ledger records that as measured, with provenance true of the TREE and not of
|
|
156
|
+
the COMMIT that will merge.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python qa_ledger.py cleanroom --repo <name> --run "<your test command>" [--setup "npm ci"]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
It creates a detached `git worktree` of the commit, verifies it is clean by construction, runs
|
|
163
|
+
what you gave it, records `ref` / `worktree_sha` / `status` / wall-clock, and removes the
|
|
164
|
+
worktree unconditionally (a zombie worktree is a defect). Status is specific:
|
|
165
|
+
`GREEN`, `RED`, `SETUP_FAILED` (a bootstrap failure is not a failing suite),
|
|
166
|
+
`WORKTREE_FAILED`, `WORKTREE_DIRTY`.
|
|
167
|
+
|
|
168
|
+
**The engine never decides what to run.** The command arrives via `--run`, the same contract
|
|
169
|
+
`golden-coverage` uses for `--harness`: the engine owns isolation, the SHA binding and
|
|
170
|
+
cleanup, and never turns a config file into a code-execution surface. That is also why it
|
|
171
|
+
works for any stack - it never had to know the stack.
|
|
172
|
+
|
|
173
|
+
**Opt-in gate:** `defaults.clean_room.mode` absent or `"off"` and nothing changes. `"final"`
|
|
174
|
+
makes `pr-ready` require a GREEN run pinned to the current HEAD; a new commit makes the
|
|
175
|
+
previous run stale for the gate.
|
|
176
|
+
|
|
177
|
+
Honest limit: this is **not** a substitute for CI. A local worktree runs on your machine, your
|
|
178
|
+
OS, your shell - environment variance is invisible to it. Different failure class, different
|
|
179
|
+
instrument.
|
|
180
|
+
|
|
152
181
|
## End-to-end flow
|
|
153
182
|
|
|
154
183
|
`uscha-discovery` is the front for something new (you only have the idea); `uscha-adr-refine` is the front
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 1.
|
|
1
|
+
uscha-kit 1.63.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"AC-CR-06": true, "AC-CR-02": true, "AC-CR-04": true, "AC-CR-05": true, "AC-CR-03": true, "AC-CR-01": true, "AC-CR-07": true, "AC-CR-08": true}
|
|
@@ -2629,6 +2629,35 @@ def _derive_phase(ledger, name, node, k, qa_order):
|
|
|
2629
2629
|
reasons.append("la evidencia medida no contiene tests ejecutados")
|
|
2630
2630
|
_go, sev = _gate_open_and_sev(node)
|
|
2631
2631
|
blk = sev.get("BLOCKER", 0) + sev.get("CRITICAL", 0)
|
|
2632
|
+
# clean-room gate (ADR-008). OPT-IN: no clean_room block, or mode "off", and this does not
|
|
2633
|
+
# exist -- behavior identical to earlier releases. Declared "final": pr-ready additionally
|
|
2634
|
+
# requires clean-room evidence that is GREEN and pinned to the CURRENT HEAD. A new commit
|
|
2635
|
+
# makes the previous run stale for the gate, the same staleness posture the rest of the
|
|
2636
|
+
# engine takes: evidence certifies the thing it was measured against, nothing later.
|
|
2637
|
+
_cr = _cr_cfg(ledger)
|
|
2638
|
+
if _cr and _cr.get("mode") == "final":
|
|
2639
|
+
_head = None
|
|
2640
|
+
_hr = None
|
|
2641
|
+
_crcfg = (_repo_cfg(ledger, name) if name != "integration"
|
|
2642
|
+
else {"path": "."}) # synthetic scope: never in config["repos"]
|
|
2643
|
+
try:
|
|
2644
|
+
_hr = subprocess.run(["git", "rev-parse", "HEAD"],
|
|
2645
|
+
cwd=_crcfg.get("path", "."),
|
|
2646
|
+
capture_output=True, text=True, encoding="utf-8",
|
|
2647
|
+
errors="replace")
|
|
2648
|
+
except OSError:
|
|
2649
|
+
_hr = None
|
|
2650
|
+
if _hr is not None and _hr.returncode == 0:
|
|
2651
|
+
_head = _hr.stdout.strip()
|
|
2652
|
+
_run = _cr_latest(ledger, name, _head) if _head else None
|
|
2653
|
+
if not _head:
|
|
2654
|
+
reasons.append("clean-room declarado pero no se pudo resolver HEAD "
|
|
2655
|
+
"(no se mide, no se aprueba)")
|
|
2656
|
+
conv = False
|
|
2657
|
+
elif not _run or not _run.get("ok"):
|
|
2658
|
+
reasons.append("falta clean-room verde para %s (evidencia del arbol no "
|
|
2659
|
+
"certifica el commit)" % _head[:8])
|
|
2660
|
+
conv = False
|
|
2632
2661
|
if conv and tests_measured_green and not tests_red and blk == 0:
|
|
2633
2662
|
evidence = ["ciclo de agente limpio", "tests verdes (medidos)",
|
|
2634
2663
|
"0 BLOCKER/CRITICAL abiertos"]
|
|
@@ -3355,6 +3384,132 @@ def cmd_golden_coverage(args):
|
|
|
3355
3384
|
|
|
3356
3385
|
|
|
3357
3386
|
|
|
3387
|
+
# --------------------------------------------------------------------------- #
|
|
3388
|
+
# cleanroom (ADR-008: verify the COMMIT, not the tree -- opt-in, human-supplied command)
|
|
3389
|
+
# --------------------------------------------------------------------------- #
|
|
3390
|
+
|
|
3391
|
+
CLEAN_ROOM_KEY = "clean_room"
|
|
3392
|
+
|
|
3393
|
+
|
|
3394
|
+
def _cr_cfg(ledger):
|
|
3395
|
+
cfg = ledger["config"].get("defaults", {}).get(CLEAN_ROOM_KEY)
|
|
3396
|
+
return cfg if isinstance(cfg, dict) else None
|
|
3397
|
+
|
|
3398
|
+
|
|
3399
|
+
def _cr_latest(ledger, repo, ref=None):
|
|
3400
|
+
"""Latest clean-room record for a repo, optionally pinned to one ref."""
|
|
3401
|
+
runs = [e for e in ledger.get(CLEAN_ROOM_KEY, [])
|
|
3402
|
+
if e.get("repo") == repo and (ref is None or e.get("ref") == ref)]
|
|
3403
|
+
return runs[-1] if runs else None
|
|
3404
|
+
|
|
3405
|
+
|
|
3406
|
+
def cmd_cleanroom(args):
|
|
3407
|
+
"""Run a command against a CLEAN CHECKOUT of one commit, in a throwaway worktree.
|
|
3408
|
+
|
|
3409
|
+
Evidence produced in the maker's tree is true of the TREE; this produces evidence true
|
|
3410
|
+
of the COMMIT. As a side effect maker != checker becomes physical: the worktree cannot
|
|
3411
|
+
see uncommitted state.
|
|
3412
|
+
|
|
3413
|
+
The engine does NOT decide what to run. The command arrives explicitly via --run, the
|
|
3414
|
+
same contract golden-coverage uses for --harness: the engine owns what it can guarantee
|
|
3415
|
+
(isolation, the SHA binding, cleanup) and never guesses what a project's suite is.
|
|
3416
|
+
Reading test_command_* from config and executing it would make the engine an executor
|
|
3417
|
+
of config-supplied shell, which it is not (ADR-008)."""
|
|
3418
|
+
import time
|
|
3419
|
+
ledger = _load(args.ledger)
|
|
3420
|
+
_repo_node(ledger, args.repo)
|
|
3421
|
+
repo_path = _repo_cfg(ledger, args.repo).get("path", ".")
|
|
3422
|
+
|
|
3423
|
+
def git(*a, **kw):
|
|
3424
|
+
cwd = kw.pop("cwd", repo_path)
|
|
3425
|
+
try:
|
|
3426
|
+
return subprocess.run(["git"] + list(a), cwd=cwd, capture_output=True,
|
|
3427
|
+
text=True, encoding="utf-8", errors="replace")
|
|
3428
|
+
except OSError:
|
|
3429
|
+
return None
|
|
3430
|
+
|
|
3431
|
+
rev = git("rev-parse", "--verify", (args.ref or "HEAD") + "^{commit}")
|
|
3432
|
+
if rev is None or rev.returncode != 0 or not rev.stdout.strip():
|
|
3433
|
+
print("[qa_ledger] cannot resolve ref %r in %s - nothing to verify."
|
|
3434
|
+
% (args.ref or "HEAD", repo_path), file=sys.stderr)
|
|
3435
|
+
sys.exit(2)
|
|
3436
|
+
sha = rev.stdout.strip()
|
|
3437
|
+
|
|
3438
|
+
wt = tempfile.mkdtemp(prefix="uscha-cleanroom-")
|
|
3439
|
+
target = os.path.join(wt, "tree")
|
|
3440
|
+
started = time.time()
|
|
3441
|
+
record = {"repo": args.repo, "ref": sha, "at": _now(), "ok": False,
|
|
3442
|
+
"status": None, "wall_ms": None, "worktree_sha": None}
|
|
3443
|
+
keep = bool((_cr_cfg(ledger) or {}).get("keep_worktree_on_failure"))
|
|
3444
|
+
try:
|
|
3445
|
+
add = git("worktree", "add", "--detach", target, sha)
|
|
3446
|
+
if add is None or add.returncode != 0:
|
|
3447
|
+
record["status"] = "WORKTREE_FAILED"
|
|
3448
|
+
print("[qa_ledger] git worktree add failed:\n%s"
|
|
3449
|
+
% ((add.stderr if add else "git unavailable") or "")[-800:], file=sys.stderr)
|
|
3450
|
+
else:
|
|
3451
|
+
# a worktree of a commit must be clean by construction; if it is not, something
|
|
3452
|
+
# (a smudge filter, a hook, a stale index) intervened and the isolation claim is
|
|
3453
|
+
# already false. Say so rather than measure in it.
|
|
3454
|
+
st = git("status", "--porcelain", cwd=target)
|
|
3455
|
+
if st is None or st.returncode != 0 or st.stdout.strip():
|
|
3456
|
+
record["status"] = "WORKTREE_DIRTY"
|
|
3457
|
+
else:
|
|
3458
|
+
record["worktree_sha"] = sha
|
|
3459
|
+
if args.setup:
|
|
3460
|
+
r = subprocess.run(args.setup, cwd=target, shell=True,
|
|
3461
|
+
capture_output=True, text=True,
|
|
3462
|
+
encoding="utf-8", errors="replace")
|
|
3463
|
+
if r.returncode != 0:
|
|
3464
|
+
record["status"] = "SETUP_FAILED"
|
|
3465
|
+
print((r.stderr or "")[-800:], file=sys.stderr)
|
|
3466
|
+
if record["status"] is None:
|
|
3467
|
+
r = subprocess.run(args.run, cwd=target, shell=True,
|
|
3468
|
+
capture_output=True, text=True,
|
|
3469
|
+
encoding="utf-8", errors="replace")
|
|
3470
|
+
record["exit_code"] = r.returncode
|
|
3471
|
+
record["status"] = "GREEN" if r.returncode == 0 else "RED"
|
|
3472
|
+
record["ok"] = r.returncode == 0
|
|
3473
|
+
if r.returncode != 0:
|
|
3474
|
+
print((r.stdout or "")[-1500:], file=sys.stderr)
|
|
3475
|
+
finally:
|
|
3476
|
+
record["wall_ms"] = int((time.time() - started) * 1000)
|
|
3477
|
+
# Cleanup is unconditional unless the human asked to inspect a FAILURE: a zombie
|
|
3478
|
+
# worktree is a defect, and `git worktree remove` alone leaves the admin entry behind.
|
|
3479
|
+
if record["ok"] or not keep:
|
|
3480
|
+
git("worktree", "remove", "--force", target)
|
|
3481
|
+
git("worktree", "prune")
|
|
3482
|
+
shutil.rmtree(wt, ignore_errors=True)
|
|
3483
|
+
# VERIFY the removal instead of assuming it. rmtree(ignore_errors=True) and a
|
|
3484
|
+
# discarded git return code can both fail in silence -- typically on Windows,
|
|
3485
|
+
# where a handle the caller's command left open blocks removal.
|
|
3486
|
+
if os.path.exists(target):
|
|
3487
|
+
record["cleanup_failed"] = True
|
|
3488
|
+
record["worktree_kept_at"] = target
|
|
3489
|
+
print("[qa_ledger] WARNING: the clean-room worktree could not be removed and "
|
|
3490
|
+
"is still at %s (a process may still hold a handle). Remove it with: "
|
|
3491
|
+
"git worktree remove --force %s && git worktree prune"
|
|
3492
|
+
% (target, target), file=sys.stderr)
|
|
3493
|
+
else:
|
|
3494
|
+
record["worktree_kept_at"] = target
|
|
3495
|
+
|
|
3496
|
+
ledger.setdefault(CLEAN_ROOM_KEY, []).append(record)
|
|
3497
|
+
ledger["step_counter"] += 1
|
|
3498
|
+
ledger["steps"].append({"n": ledger["step_counter"], "at": _now(),
|
|
3499
|
+
"kind": "cleanroom", "repo": args.repo})
|
|
3500
|
+
_save(args.ledger, ledger)
|
|
3501
|
+
|
|
3502
|
+
if args.json:
|
|
3503
|
+
print(json.dumps(record, indent=2, ensure_ascii=False))
|
|
3504
|
+
else:
|
|
3505
|
+
print("CLEANROOM %s @ %s: %s (%.1fs)"
|
|
3506
|
+
% (args.repo, sha[:8], record["status"], (record["wall_ms"] or 0) / 1000.0))
|
|
3507
|
+
if record.get("worktree_kept_at"):
|
|
3508
|
+
print(" worktree kept for inspection: " + record["worktree_kept_at"])
|
|
3509
|
+
sys.exit(0 if record["ok"] else 1)
|
|
3510
|
+
|
|
3511
|
+
|
|
3512
|
+
|
|
3358
3513
|
def cmd_escalate(args):
|
|
3359
3514
|
ledger = _load(args.ledger)
|
|
3360
3515
|
_repo_node(ledger, args.repo)
|
|
@@ -4401,6 +4556,9 @@ def cmd_dashboard(args):
|
|
|
4401
4556
|
_org[_rn] = _snaps[-1]["origin"]
|
|
4402
4557
|
if _org:
|
|
4403
4558
|
out["evidence_origin"] = _org
|
|
4559
|
+
if ledger.get(CLEAN_ROOM_KEY):
|
|
4560
|
+
out["clean_room"] = {r: [e for e in ledger[CLEAN_ROOM_KEY] if e.get("repo") == r][-1]
|
|
4561
|
+
for r in {e.get("repo") for e in ledger[CLEAN_ROOM_KEY]}}
|
|
4404
4562
|
if getattr(args, "json", False):
|
|
4405
4563
|
print(json.dumps(out, indent=2, ensure_ascii=False))
|
|
4406
4564
|
return
|
|
@@ -7105,6 +7263,17 @@ def build_parser():
|
|
|
7105
7263
|
pfp.add_argument("--json", action="store_true")
|
|
7106
7264
|
pfp.set_defaults(func=cmd_fastpath_eval)
|
|
7107
7265
|
|
|
7266
|
+
pcr = sub.add_parser("cleanroom",
|
|
7267
|
+
help="run a command against a CLEAN checkout of one commit in a throwaway worktree (ADR-008)")
|
|
7268
|
+
pcr.add_argument("--ledger", default="QA-LEDGER.json")
|
|
7269
|
+
pcr.add_argument("--repo", required=True)
|
|
7270
|
+
pcr.add_argument("--ref", default=None, help="commit to verify; default HEAD")
|
|
7271
|
+
pcr.add_argument("--run", required=True,
|
|
7272
|
+
help="the command to run inside the worktree; the engine never guesses it")
|
|
7273
|
+
pcr.add_argument("--setup", default=None, help="optional bootstrap before --run (e.g. npm ci)")
|
|
7274
|
+
pcr.add_argument("--json", action="store_true")
|
|
7275
|
+
pcr.set_defaults(func=cmd_cleanroom)
|
|
7276
|
+
|
|
7108
7277
|
pgc = sub.add_parser("golden-coverage",
|
|
7109
7278
|
help="record the MEASURED source files a golden's harness exercises (ADR-006)")
|
|
7110
7279
|
pgc.add_argument("--harness", required=True, help="script that drives the subject")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.63.0",
|
|
3
3
|
"project": null,
|
|
4
4
|
"defaults": {
|
|
5
5
|
"coverage_threshold": 60,
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"spec_drift": {
|
|
56
56
|
"max_lag_days": 30
|
|
57
57
|
},
|
|
58
|
+
"clean_room": {
|
|
59
|
+
"mode": "off",
|
|
60
|
+
"keep_worktree_on_failure": false
|
|
61
|
+
},
|
|
58
62
|
"static_gate_zero_at": 10,
|
|
59
63
|
"constitution_file": "CONSTITUTION.md",
|
|
60
64
|
"rebuild": {
|