@andresmassello/uscha 1.40.2 → 1.43.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.
Files changed (39) hide show
  1. package/README.md +6 -6
  2. package/bin/uscha.js +19 -7
  3. package/package.json +1 -1
  4. package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +2 -2
  5. package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +3 -1
  6. package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +729 -155
  7. package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +22 -7
  8. package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +44 -5
  9. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +1 -1
  10. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +1 -1
  11. package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +666 -586
  12. package/uscha-kit/.claude-plugin/plugin.json +1 -1
  13. package/uscha-kit/.codex-plugin/plugin.json +1 -1
  14. package/uscha-kit/CHANGELOG-1.41.0.md +18 -0
  15. package/uscha-kit/CHANGELOG-1.41.1.md +53 -0
  16. package/uscha-kit/CHANGELOG-1.41.2.md +34 -0
  17. package/uscha-kit/CHANGELOG-1.41.3.md +30 -0
  18. package/uscha-kit/CHANGELOG-1.42.0.md +41 -0
  19. package/uscha-kit/CHANGELOG-1.43.0.md +37 -0
  20. package/uscha-kit/INSTALL.md +120 -101
  21. package/uscha-kit/README.md +24 -13
  22. package/uscha-kit/VERSION +1 -1
  23. package/uscha-kit/WORKBENCH.md +19 -5
  24. package/uscha-kit/hooks/block-approved-writes.py +25 -0
  25. package/uscha-kit/install-uscha.py +534 -267
  26. package/uscha-kit/skills/uscha-adr-refine/SKILL.md +2 -2
  27. package/uscha-kit/skills/uscha-devloop/SKILL.md +3 -1
  28. package/uscha-kit/skills/uscha-devloop/qa_ledger.py +729 -155
  29. package/uscha-kit/skills/uscha-mirador/SKILL.md +22 -7
  30. package/uscha-kit/skills/uscha-mirador/mirador-render.py +44 -5
  31. package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +1 -1
  32. package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +1 -1
  33. package/uscha-kit/skills/uscha-mirador/mirador.template.html +666 -586
  34. package/uscha-kit/templates/CONSTITUTION.md +4 -4
  35. package/uscha-kit/templates/docs/adr/README.md +19 -19
  36. package/uscha-kit/tests/ledger-integrity-regressions.py +136 -0
  37. package/uscha-kit/tests/smoke-engine.sh +1312 -29
  38. package/uscha-kit/uscha.config.json +1 -1
  39. package/uscha-kit/workbench-doctor.sh +47 -3
@@ -6,7 +6,7 @@ whatever trade-off wins. An ADR *chooses* between alternatives; the CONSTITUTION
6
6
  > Truth hierarchy: **SPEC** = what must happen · **ADR** = why this shape ·
7
7
  > **CONSTITUTION** = what is never acceptable.
8
8
 
9
- Versioned, one per project. `/discovery`, `/adr-refine` and `/dev-loop` read it **before**
9
+ Versioned, one per project. `/uscha-discovery`, `/uscha-adr-refine` and `/uscha-devloop` read it **before**
10
10
  proposing or touching anything. A violation is a **BLOCKER** finding (non-negotiable): the agent
11
11
  MUST record it — `qa_ledger.py flag-blocker --kind constitution` — and once recorded it
12
12
  blocks convergence and caps readiness ≤65 until `--resolve` (a human decision). The engine
@@ -100,7 +100,7 @@ enforcing the record is the engine's job. It is never resolved by "working aroun
100
100
 
101
101
  ## Anti-ceremony — Lean over the method itself (meta-invariant)
102
102
 
103
- > The risk is not a bad gate: it is the **sum** of good gates turning `/dev-loop` into an
103
+ > The risk is not a bad gate: it is the **sum** of good gates turning `/uscha-devloop` into an
104
104
  > audit. That is *over-processing* — the waste of ceremony (Poppendieck ch. 4). It applies to the
105
105
  > tool, not the code: if a step does not add value **for the human**, it is waste.
