@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.
- package/README.md +6 -6
- package/bin/uscha.js +19 -7
- package/package.json +1 -1
- package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +2 -2
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +3 -1
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +729 -155
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +22 -7
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +44 -5
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +1 -1
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +1 -1
- package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +666 -586
- package/uscha-kit/.claude-plugin/plugin.json +1 -1
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/CHANGELOG-1.41.0.md +18 -0
- package/uscha-kit/CHANGELOG-1.41.1.md +53 -0
- package/uscha-kit/CHANGELOG-1.41.2.md +34 -0
- package/uscha-kit/CHANGELOG-1.41.3.md +30 -0
- package/uscha-kit/CHANGELOG-1.42.0.md +41 -0
- package/uscha-kit/CHANGELOG-1.43.0.md +37 -0
- package/uscha-kit/INSTALL.md +120 -101
- package/uscha-kit/README.md +24 -13
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/WORKBENCH.md +19 -5
- package/uscha-kit/hooks/block-approved-writes.py +25 -0
- package/uscha-kit/install-uscha.py +534 -267
- package/uscha-kit/skills/uscha-adr-refine/SKILL.md +2 -2
- package/uscha-kit/skills/uscha-devloop/SKILL.md +3 -1
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +729 -155
- package/uscha-kit/skills/uscha-mirador/SKILL.md +22 -7
- package/uscha-kit/skills/uscha-mirador/mirador-render.py +44 -5
- package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +1 -1
- package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +1 -1
- package/uscha-kit/skills/uscha-mirador/mirador.template.html +666 -586
- package/uscha-kit/templates/CONSTITUTION.md +4 -4
- package/uscha-kit/templates/docs/adr/README.md +19 -19
- package/uscha-kit/tests/ledger-integrity-regressions.py +136 -0
- package/uscha-kit/tests/smoke-engine.sh +1312 -29
- package/uscha-kit/uscha.config.json +1 -1
- 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 `/
|
|
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 `/
|
|
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
|
-
- `/
|
|
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")
|