@andresmassello/uscha 1.56.1 → 1.60.1
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 +3 -3
- package/bin/uscha.js +0 -0
- package/package.json +2 -1
- package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +18 -0
- package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +21 -0
- package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +636 -0
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +11 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +22 -22
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +0 -0
- package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +39 -1
- package/uscha-kit/.claude/skills/uscha-status/SKILL.md +8 -0
- package/uscha-kit/.claude-plugin/plugin.json +2 -2
- package/uscha-kit/.codex-plugin/plugin.json +1 -1
- package/uscha-kit/INSTALL.md +128 -128
- package/uscha-kit/README.md +90 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/hooks/block-approved-writes.py +0 -0
- package/uscha-kit/reports/junit/.fastpath-cases.json +1 -0
- package/uscha-kit/reports/junit/.goldencov-cases.json +1 -0
- package/uscha-kit/reports/junit/.specdrift-cases.json +1 -0
- package/uscha-kit/skills/uscha-characterize/SKILL.md +18 -0
- package/uscha-kit/skills/uscha-devloop/SKILL.md +21 -0
- package/uscha-kit/skills/uscha-devloop/qa_ledger.py +636 -0
- package/uscha-kit/skills/uscha-mirador/SKILL.md +11 -0
- package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +22 -22
- package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +0 -0
- package/uscha-kit/skills/uscha-mirador/mirador.template.html +39 -1
- package/uscha-kit/skills/uscha-status/SKILL.md +8 -0
- package/uscha-kit/uscha.config.json +16 -1
- package/uscha-kit/workbench-doctor.sh +0 -0
|
@@ -67,6 +67,17 @@ than inventing a step. Keep the CONTENT in the conversation's language and the l
|
|
|
67
67
|
`review_trigger`, `experiment_valid`, `experiment_missing`, and `expired`; top-level
|
|
68
68
|
`adr_experiments` summarizes open/malformed/expired experiments. This is advisory
|
|
69
69
|
visibility for measured hypotheses, not readiness scoring.
|
|
70
|
+
- **Fast-path (ADR-003):** `dashboard --json` carries `fast_path` — the latest verdict per
|
|
71
|
+
repo straight from the ledger, or null when none was requested. The template degrades when
|
|
72
|
+
absent, like every other field.
|
|
73
|
+
- **Spec-drift (ADR-005):** `dashboard --json` carries `spec_drift` — the latest advisory
|
|
74
|
+
run (per-document verdicts: SPEC_STALE / CLEAN / UNMAPPED / UNTRACKED) — only when a run
|
|
75
|
+
exists in the ledger; a virgin ledger keeps the exact prior schema. Advisory visibility of
|
|
76
|
+
the spec-maintenance tax, never readiness input.
|
|
77
|
+
- **Modes card:** the template draws one card for both modes — fast-path verdict chips per
|
|
78
|
+
repo (ALLOW green / ESCALATED amber / DENY red) and spec-drift rows per document, labeled
|
|
79
|
+
advisory. The card is hidden entirely when neither key exists (absent block = identical
|
|
80
|
+
view, same rule as the JSON).
|
|
70
81
|
- **Session telemetry (optional, vendor-reported):** if `.uscha/telemetry.jsonl` exists,
|
|
71
82
|
the skill aggregates it and MERGES a `telemetry` object into `DATA`. This is the ONE
|
|
72
83
|
panel that is **narrated by the vendor (Claude Code), not measured by the engine** —
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# mirador-watch.ps1 -- live second-screen mirador (uscha-kit 1.34.0), Windows.
|
|
2
|
-
# Regenerates mirador.html every N seconds from the current ledger; the page is rendered
|
|
3
|
-
# with a meta-refresh at the same interval, so a browser open on it updates on its own.
|
|
4
|
-
#
|
|
5
|
-
# Usage (run in a spare terminal, from the project root):
|
|
6
|
-
# powershell -NoProfile -File <kit>\.claude\skills\uscha-mirador\mirador-watch.ps1 [-Interval 30]
|
|
7
|
-
# then open mirador.html in a browser on your second screen. Ctrl-C to stop.
|
|
8
|
-
#
|
|
9
|
-
# Overridable via env: ENGINE, LEDGER, TEMPLATE, OUT, PYTHON.
|
|
10
|
-
param([int]$Interval = 30)
|
|
11
|
-
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
12
|
-
$engine = if ($env:ENGINE) { $env:ENGINE } else { Join-Path $here "..\uscha-devloop\qa_ledger.py" }
|
|
13
|
-
$ledger = if ($env:LEDGER) { $env:LEDGER } else { "QA-LEDGER.json" }
|
|
14
|
-
$template = if ($env:TEMPLATE) { $env:TEMPLATE } else { Join-Path $here "mirador.template.html" }
|
|
15
|
-
$out = if ($env:OUT) { $env:OUT } else { "mirador.html" }
|
|
16
|
-
$py = if ($env:PYTHON) { $env:PYTHON } else { "python" }
|
|
17
|
-
Write-Host "mirador-watch: regenerating $out every ${Interval}s (Ctrl-C to stop). Open $out in a browser."
|
|
18
|
-
while ($true) {
|
|
19
|
-
& $py (Join-Path $here "mirador-render.py") --engine $engine --ledger $ledger --template $template --out $out --refresh $Interval --no-open
|
|
20
|
-
if ($LASTEXITCODE -ne 0) { Write-Host "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying" }
|
|
21
|
-
Start-Sleep -Seconds $Interval
|
|
22
|
-
}
|
|
1
|
+
# mirador-watch.ps1 -- live second-screen mirador (uscha-kit 1.34.0), Windows.
|
|
2
|
+
# Regenerates mirador.html every N seconds from the current ledger; the page is rendered
|
|
3
|
+
# with a meta-refresh at the same interval, so a browser open on it updates on its own.
|
|
4
|
+
#
|
|
5
|
+
# Usage (run in a spare terminal, from the project root):
|
|
6
|
+
# powershell -NoProfile -File <kit>\.claude\skills\uscha-mirador\mirador-watch.ps1 [-Interval 30]
|
|
7
|
+
# then open mirador.html in a browser on your second screen. Ctrl-C to stop.
|
|
8
|
+
#
|
|
9
|
+
# Overridable via env: ENGINE, LEDGER, TEMPLATE, OUT, PYTHON.
|
|
10
|
+
param([int]$Interval = 30)
|
|
11
|
+
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
12
|
+
$engine = if ($env:ENGINE) { $env:ENGINE } else { Join-Path $here "..\uscha-devloop\qa_ledger.py" }
|
|
13
|
+
$ledger = if ($env:LEDGER) { $env:LEDGER } else { "QA-LEDGER.json" }
|
|
14
|
+
$template = if ($env:TEMPLATE) { $env:TEMPLATE } else { Join-Path $here "mirador.template.html" }
|
|
15
|
+
$out = if ($env:OUT) { $env:OUT } else { "mirador.html" }
|
|
16
|
+
$py = if ($env:PYTHON) { $env:PYTHON } else { "python" }
|
|
17
|
+
Write-Host "mirador-watch: regenerating $out every ${Interval}s (Ctrl-C to stop). Open $out in a browser."
|
|
18
|
+
while ($true) {
|
|
19
|
+
& $py (Join-Path $here "mirador-render.py") --engine $engine --ledger $ledger --template $template --out $out --refresh $Interval --no-open
|
|
20
|
+
if ($LASTEXITCODE -ne 0) { Write-Host "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying" }
|
|
21
|
+
Start-Sleep -Seconds $Interval
|
|
22
|
+
}
|
|
File without changes
|
|
@@ -278,6 +278,11 @@ svg{display:block;width:100%;height:auto}
|
|
|
278
278
|
The verdict above is all you need to look at. These roll up into it; they're here if you want to dig.
|
|
279
279
|
</p>
|
|
280
280
|
</div>
|
|
281
|
+
|
|
282
|
+
<div class="card col6" id="card-modes" hidden>
|
|
283
|
+
<h2>Fast-path · Spec-drift <span class="n" id="modes-n">—</span></h2>
|
|
284
|
+
<div class="speclist" id="modes"></div>
|
|
285
|
+
</div>
|
|
281
286
|
</section>
|
|
282
287
|
|
|
283
288
|
<section class="telemetry" id="telemetry" hidden>
|
|
@@ -654,6 +659,39 @@ function renderTelemetry(){
|
|
|
654
659
|
if(sec)sec.hidden=false;
|
|
655
660
|
}
|
|
656
661
|
|
|
662
|
+
function renderModes(){
|
|
663
|
+
/* fast_path: latest measured verdict per repo (ADR-003). spec_drift: latest advisory
|
|
664
|
+
run (ADR-005) -- advisory means it never feeds readiness; this card only makes the
|
|
665
|
+
ledger fact visible. BOTH keys are conditional in dashboard --json, so a project
|
|
666
|
+
that never used either sees no card at all (absent block = identical view). */
|
|
667
|
+
const card=$("#card-modes");if(!card)return;
|
|
668
|
+
const fp=DATA.fast_path,sd=DATA.spec_drift;
|
|
669
|
+
if(!fp&&!sd){card.hidden=true;return;}
|
|
670
|
+
const host=$("#modes");host.replaceChildren();
|
|
671
|
+
const parts=[];
|
|
672
|
+
if(fp){
|
|
673
|
+
const cls={ALLOW:"st-done",ESCALATED:"st-prog",DENY:"st-block"};
|
|
674
|
+
Object.keys(fp).forEach(r=>{const e=fp[r]||{};
|
|
675
|
+
append(host,append(node("div","spec"),node("span","id","fast-path"),
|
|
676
|
+
node("span","t",r+(e.intent?(" — "+e.intent):"")),
|
|
677
|
+
node("span","chip "+(cls[e.verdict]||"st-todo"),String(e.verdict||"?"))));});
|
|
678
|
+
parts.push(Object.keys(fp).length+" repo"+(Object.keys(fp).length>1?"s":""));
|
|
679
|
+
}
|
|
680
|
+
if(sd){
|
|
681
|
+
const cls={SPEC_STALE:"st-block",CLEAN:"st-done",UNMAPPED:"st-todo",UNTRACKED:"st-todo"};
|
|
682
|
+
const res=sd.results||[];
|
|
683
|
+
res.forEach(d=>{const extra=d.verdict==="SPEC_STALE"
|
|
684
|
+
?(" — "+(d.newer_files_total||0)+" file(s) newer than the spec"):"";
|
|
685
|
+
append(host,append(node("div","spec"),node("span","id","spec-drift"),
|
|
686
|
+
node("span","t",String(d.file||"")+extra),
|
|
687
|
+
node("span","chip "+(cls[d.verdict]||"st-todo"),String(d.verdict||"?"))));});
|
|
688
|
+
const stale=res.filter(d=>d.verdict==="SPEC_STALE").length;
|
|
689
|
+
parts.push(stale?stale+" stale spec"+(stale>1?"s":""):"no drift measured");
|
|
690
|
+
host.appendChild(emptyHint("spec-drift is ADVISORY (ADR-005): it prompts a human look, it never gates."));
|
|
691
|
+
}
|
|
692
|
+
$("#modes-n").textContent=parts.join(" · ");
|
|
693
|
+
card.hidden=false;
|
|
694
|
+
}
|
|
657
695
|
/* ==== status story: cómo viene / qué lo traba / qué sigue — all from measured DATA ==== */
|
|
658
696
|
function renderStatus(){
|
|
659
697
|
const sec=$("#status");if(!sec)return;
|
|
@@ -741,7 +779,7 @@ function applySnapshot(i){
|
|
|
741
779
|
scrub.addEventListener("input",()=>applySnapshot(+scrub.value));
|
|
742
780
|
|
|
743
781
|
/* ==== init ==== */
|
|
744
|
-
renderStatus();renderAcceptance();renderAdrs();renderInv();renderHeat();renderLoops();renderSubs();renderExecutionPolicy();renderTelemetry();
|
|
782
|
+
renderStatus();renderModes();renderAcceptance();renderAdrs();renderInv();renderHeat();renderLoops();renderSubs();renderExecutionPolicy();renderTelemetry();
|
|
745
783
|
$("#asof").textContent=snaps.length?snaps[snaps.length-1].date:"no history yet (readiness --record)";
|
|
746
784
|
renderTrail(null);
|
|
747
785
|
</script>
|
|
@@ -86,6 +86,14 @@ Line guide:
|
|
|
86
86
|
WHEN the evidence was captured — facts do not move without new evidence. If the
|
|
87
87
|
field is absent (nothing recorded yet), omit the line; never guess a timestamp.
|
|
88
88
|
|
|
89
|
+
**Fast-path mode (ADR-003):** if the ledger carries `fast_path` entries, add ONE line to the
|
|
90
|
+
block with the latest verdict per repo (`fast-path: ALLOW (intent...)` / `ESCALATED`). Absent
|
|
91
|
+
entries → no line at all: silence is honest when no mode was requested.
|
|
92
|
+
|
|
93
|
+
**Spec-drift (ADR-005):** if the ledger carries a `spec_drift` run, add ONE line:
|
|
94
|
+
`spec-drift: N stale / M docs (advisory)` — or `spec-drift: no drift measured` when zero are
|
|
95
|
+
stale. Always label it advisory; it never explains a blocked phase. Absent key → no line.
|
|
96
|
+
|
|
89
97
|
## Degradation (honest, specific)
|
|
90
98
|
|
|
91
99
|
- `measured` missing entirely → print: *"No measurement recorded yet — the trail
|
|
@@ -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.60.1",
|
|
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, 32 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/INSTALL.md
CHANGED
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
# Install Uscha
|
|
2
|
-
|
|
3
|
-
Uscha installs as a machine-level helper for coding agents. The recommended path
|
|
4
|
-
is npm/npx because it works the same on a fresh Codex or Claude Code machine.
|
|
5
|
-
|
|
6
|
-
The method itself — the paradigm, the five rules, the skills and the library — lives at
|
|
7
|
-
**[uscha.dev](https://uscha.dev)**.
|
|
8
|
-
|
|
9
|
-
## Quick path
|
|
10
|
-
|
|
11
|
-
### Codex Desktop
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npx --yes @andresmassello/uscha@latest version
|
|
15
|
-
npx --yes @andresmassello/uscha@latest install --target codex --dry-run
|
|
16
|
-
npx --yes @andresmassello/uscha@latest install --target codex
|
|
17
|
-
npx --yes @andresmassello/uscha@latest doctor --target codex
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Then restart Codex or open a new thread. The installer registers Uscha as a
|
|
21
|
-
personal local plugin under `~/plugins/uscha` and updates
|
|
22
|
-
`~/.agents/plugins/marketplace.json`. It preflights that marketplace before replacing the plugin tree and writes the install marker last.
|
|
23
|
-
|
|
24
|
-
### Claude Code
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
npx --yes @andresmassello/uscha@latest install --target claude --dry-run
|
|
28
|
-
npx --yes @andresmassello/uscha@latest install --target claude
|
|
29
|
-
npx --yes @andresmassello/uscha@latest doctor --target claude
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
This installs the `uscha-*` skills and registers a portable Python `PreToolUse` hook under `~/.claude` while preserving unrelated `settings.json` entries. Restart or reload Claude Code after installing.
|
|
33
|
-
|
|
34
|
-
### Same machine uses both
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npx --yes @andresmassello/uscha@latest install --target both --dry-run
|
|
38
|
-
npx --yes @andresmassello/uscha@latest install --target both
|
|
39
|
-
npx --yes @andresmassello/uscha@latest doctor --target both
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Prepare a project repo
|
|
43
|
-
|
|
44
|
-
After the machine install, initialize each project where Uscha should govern the
|
|
45
|
-
workflow:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npx --yes @andresmassello/uscha@latest init --repo . --dry-run
|
|
49
|
-
npx --yes @andresmassello/uscha@latest init --repo .
|
|
50
|
-
# Existing differing files are preserved; use --force only to replace them deliberately.
|
|
51
|
-
npx --yes @andresmassello/uscha@latest init --repo . --force
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
`init` exits nonzero and reports conflicts for differing `uscha.config.json`, `CLAUDE.md`, `CONSTITUTION.md`, or `.gitattributes`; `--dry-run` performs the same conflict check without writing.
|
|
55
|
-
|
|
56
|
-
`init` also installs the **progress statusline** (kit 1.46.0): it copies
|
|
57
|
-
`.claude/scripts/uscha_{statusline,progress}.py` and merges a `statusLine` + a `Stop` hook into
|
|
58
|
-
`.claude/settings.json` (never clobbering an existing `statusLine`). Add a `label`, `roadmap`
|
|
59
|
-
and `build_priority` to your `repos[]` entry to drive it; with no data it stays hidden.
|
|
60
|
-
|
|
61
|
-
Project state stays in the project: `uscha.config.json`, `QA-LEDGER.json`,
|
|
62
|
-
`ACCEPTANCE.md`, and approved golden fixtures when used.
|
|
63
|
-
|
|
64
|
-
## See the dashboard (mirador)
|
|
65
|
-
|
|
66
|
-
From the root of any project that has a `QA-LEDGER.json`, one command renders the mirador and
|
|
67
|
-
opens it — no python, no paths:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
npx --yes @andresmassello/uscha@latest mirador # one glance: render + open
|
|
71
|
-
npx --yes @andresmassello/uscha@latest mirador --watch # live second-screen view (auto-refresh)
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
It defaults to the `QA-LEDGER.json` convention in the current directory (pass `--ledger` to
|
|
75
|
-
point elsewhere) and prints the absolute path it wrote. `--watch` re-renders every `--interval`
|
|
76
|
-
seconds (default 30) into one self-reloading tab.
|
|
77
|
-
|
|
78
|
-
## Requirements
|
|
79
|
-
|
|
80
|
-
| Requirement | Why |
|
|
81
|
-
|-------------|-----|
|
|
82
|
-
| Node.js + npm | Runs the universal `npx` entrypoint. |
|
|
83
|
-
| Python 3.8+ | Runs the canonical stdlib installer and engine. |
|
|
84
|
-
| Git | Used by the method and by project setup checks. |
|
|
85
|
-
| Codex Desktop and/or Claude Code | The agent runtime you want to install Uscha into. |
|
|
86
|
-
|
|
87
|
-
No `pip install` is required. The engine is Python stdlib-only.
|
|
88
|
-
|
|
89
|
-
## Other install options
|
|
90
|
-
|
|
91
|
-
| Option | Use when | Tradeoff |
|
|
92
|
-
|--------|----------|----------|
|
|
93
|
-
| `npx @andresmassello/uscha@latest ...` | Normal install/update on any machine. | Requires npm registry access. |
|
|
94
|
-
| Git checkout + `python uscha-kit/install-uscha.py ...` | Developing Uscha itself or testing unreleased changes. | You must clone/pull the repo yourself. |
|
|
95
|
-
| `--mode link` from a checkout | This machine develops the kit and installed skills should follow local edits. | Links are great for development, risky for normal users. |
|
|
96
|
-
| Claude Code plugin commands | You specifically want Claude Code's native plugin flow. | Codex still needs the npm/git installer path. |
|
|
97
|
-
| Manual copy | Debugging the installer. | Easy to drift; not recommended for adoption. |
|
|
98
|
-
|
|
99
|
-
Development checkout example:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
git clone https://github.com/andresmassello/uscha.git
|
|
103
|
-
cd uscha
|
|
104
|
-
python uscha-kit/install-uscha.py install --target both --mode link --dry-run
|
|
105
|
-
python uscha-kit/install-uscha.py install --target both --mode link
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
Claude Code plugin option:
|
|
109
|
-
|
|
110
|
-
```text
|
|
111
|
-
/plugin marketplace add andresmassello/uscha
|
|
112
|
-
/plugin install uscha@uscha
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Update and verify
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
npm view @andresmassello/uscha version
|
|
119
|
-
npx --yes @andresmassello/uscha@latest version
|
|
120
|
-
npx --yes @andresmassello/uscha@latest install --target both
|
|
121
|
-
npx --yes @andresmassello/uscha@latest doctor --target both
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
`doctor` exits 1 for any unhealthy target in either text or `--json` mode. It checks installed skill presence, manifest/marketplace or hook registration, marker, and version; it does not measure file-content integrity.
|
|
125
|
-
|
|
126
|
-
If `npm view` returns `404` immediately after a new release, wait a few minutes:
|
|
127
|
-
npm search/dist-tags can propagate before the package metadata endpoint used by
|
|
128
|
-
`npx`. Do not republish the same version while propagation is in progress.
|
|
1
|
+
# Install Uscha
|
|
2
|
+
|
|
3
|
+
Uscha installs as a machine-level helper for coding agents. The recommended path
|
|
4
|
+
is npm/npx because it works the same on a fresh Codex or Claude Code machine.
|
|
5
|
+
|
|
6
|
+
The method itself — the paradigm, the five rules, the skills and the library — lives at
|
|
7
|
+
**[uscha.dev](https://uscha.dev)**.
|
|
8
|
+
|
|
9
|
+
## Quick path
|
|
10
|
+
|
|
11
|
+
### Codex Desktop
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx --yes @andresmassello/uscha@latest version
|
|
15
|
+
npx --yes @andresmassello/uscha@latest install --target codex --dry-run
|
|
16
|
+
npx --yes @andresmassello/uscha@latest install --target codex
|
|
17
|
+
npx --yes @andresmassello/uscha@latest doctor --target codex
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then restart Codex or open a new thread. The installer registers Uscha as a
|
|
21
|
+
personal local plugin under `~/plugins/uscha` and updates
|
|
22
|
+
`~/.agents/plugins/marketplace.json`. It preflights that marketplace before replacing the plugin tree and writes the install marker last.
|
|
23
|
+
|
|
24
|
+
### Claude Code
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx --yes @andresmassello/uscha@latest install --target claude --dry-run
|
|
28
|
+
npx --yes @andresmassello/uscha@latest install --target claude
|
|
29
|
+
npx --yes @andresmassello/uscha@latest doctor --target claude
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This installs the `uscha-*` skills and registers a portable Python `PreToolUse` hook under `~/.claude` while preserving unrelated `settings.json` entries. Restart or reload Claude Code after installing.
|
|
33
|
+
|
|
34
|
+
### Same machine uses both
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx --yes @andresmassello/uscha@latest install --target both --dry-run
|
|
38
|
+
npx --yes @andresmassello/uscha@latest install --target both
|
|
39
|
+
npx --yes @andresmassello/uscha@latest doctor --target both
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Prepare a project repo
|
|
43
|
+
|
|
44
|
+
After the machine install, initialize each project where Uscha should govern the
|
|
45
|
+
workflow:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx --yes @andresmassello/uscha@latest init --repo . --dry-run
|
|
49
|
+
npx --yes @andresmassello/uscha@latest init --repo .
|
|
50
|
+
# Existing differing files are preserved; use --force only to replace them deliberately.
|
|
51
|
+
npx --yes @andresmassello/uscha@latest init --repo . --force
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`init` exits nonzero and reports conflicts for differing `uscha.config.json`, `CLAUDE.md`, `CONSTITUTION.md`, or `.gitattributes`; `--dry-run` performs the same conflict check without writing.
|
|
55
|
+
|
|
56
|
+
`init` also installs the **progress statusline** (kit 1.46.0): it copies
|
|
57
|
+
`.claude/scripts/uscha_{statusline,progress}.py` and merges a `statusLine` + a `Stop` hook into
|
|
58
|
+
`.claude/settings.json` (never clobbering an existing `statusLine`). Add a `label`, `roadmap`
|
|
59
|
+
and `build_priority` to your `repos[]` entry to drive it; with no data it stays hidden.
|
|
60
|
+
|
|
61
|
+
Project state stays in the project: `uscha.config.json`, `QA-LEDGER.json`,
|
|
62
|
+
`ACCEPTANCE.md`, and approved golden fixtures when used.
|
|
63
|
+
|
|
64
|
+
## See the dashboard (mirador)
|
|
65
|
+
|
|
66
|
+
From the root of any project that has a `QA-LEDGER.json`, one command renders the mirador and
|
|
67
|
+
opens it — no python, no paths:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx --yes @andresmassello/uscha@latest mirador # one glance: render + open
|
|
71
|
+
npx --yes @andresmassello/uscha@latest mirador --watch # live second-screen view (auto-refresh)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
It defaults to the `QA-LEDGER.json` convention in the current directory (pass `--ledger` to
|
|
75
|
+
point elsewhere) and prints the absolute path it wrote. `--watch` re-renders every `--interval`
|
|
76
|
+
seconds (default 30) into one self-reloading tab.
|
|
77
|
+
|
|
78
|
+
## Requirements
|
|
79
|
+
|
|
80
|
+
| Requirement | Why |
|
|
81
|
+
|-------------|-----|
|
|
82
|
+
| Node.js + npm | Runs the universal `npx` entrypoint. |
|
|
83
|
+
| Python 3.8+ | Runs the canonical stdlib installer and engine. |
|
|
84
|
+
| Git | Used by the method and by project setup checks. |
|
|
85
|
+
| Codex Desktop and/or Claude Code | The agent runtime you want to install Uscha into. |
|
|
86
|
+
|
|
87
|
+
No `pip install` is required. The engine is Python stdlib-only.
|
|
88
|
+
|
|
89
|
+
## Other install options
|
|
90
|
+
|
|
91
|
+
| Option | Use when | Tradeoff |
|
|
92
|
+
|--------|----------|----------|
|
|
93
|
+
| `npx @andresmassello/uscha@latest ...` | Normal install/update on any machine. | Requires npm registry access. |
|
|
94
|
+
| Git checkout + `python uscha-kit/install-uscha.py ...` | Developing Uscha itself or testing unreleased changes. | You must clone/pull the repo yourself. |
|
|
95
|
+
| `--mode link` from a checkout | This machine develops the kit and installed skills should follow local edits. | Links are great for development, risky for normal users. |
|
|
96
|
+
| Claude Code plugin commands | You specifically want Claude Code's native plugin flow. | Codex still needs the npm/git installer path. |
|
|
97
|
+
| Manual copy | Debugging the installer. | Easy to drift; not recommended for adoption. |
|
|
98
|
+
|
|
99
|
+
Development checkout example:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git clone https://github.com/andresmassello/uscha.git
|
|
103
|
+
cd uscha
|
|
104
|
+
python uscha-kit/install-uscha.py install --target both --mode link --dry-run
|
|
105
|
+
python uscha-kit/install-uscha.py install --target both --mode link
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Claude Code plugin option:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
/plugin marketplace add andresmassello/uscha
|
|
112
|
+
/plugin install uscha@uscha
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Update and verify
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm view @andresmassello/uscha version
|
|
119
|
+
npx --yes @andresmassello/uscha@latest version
|
|
120
|
+
npx --yes @andresmassello/uscha@latest install --target both
|
|
121
|
+
npx --yes @andresmassello/uscha@latest doctor --target both
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`doctor` exits 1 for any unhealthy target in either text or `--json` mode. It checks installed skill presence, manifest/marketplace or hook registration, marker, and version; it does not measure file-content integrity.
|
|
125
|
+
|
|
126
|
+
If `npm view` returns `404` immediately after a new release, wait a few minutes:
|
|
127
|
+
npm search/dist-tags can propagate before the package metadata endpoint used by
|
|
128
|
+
`npx`. Do not republish the same version while propagation is in progress.
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v1.
|
|
3
|
+
**Kit version:** v1.60.1 <!-- 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`,
|
|
@@ -38,6 +38,95 @@ uscha-kit/
|
|
|
38
38
|
└─ uscha-rubric/ # (optional) rubric grading — thin adapter; the core is agnostic
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Fast-path (ADR-003) — measured entry for trivial changes
|
|
42
|
+
|
|
43
|
+
A one-line fix should not pay the ceremony of a schema migration. `fastpath-eval` grants the
|
|
44
|
+
shortcut from **measured signals**, never from anyone's opinion of "small":
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python qa_ledger.py fastpath-eval --repo <name> --json # dry-run: verdict only
|
|
48
|
+
python qa_ledger.py fastpath-eval --repo <name> --intent "fix: x" # records it in the ledger
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Config (`defaults.fast_path`; absent block = feature off, behavior identical to earlier
|
|
52
|
+
releases): `max_files_changed` (3) and `max_loc_delta` (80) measured from
|
|
53
|
+
`git diff --numstat` against the merge-base with `origin/main` (fallback `main`) **plus
|
|
54
|
+
untracked files** — a new file is still a change; `protected_paths` globs deny regardless of
|
|
55
|
+
size; `require_asserting_test` caps readiness while a fast-path run has no measured test.
|
|
56
|
+
|
|
57
|
+
**Fail-closed:** no git, no resolvable base, no config → `DENY` with the reason named.
|
|
58
|
+
"Could not measure" never grants the shortcut. **Escalation:** re-running `fastpath-eval`
|
|
59
|
+
mid-run (the devloop does, before the PR step) with thresholds now exceeded flips the run to
|
|
60
|
+
`ESCALATED` through the standard escalation machinery — the derived phase blocks `pr-ready`
|
|
61
|
+
and readiness is capped until a human runs `resolve-escalation`, after producing the ADR +
|
|
62
|
+
ACCEPTANCE the change turned out to deserve. **The override is asymmetric (INV-RIGOR-02):**
|
|
63
|
+
you can always force the full path; nothing can force `ALLOW` over a measured `DENY`.
|
|
64
|
+
|
|
65
|
+
## Golden coverage (ADR-006) — the veto knows what a golden actually covers
|
|
66
|
+
|
|
67
|
+
A golden freezes behavior. Changing code a golden exercises is never a trivial change — but
|
|
68
|
+
until the engine knew WHICH source files a golden covers, that veto could not be measured, so
|
|
69
|
+
ADR-004 deferred it rather than ship a gate that pretended. `golden-coverage` builds the
|
|
70
|
+
mapping **by measurement**:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python qa_ledger.py golden-coverage --harness tests/golden/harness-x.py --golden tests/golden/x.approved.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It runs the harness under `coverage.py` and records the source files that actually executed
|
|
77
|
+
into `golden.coverage.json` at the repo root (same convention as `golden.scrub.json`). The
|
|
78
|
+
harnesses drive their subject through a **subprocess**, so instrumentation is injected into
|
|
79
|
+
every python they spawn — measuring only the parent would record nothing and produce an empty
|
|
80
|
+
map, which would read as "this golden covers nothing".
|
|
81
|
+
|
|
82
|
+
Then, **opt-in**, the fast-path grows a `golden_touched` signal:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
"fast_path": { "forbid_when_golden_touched": true }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Absent or `false` → the veto does not exist and behavior is identical to earlier releases.
|
|
89
|
+
Declared → **fail-closed**: touching a mapped file denies (naming the golden and the file), and
|
|
90
|
+
so does a missing manifest, because "could not measure" never grants the shortcut. A malformed
|
|
91
|
+
manifest exits 2 rather than degrading into a silent "no mapping". Every verdict carries the
|
|
92
|
+
capture commit and tool version in the signal's `source` — provenance, not a freshness gate:
|
|
93
|
+
an aged map's real risk is a false negative, which cannot be detected without re-capturing.
|
|
94
|
+
|
|
95
|
+
Two honest limits (ADR-006): `coverage.py` is Python-only, so a harness in another language
|
|
96
|
+
cannot produce a map — declaring the veto there yields a permanent `DENY`, and the remedy is
|
|
97
|
+
not to declare it. And **file** granularity over-fires on monolithic files: in a repo whose
|
|
98
|
+
engine is one large module, the veto fires on nearly every change to it. When it errs, it errs
|
|
99
|
+
toward more ceremony, never less.
|
|
100
|
+
|
|
101
|
+
## Spec-drift (ADR-005) — the spec maintenance tax, made visible
|
|
102
|
+
|
|
103
|
+
Specs rot silently: the code moves and `SPEC.md` stays where it was. `spec-drift` detects
|
|
104
|
+
that lag **mechanically** — and reports it as **advisory, never a gate**, because whether an
|
|
105
|
+
older spec still covers newer code is a relevance judgment, and a guess advises.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
python qa_ledger.py spec-drift --repo <name> --json # advisory report; exit 0 always
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Map specs to code with a `governs:` glob list in the frontmatter of `SPEC.md` and each
|
|
112
|
+
`docs/adr/*.md`:
|
|
113
|
+
|
|
114
|
+
```markdown
|
|
115
|
+
---
|
|
116
|
+
governs:
|
|
117
|
+
- src/payments/**
|
|
118
|
+
- db/**
|
|
119
|
+
---
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Per document: **`SPEC_STALE`** when governed code outran the spec by more than
|
|
123
|
+
`defaults.spec_drift.max_lag_days` (default 30), listing the newer files; **`CLEAN`** when it
|
|
124
|
+
did not; **`UNMAPPED`** when there is no `governs:` frontmatter *or its globs match nothing*
|
|
125
|
+
— absence of a mapping is absence of measurement, not "no drift"; **`UNTRACKED`** when the
|
|
126
|
+
spec has no commit date to compare. The latest run lands in the ledger (`spec_drift`) so the
|
|
127
|
+
mirador can surface it. No readiness impact, no exit-code gate: a stale spec is a prompt for
|
|
128
|
+
a human conversation, not a blocked pipeline.
|
|
129
|
+
|
|
41
130
|
## End-to-end flow
|
|
42
131
|
|
|
43
132
|
`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.60.1
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"AC-FP-01": true, "AC-FP-11": true, "AC-FP-07": true, "AC-FP-06": true, "AC-FP-05": true, "AC-FP-02": true, "AC-FP-03": true, "AC-FP-09": true, "AC-FP-10": true, "AC-FP-08": true}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"AC-GM-01": true, "AC-GM-03": true, "AC-GM-05": true, "AC-GM-04": true, "AC-GM-02": true, "AC-GM-06": true, "AC-GM-07": true, "AC-GM-08": null}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"AC-SD-01": true, "AC-SD-03": true, "AC-SD-02": true, "AC-SD-04": true}
|
|
@@ -190,3 +190,21 @@ covered.
|
|
|
190
190
|
|
|
191
191
|
Never overwrite an existing approved golden. If a `.received` already exists, regenerate it;
|
|
192
192
|
if a `.approved` exists, it is the human's — leave it untouched and surface the diff.
|
|
193
|
+
|
|
194
|
+
## Record the coverage map (ADR-006)
|
|
195
|
+
|
|
196
|
+
After the `.received` is produced and while the harness is still the thing that just ran,
|
|
197
|
+
record WHICH source files it exercised — the mapping that lets the fast-path veto a change
|
|
198
|
+
touching code a golden froze:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
python qa_ledger.py golden-coverage --harness <harness> --golden <the .approved sibling>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This is **derived measurement, not judgment**: the agent may write `golden.coverage.json`.
|
|
205
|
+
INV-GOLDEN-01 governs the `.approved` bytes, which encode judgment, and nothing here changes
|
|
206
|
+
that — the human still approves the golden itself.
|
|
207
|
+
|
|
208
|
+
`coverage.py` is a capture-time dependency. Without it the command writes **nothing** and exits
|
|
209
|
+
2: an empty map would read as "this golden covers nothing", which is exactly the lie that would
|
|
210
|
+
let the veto pass. Skipping the map is honest; recording an empty one is not.
|
|
@@ -169,6 +169,27 @@ install `hooks/block-approved-writes.py` as a `PreToolUse` hook in
|
|
|
169
169
|
`settings.json`, and add `*.approved.* binary` to `.gitattributes` (ships in
|
|
170
170
|
`templates/.gitattributes`) so line endings can't lie in the byte-compare.
|
|
171
171
|
|
|
172
|
+
## Phase 0a — Fast-path check (ADR-003; run FIRST, before planning ceremony)
|
|
173
|
+
|
|
174
|
+
If `defaults.fast_path` exists in config, run the measured classifier before demanding a
|
|
175
|
+
full spec package:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python qa_ledger.py fastpath-eval --repo <name> --json # dry-run first
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- **ALLOW** and the operator wants the shortcut: re-run with
|
|
182
|
+
`--intent "<one sentence: what and why>"` to record it, then skip Phase 0's full-package
|
|
183
|
+
demand. The micro-contract replaces it: the recorded INTENT plus at least one new/modified
|
|
184
|
+
asserting test (readiness stays capped until that test shows up in measured evidence).
|
|
185
|
+
- **DENY**: state WHICH measured signal denied it — echo the engine's breakdown verbatim.
|
|
186
|
+
The skill wires; it never computes and never argues with the verdict. Proceed with the
|
|
187
|
+
normal full path. The operator may always choose the full path over an ALLOW; nothing —
|
|
188
|
+
operator, agent or flag — can force ALLOW over a DENY (INV-RIGOR-02).
|
|
189
|
+
- **Re-evaluate before the PR step** (same command, same intent): thresholds exceeded mid-run
|
|
190
|
+
flip the run to `ESCALATED` — the derived phase blocks pr-ready and readiness is capped.
|
|
191
|
+
Produce the ADR + ACCEPTANCE the change turned out to deserve, then `resolve-escalation`.
|
|
192
|
+
|
|
172
193
|
## Phase 0 — Plan (ADR-first)
|
|
173
194
|
|
|
174
195
|
- **Read `CONSTITUTION.md` first (if present).** It lists the project invariants no SPEC
|