106
106
  > It is a **meta-invariant** — the criterion that EVERY future gate must pass before entering.
@@ -114,9 +114,9 @@ enforcing the record is the engine's job. It is never resolved by "working aroun
114
114
 
115
115
  ## How it is enforced
116
116
 
117
- - `/discovery` and `/adr-refine` read it and derive the **severity gate** from here (the
117
+ - `/uscha-discovery` and `/uscha-adr-refine` read it and derive the **severity gate** from here (the
118
118
  "inviolable constraints" step). Each invariant carries, where it maps, a CWE reference.
119
- - `/dev-loop` consults it before touching a governed area; a violation is recorded with
119
+ - `/uscha-devloop` consults it before touching a governed area; a violation is recorded with
120
120
  `qa_ledger.py flag-blocker --kind constitution --note "<invariant>"` and enters the ledger
121
121
  as a **BLOCKER** finding (readiness cap ≤ 65, blocks convergence until `--resolve`).
122
122
  Detecting it is the agent/human's obligation; once recorded, enforcement is the engine's.
@@ -1,19 +1,19 @@
1
- # Architecture Decision Records
2
-
3
- Durable technical decisions of this repo. One per file:
4
- `ADR-NNN-<slug>.md`. They are written by `/discovery` or `/adr-refine`, or proposed during the
5
- build when a real decision appears (see the ADR rules in `CLAUDE.md`).
6
-
7
- Format: Status (proposed/accepted/experiment/deprecated/superseded) · Context · Alternatives ·
8
- Decision · Consequences · Implementation Plan (affected paths, patterns, tests) ·
9
- Verification (checkboxes).
10
-
11
- `Status: Experiment` is for a bounded, reversible hypothesis that needs real feedback.
12
- It must include: Hypothesis, Feedback Signal, Review By or Review Trigger, Promote
13
- Criteria, and Rollback / Supersede Criteria. Missing/expired metadata is advisory in
14
- Mirador/dashboard; it is not a readiness score.
15
-
16
- ## Index
17
-
18
- <!-- add one line per ADR -->
19
- - _(no ADRs yet)_
1
+ # Architecture Decision Records
2
+
3
+ Durable technical decisions of this repo. One per file:
4
+ `ADR-NNN-<slug>.md`. They are written by `/uscha-discovery` or `/uscha-adr-refine`, or proposed during the
5
+ build when a real decision appears (see the ADR rules in `CLAUDE.md`).
6
+
7
+ Format: Status (proposed/accepted/experiment/deprecated/superseded) · Context · Alternatives ·
8
+ Decision · Consequences · Implementation Plan (affected paths, patterns, tests) ·
9
+ Verification (checkboxes).
10
+
11
+ `Status: Experiment` is for a bounded, reversible hypothesis that needs real feedback.
12
+ It must include: Hypothesis, Feedback Signal, Review By or Review Trigger, Promote
13
+ Criteria, and Rollback / Supersede Criteria. Missing/expired metadata is advisory in
14
+ Mirador/dashboard; it is not a readiness score.
15
+
16
+ ## Index
17
+
18
+ <!-- add one line per ADR -->
19
+ - _(no ADRs yet)_
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env python3
2
+ """Focused public-CLI regressions for P1 ledger integrity."""
3
+
4
+ import json
5
+ import pathlib
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+
10
+
11
+ QL = pathlib.Path(sys.argv[1]).resolve()
12
+
13
+
14
+ def run(root, *args):
15
+ return subprocess.run(
16
+ [sys.executable, str(QL), *args], cwd=root, text=True,
17
+ encoding="utf-8", stdout=subprocess.PIPE, stderr=subprocess.PIPE)
18
+
19
+
20
+ def config(defaults=None, integration=False):
21
+ return {
22
+ "defaults": defaults or {},
23
+ "repos": [{"name": "app", "path": "app", "type": "python"}],
24
+ "integration": {"enabled": integration},
25
+ }
26
+
27
+
28
+ def assert_rejected_before_ledger(root, label, payload):
29
+ cfg = root / (label + ".json")
30
+ ledger = root / (label + ".ledger.json")
31
+ cfg.write_text(payload if isinstance(payload, str) else json.dumps(payload),
32
+ encoding="utf-8")
33
+ result = run(root, "init", "--config", str(cfg), "--out", str(ledger))
34
+ assert result.returncode != 0, (label, result.stdout, result.stderr)
35
+ assert not ledger.exists(), (label, "invalid config created a ledger")
36
+
37
+
38
+ def assert_init_accepts(root, label, payload):
39
+ cfg = root / (label + ".json")
40
+ ledger = root / (label + ".ledger.json")
41
+ cfg.write_text(json.dumps(payload), encoding="utf-8")
42
+ result = run(root, "init", "--config", str(cfg), "--out", str(ledger))
43
+ assert result.returncode == 0, (label, result.stdout, result.stderr)
44
+ assert ledger.exists(), (label, "valid config did not create a ledger")
45
+ result = run(root, "readiness", "--ledger", str(ledger), "--json")
46
+ assert result.returncode == 0, (label, result.stdout, result.stderr)
47
+
48
+
49
+ def assert_second_resolve_preserves_bytes(root, command, create_args, resolve_args):
50
+ ledger = root / (command + ".ledger.json")
51
+ cfg = root / (command + ".json")
52
+ cfg.write_text(json.dumps(config()), encoding="utf-8")
53
+ assert run(root, "init", "--config", str(cfg), "--out", str(ledger)).returncode == 0
54
+ assert run(root, *create_args, "--ledger", str(ledger)).returncode == 0
55
+ assert run(root, *resolve_args, "--ledger", str(ledger)).returncode == 0
56
+ before = ledger.read_bytes()
57
+ result = run(root, *resolve_args, "--ledger", str(ledger))
58
+ assert result.returncode != 0, (command, result.stdout, result.stderr)
59
+ assert ledger.read_bytes() == before, (command, "second resolve mutated ledger")
60
+
61
+
62
+ with tempfile.TemporaryDirectory(prefix=".uscha-ledger-integrity-", dir=pathlib.Path.cwd()) as tmp:
63
+ root = pathlib.Path(tmp)
64
+ (root / "app").mkdir()
65
+
66
+ bad = [
67
+ ("weights-must-map", config({"readiness_weights": []})),
68
+ ("weights-reject-unknown-key", config({"readiness_weights": {"typo": 1}})),
69
+ ("weights-reject-bool", config({"readiness_weights": {"coverage": True}})),
70
+ ("weights-reject-negative", config({"readiness_weights": {"coverage": -1}})),
71
+ ("weights-reject-nan", '{"defaults":{"readiness_weights":{"coverage":NaN}},"repos":[],"integration":{"enabled":false}}'),
72
+ ("caps-must-map", config({"readiness_caps": []})),
73
+ ("caps-reject-unknown-key", config({"readiness_caps": {"typo": 1}})),
74
+ ("caps-reject-out-of-range", config({"readiness_caps": {"tests_red": 101}})),
75
+ ("caps-reject-infinite", '{"defaults":{"readiness_caps":{"tests_red":Infinity}},"repos":[],"integration":{"enabled":false}}'),
76
+ ("static-zero-rejects-zero", config({"static_gate_zero_at": 0})),
77
+ ("static-zero-rejects-bool", config({"static_gate_zero_at": False})),
78
+ ("static-zero-rejects-nan", '{"defaults":{"static_gate_zero_at":NaN},"repos":[],"integration":{"enabled":false}}'),
79
+ ("severity-gate-must-list", config({"severity_gate": "HIGH"})),
80
+ ("severity-gate-rejects-unknown", config({"severity_gate": ["TYPO"]})),
81
+ ("integration-must-map", {"defaults": {}, "repos": [], "integration": []}),
82
+ ("integration-enabled-must-bool", {"defaults": {}, "repos": [], "integration": {"enabled": "yes"}}),
83
+ ("weights-all-zero-integration-disabled", config({"readiness_weights": {k: 0 for k in ("acceptance", "adr", "coverage", "static_gate", "convergence", "integration")}}, False)),
84
+ ("weights-only-integration-enabled", config({"readiness_weights": {"acceptance": 0, "adr": 0, "coverage": 0, "static_gate": 0, "convergence": 0}}, True)),
85
+ ]
86
+ for label, payload in bad:
87
+ assert_rejected_before_ledger(root, label, payload)
88
+
89
+ assert_init_accepts(root, "partial-readiness-overrides", config({
90
+ "readiness_weights": {"coverage": 20},
91
+ "readiness_caps": {"tests_red": 50},
92
+ "static_gate_zero_at": 5,
93
+ "severity_gate": ["HIGH", "CRITICAL", "BLOCKER"],
94
+ }))
95
+ print("ledger integrity: readiness config validation")
96
+
97
+ assert_second_resolve_preserves_bytes(
98
+ root, "production-finding",
99
+ ("production-finding", "--repo", "app", "--title", "prod defect", "--evidence", "log:1"),
100
+ ("production-finding", "--id", "PF-001", "--resolve", "--note", "triaged"),
101
+ )
102
+ assert_second_resolve_preserves_bytes(
103
+ root, "spec-doubt",
104
+ ("spec-doubt", "--repo", "app", "--note", "contract mismatch"),
105
+ ("spec-doubt", "--id", "SD-001", "--resolve", "--decision", "SPEC amended"),
106
+ )
107
+
108
+ ledger = root / "spec-change-request.ledger.json"
109
+ cfg = root / "spec-change-request.json"
110
+ cfg.write_text(json.dumps(config()), encoding="utf-8")
111
+ assert run(root, "init", "--config", str(cfg), "--out", str(ledger)).returncode == 0
112
+ assert run(root, "production-finding", "--ledger", str(ledger), "--repo", "app", "--title", "source", "--evidence", "log:2").returncode == 0
113
+ assert run(root, "spec-change-request", "--ledger", str(ledger), "--repo", "app", "--source", "PF-001", "--requested-change", "amend", "--evidence", "log:3").returncode == 0
114
+ resolve_scr = ("spec-change-request", "--id", "SCR-001", "--resolve", "--decision", "accepted", "--amended", "SPEC.md")
115
+ assert run(root, *resolve_scr, "--ledger", str(ledger)).returncode == 0
116
+ before = ledger.read_bytes()
117
+ result = run(root, *resolve_scr, "--ledger", str(ledger))
118
+ assert result.returncode != 0, (result.stdout, result.stderr)
119
+ assert ledger.read_bytes() == before, "spec-change-request second resolve mutated ledger"
120
+ print("ledger integrity: second resolve preserves bytes for PF/SD/SCR")
121
+
122
+ legacy = root / "legacy.ledger.json"
123
+ cfg = root / "legacy.json"
124
+ cfg.write_text(json.dumps(config()), encoding="utf-8")
125
+ assert run(root, "init", "--config", str(cfg), "--out", str(legacy)).returncode == 0
126
+ assert run(root, "production-finding", "--ledger", str(legacy), "--repo", "app", "--title", "legacy", "--evidence", "log:4").returncode == 0
127
+ data = json.loads(legacy.read_text(encoding="utf-8"))
128
+ del data["production_findings"][0]["status"]
129
+ data.pop("integrity", None) # deliberate legacy fixture mutation: accept it explicitly
130
+ legacy.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
131
+ before = legacy.read_bytes()
132
+ result = run(root, "production-finding", "--ledger", str(legacy), "--id", "PF-001", "--resolve", "--note", "triaged")
133
+ assert result.returncode != 0, (result.stdout, result.stderr)
134
+ assert "fail-closed" in (result.stdout + result.stderr), (result.stdout, result.stderr)
135
+ assert legacy.read_bytes() == before, "legacy missing status resolve mutated ledger"
136
+ print("ledger integrity: legacy rows without status fail closed")