@andresmassello/uscha 1.40.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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +104 -0
  3. package/bin/README.md +6 -0
  4. package/bin/uscha.js +28 -0
  5. package/package.json +38 -0
  6. package/uscha-kit/.claude/skills/uscha-adr-refine/SKILL.md +161 -0
  7. package/uscha-kit/.claude/skills/uscha-characterize/SKILL.md +124 -0
  8. package/uscha-kit/.claude/skills/uscha-devloop/SKILL.md +490 -0
  9. package/uscha-kit/.claude/skills/uscha-devloop/qa_ledger.py +5602 -0
  10. package/uscha-kit/.claude/skills/uscha-discovery/SKILL.md +161 -0
  11. package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +145 -0
  12. package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +112 -0
  13. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.ps1 +22 -0
  14. package/uscha-kit/.claude/skills/uscha-mirador/mirador-watch.sh +26 -0
  15. package/uscha-kit/.claude/skills/uscha-mirador/mirador.template.html +586 -0
  16. package/uscha-kit/.claude/skills/uscha-mirador/telemetry-extract.py +130 -0
  17. package/uscha-kit/.claude/skills/uscha-reverse-discovery/SKILL.md +116 -0
  18. package/uscha-kit/.claude/skills/uscha-rubric/SKILL.md +79 -0
  19. package/uscha-kit/.claude/skills/uscha-sysdoc/SKILL.md +88 -0
  20. package/uscha-kit/.claude-plugin/plugin.json +24 -0
  21. package/uscha-kit/.codex-plugin/plugin.json +37 -0
  22. package/uscha-kit/CHANGELOG-1.10.0.md +84 -0
  23. package/uscha-kit/CHANGELOG-1.11.0.md +67 -0
  24. package/uscha-kit/CHANGELOG-1.12.0.md +46 -0
  25. package/uscha-kit/CHANGELOG-1.13.0.md +33 -0
  26. package/uscha-kit/CHANGELOG-1.14.0.md +42 -0
  27. package/uscha-kit/CHANGELOG-1.15.0.md +58 -0
  28. package/uscha-kit/CHANGELOG-1.16.0.md +55 -0
  29. package/uscha-kit/CHANGELOG-1.17.0.md +44 -0
  30. package/uscha-kit/CHANGELOG-1.18.0.md +42 -0
  31. package/uscha-kit/CHANGELOG-1.19.0.md +41 -0
  32. package/uscha-kit/CHANGELOG-1.2.2.md +16 -0
  33. package/uscha-kit/CHANGELOG-1.2.3.md +20 -0
  34. package/uscha-kit/CHANGELOG-1.2.4.md +10 -0
  35. package/uscha-kit/CHANGELOG-1.2.5.md +23 -0
  36. package/uscha-kit/CHANGELOG-1.2.6.md +11 -0
  37. package/uscha-kit/CHANGELOG-1.2.7.md +15 -0
  38. package/uscha-kit/CHANGELOG-1.2.8.md +24 -0
  39. package/uscha-kit/CHANGELOG-1.2.9.md +4 -0
  40. package/uscha-kit/CHANGELOG-1.20.0.md +29 -0
  41. package/uscha-kit/CHANGELOG-1.21.0.md +33 -0
  42. package/uscha-kit/CHANGELOG-1.22.0.md +60 -0
  43. package/uscha-kit/CHANGELOG-1.23.0.md +75 -0
  44. package/uscha-kit/CHANGELOG-1.24.0.md +50 -0
  45. package/uscha-kit/CHANGELOG-1.25.0.md +55 -0
  46. package/uscha-kit/CHANGELOG-1.26.0.md +70 -0
  47. package/uscha-kit/CHANGELOG-1.27.0.md +45 -0
  48. package/uscha-kit/CHANGELOG-1.28.0.md +35 -0
  49. package/uscha-kit/CHANGELOG-1.29.0.md +20 -0
  50. package/uscha-kit/CHANGELOG-1.3.0.md +74 -0
  51. package/uscha-kit/CHANGELOG-1.30.0.md +46 -0
  52. package/uscha-kit/CHANGELOG-1.31.0.md +59 -0
  53. package/uscha-kit/CHANGELOG-1.32.0.md +50 -0
  54. package/uscha-kit/CHANGELOG-1.33.0.md +46 -0
  55. package/uscha-kit/CHANGELOG-1.34.0.md +55 -0
  56. package/uscha-kit/CHANGELOG-1.35.0.md +30 -0
  57. package/uscha-kit/CHANGELOG-1.36.0.md +33 -0
  58. package/uscha-kit/CHANGELOG-1.37.0.md +41 -0
  59. package/uscha-kit/CHANGELOG-1.38.0.md +11 -0
  60. package/uscha-kit/CHANGELOG-1.39.0.md +14 -0
  61. package/uscha-kit/CHANGELOG-1.4.0.md +68 -0
  62. package/uscha-kit/CHANGELOG-1.40.0.md +16 -0
  63. package/uscha-kit/CHANGELOG-1.40.1.md +11 -0
  64. package/uscha-kit/CHANGELOG-1.5.0.md +64 -0
  65. package/uscha-kit/CHANGELOG-1.6.0.md +57 -0
  66. package/uscha-kit/CHANGELOG-1.7.0.md +74 -0
  67. package/uscha-kit/CHANGELOG-1.8.0.md +46 -0
  68. package/uscha-kit/CHANGELOG-1.9.0.md +112 -0
  69. package/uscha-kit/LICENSE +21 -0
  70. package/uscha-kit/README.md +497 -0
  71. package/uscha-kit/VERSION +1 -0
  72. package/uscha-kit/WORKBENCH.md +178 -0
  73. package/uscha-kit/hooks/block-approved-writes.ps1 +46 -0
  74. package/uscha-kit/hooks/hooks.json +15 -0
  75. package/uscha-kit/install-uscha.py +344 -0
  76. package/uscha-kit/skills/uscha-adr-refine/SKILL.md +161 -0
  77. package/uscha-kit/skills/uscha-characterize/SKILL.md +124 -0
  78. package/uscha-kit/skills/uscha-devloop/SKILL.md +490 -0
  79. package/uscha-kit/skills/uscha-devloop/qa_ledger.py +5602 -0
  80. package/uscha-kit/skills/uscha-discovery/SKILL.md +161 -0
  81. package/uscha-kit/skills/uscha-mirador/SKILL.md +145 -0
  82. package/uscha-kit/skills/uscha-mirador/mirador-render.py +112 -0
  83. package/uscha-kit/skills/uscha-mirador/mirador-watch.ps1 +22 -0
  84. package/uscha-kit/skills/uscha-mirador/mirador-watch.sh +26 -0
  85. package/uscha-kit/skills/uscha-mirador/mirador.template.html +586 -0
  86. package/uscha-kit/skills/uscha-mirador/telemetry-extract.py +130 -0
  87. package/uscha-kit/skills/uscha-reverse-discovery/SKILL.md +116 -0
  88. package/uscha-kit/skills/uscha-rubric/SKILL.md +79 -0
  89. package/uscha-kit/skills/uscha-sysdoc/SKILL.md +88 -0
  90. package/uscha-kit/templates/.gitattributes +6 -0
  91. package/uscha-kit/templates/CLAUDE.md +56 -0
  92. package/uscha-kit/templates/CONSTITUTION.md +149 -0
  93. package/uscha-kit/templates/RUBRIC.md +38 -0
  94. package/uscha-kit/templates/docs/adr/README.md +19 -0
  95. package/uscha-kit/templates/rubric-grader-prompt.md +63 -0
  96. package/uscha-kit/tests/smoke-engine.sh +1739 -0
  97. package/uscha-kit/uscha.config.json +181 -0
  98. package/uscha-kit/workbench-doctor.sh +45 -0
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: uscha-discovery
3
+ description: >
4
+ Front of the methodology for greenfield work. From a general idea (plus optional
5
+ reference docs, URLs, PDFs, or an existing codebase), grill the user ONE question at
6
+ a time — each with your recommended answer — PROPOSING the system shape (domain model,
7
+ API/operation surface, architecture options) so the human approves instead of authors.
8
+ Write the spec package (CONTEXT.md, SPEC.md, docs/adr/*.md, ACCEPTANCE.md, RISKS.md,
9
+ HANDOFF.md) directly to the repo as decisions crystallize. Invoke for "discovery",
10
+ "modelá esto desde una idea", "solo tengo la idea, no sé el cómo todavía".
11
+ allowed-tools: Read, Write, Glob, Grep, WebFetch
12
+ disable-model-invocation: false
13
+ ---
14
+
15
+ # discovery — from a bare idea to a writable spec package
16
+
17
+ The human brings the idea, the constraints and the reference material. **You bring the
18
+ shape.** Your job is to interrogate until there is a shared system shape, and to write
19
+ the documents as you go — not to ask the human to design the system for you.
20
+
21
+ ## Non-negotiable principles
22
+
23
+ 1. **One question at a time, each WITH your recommended answer.** This is the inversion
24
+ that makes discovery work: you propose (entities, endpoints, architecture, a default
25
+ decision), the human confirms or corrects. Never dump a list of 20 questions, and
26
+ never ask the human to supply structure you can propose yourself.
27
+ 2. **Explore instead of asking.** If a reference doc/URL/PDF, the existing codebase, or
28
+ an existing `CONTEXT.md`/`docs/adr/` can answer a question, read it first. Only ask
29
+ the human what genuinely requires their judgment.
30
+ 3. **Propose the shape.** From the idea + references, propose the core entities, the
31
+ operation/API surface, and 2–3 architecture options with trade-offs. Walk the design
32
+ tree branch by branch, resolving dependencies between decisions one at a time.
33
+ 4. **Grill, don't agree.** Surface contradictions, fuzzy/overloaded terms, missing
34
+ failure modes and unstated constraints. A discovery where you agreed with everything
35
+ failed.
36
+ 5. **Write files lazily and inline.** Create a file only when you have something real to
37
+ write, and update it the moment a decision crystallizes — don't batch to the end.
38
+
39
+ ## Inputs
40
+
41
+ - **The idea** (required): what the human wants to achieve. It doesn't have to be
42
+ complete or well-formed.
43
+ - **Reference material** (optional): manuals, PDFs, URLs, API specs. Read them with
44
+ WebFetch/Read before proposing — they are how you propose an accurate shape.
45
+ - **Existing codebase** (optional): if present, explore it; respect `CONTEXT.md` and
46
+ existing ADRs.
47
+
48
+ ## Domain awareness (explore first)
49
+
50
+ - Look for `CONTEXT.md` (domain glossary), `CONSTITUTION.md` (project invariants) and
51
+ `docs/adr/` and read them. The CONSTITUTION constrains every shape you propose — never
52
+ propose anything that would violate it.
53
+ - **Sharpen fuzzy language.** When the human uses a vague or overloaded term, propose a
54
+ precise canonical term: "You say 'account' — the Customer or the User? They're different things."
55
+ - **Maintain a glossary in `CONTEXT.md`**, updated inline as each term is resolved. Only
56
+ terms meaningful to domain experts; don't couple it to implementation details.
57
+
58
+ ## The grilling agenda (walk the tree, propose at each step)
59
+
60
+ Resolve these in order; for each, propose first, then ask. Skip what references already
61
+ answer.
62
+
63
+ 0. **Project name (first).** The very first thing you ask: "What do we call this?" Write the
64
+ answer to `uscha.config.json` as `"project": "<name>"` (create the config if absent). The
65
+ mirador shows it prominently at the top and the dashboard reads it from there; if unset it
66
+ falls back to the joined repo names.
67
+ 1. **Purpose / value / why now.** What job does this remove? Cost of not doing it?
68
+ 2. **Domain model.** Propose the core entities and their relationships. ("From the domain
69
+ I deduce these core entities: … do they work for you, or is one missing?")
70
+ 3. **Operation / API surface.** Propose the endpoints/operations and their
71
+ contracts (idempotency, status codes).
72
+ 4. **Big decisions (→ ADR).** Propose 2–3 architecture options with trade-offs and
73
+ a recommended default: persistence, protocol, idempotency, sync/async, multi-tenancy.
74
+ 5. **Behavior and dirty cases.** Happy path, then failures, retries, partial states,
75
+ concurrency, what must NOT happen.
76
+ 6. **Inviolable constraints (→ `CONSTITUTION.md`).** Domain + security + operation
77
+ rules that can't be broken (money to the cent, no numbering gaps, never cross
78
+ environments, secrets never logged, auth). Write/extend `CONSTITUTION.md` with these —
79
+ one invariant per line, with a CWE reference where it maps. They feed the severity gate
80
+ downstream, and a breach is a BLOCKER, never a trade-off.
81
+ 7. **Out of scope.** Explicit boundaries with forward references.
82
+ 8. **Acceptance / Definition of Done.** Concrete, checkable criteria + success metrics.
83
+ 9. **Quality bar (→ config, kit 1.17.0).** "What level of quality is ENOUGH here, and which
84
+ dimensions are negotiable (coverage, perf, security)?" Propose thresholds fit for
85
+ the risk profile (a payments core is not an internal dashboard). What the human
86
+ declares goes into `uscha.config.json` (`defaults.coverage_threshold`,
87
+ `defaults.readiness_caps`, `defaults.simplicity`) — a declared threshold reads as
88
+ **requerimiento (config)** in the engine's output; an undeclared one stays a kit
89
+ default (opinion) and is labeled as such. Declaring is committing the config.
90
+ 10. **Residual risks and dependencies.** What's uncertain, what must exist first.
91
+ For each HIGH-uncertainty risk, ask (kit 1.19.0, Tip 21 'Prototype to Learn'):
92
+ "Does it warrant a time-boxed spike before freezing the SPEC?" A spike runs on a
93
+ `spike/*` branch and its ONLY legitimate output is an **ADR with lessons**
94
+ (facts that feed the SPEC) — never mergeable code. The contract is executable:
95
+ `phase --require pr-ready` refuses any `spike/*` branch, INV-GOLDEN-01 style.
96
+
97
+ ## Files to write (lazily, inline)
98
+
99
+ - **`CONTEXT.md`** — domain glossary; create on first resolved term, update as you go.
100
+ - **`CONSTITUTION.md`** — project invariants no ADR/SPEC may violate (security with CWE
101
+ refs, domain rules, operation). Create/extend from the "restricciones inviolables" step.
102
+ This is the layer above the ADRs; a breach is a BLOCKER finding downstream.
103
+ - **`DOMAIN-MODEL.md`** — the proposed core entities and their relationships (the "shape"
104
+ you proposed and the human approved). Distinct from the glossary: this is the model, not
105
+ the vocabulary.
106
+ - **`SPEC.md`** — objective/value, risk, scope/out-of-scope, behavior,
107
+ inputs/outputs/errors, acceptance, test plan, operation, rollback.
108
+ - **`docs/adr/ADR-NNN-<slug>.md`** — one per durable decision. Format: Status
109
+ (proposed/accepted/**experiment**/deprecated/superseded) · Context · Alternatives · Decision ·
110
+ Consequences · **Implementation Plan** (affected paths, patterns to follow, tests to
111
+ write) · **Verification** (`- [ ]` checkboxes a coding agent can check). The
112
+ Implementation Plan makes the ADR an executable spec: the agent implements it without
113
+ asking follow-ups. Number from the highest existing ADR. Use `Status: Experiment` only
114
+ for a bounded, reversible hypothesis that needs real feedback; include `Hypothesis`,
115
+ `Feedback Signal`, `Review By` or `Review Trigger`, `Promote Criteria`, and
116
+ `Rollback / Supersede Criteria`.
117
+ - **`ACCEPTANCE.md`** — Definition of Done as `- [ ]` checkboxes + success metrics. This
118
+ is the file the readiness KPI measures downstream. Give EVERY criterion a stable
119
+ traceable ID: `- [ ] AC-01 — when X then Y` (sequential, never reused). Downstream,
120
+ a criterion only closes MEASURED when a green testcase carries its tag in the name
121
+ (`test_ac1_x` / `testAC01X` / `"AC-01: ..."`) — write criteria so each one is
122
+ coverable by at least one named test.
123
+ - **`RISKS.md`** — residual risks, assumptions, points needing human approval.
124
+ - **`HANDOFF.md`** — what to read before coding + hard "no hacer" rules + required evidence.
125
+
126
+ ## Offer ADRs sparingly
127
+
128
+ Only write an ADR when ALL three hold (otherwise it's noise that buries the important ones):
129
+ 1. **Hard to reverse** — changing your mind later has real cost.
130
+ 2. **Surprising without context** — a future reader will wonder "why this way?".
131
+ 3. **A real trade-off** — there were genuine alternatives and you chose one for reasons.
132
+
133
+ ## Convergence — finish when
134
+
135
+ A shared system shape exists: entities, operations and big decisions taken (or recorded
136
+ as explicit assumptions), every failure mode has defined behavior, out-of-scope is
137
+ explicit, and the DoD is checkable. State plainly that it converged, then write/finalize
138
+ the package and the handoff.
139
+
140
+ ## Handoff
141
+
142
+ End with the implementation handoff (works for a human, an agent, or CI):
143
+
144
+ > "Read CONTEXT.md, SPEC.md, docs/adr/*.md and ACCEPTANCE.md. Before touching code:
145
+ > 1) summarize the expected behavior, 2) flag ambiguities or contradictions,
146
+ > 3) propose a file plan + tests. Implement only the SPEC's scope. Do not change
147
+ > contracts outside the SPEC, and do not edit the SPEC to make your implementation look correct."
148
+
149
+ Flow: `/uscha-discovery` (idea → package) → `/uscha-devloop` (build + QA + evidence) → human gate.
150
+
151
+ ## Relationship to adr-refine
152
+
153
+ `uscha-discovery` is the greenfield front: you only have an idea, so the skill PROPOSES the
154
+ shape. `uscha-adr-refine` is the same interview applied to a KNOWN feature where the shape is
155
+ already clear and you only need precision. Both emit the same package; pick by starting
156
+ point.
157
+
158
+ ## Tracked-markdown protocol
159
+
160
+ If a target `.md` already exists and is tracked, ask for its current version before
161
+ overwriting — never silently replace real progress.
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: uscha-mirador
3
+ description: >
4
+ Bird's-eye status view ("mirador") of a spec-loop project: one glance at
5
+ readiness, sub-scores, the phase path, invariants, QA loops and a readiness
6
+ time-lapse. Runs `qa_ledger.py dashboard --json` (read-only, deterministic,
7
+ zero LLM narration), injects that JSON into mirador.template.html, writes
8
+ mirador.html at the project root and opens it. The skill WIRES, it does not
9
+ calculate — every number comes from the ledger (truth-pass: a field with no
10
+ source shows null and the template degrades, never invented). Invoke for
11
+ "mirador", "vista de estado", "bird's-eye", "dashboard del proyecto".
12
+ allowed-tools: Read, Write, Glob, Grep, Bash
13
+ ---
14
+
15
+ # uscha-mirador — bird's-eye status view
16
+
17
+ Paints the REAL state of the project at a glance. It does not narrate or estimate: it
18
+ wires the JSON the engine emits into the template. Read-only.
19
+
20
+ ## Contract
21
+
22
+ - **Source of truth:** `qa_ledger.py dashboard --json` — aggregates ONLY state the
23
+ ledger already has (readiness, subscores, phases, specs, adrs, inv, layers, loops,
24
+ snapshots, evidence). A field with no source comes out `null`/`[]`; the template
25
+ degrades. **A datum is never invented.**
26
+ - **Template:** `mirador.template.html` (in this folder). The data block lives between
27
+ `/*MIRADOR_DATA_START*/` and `/*MIRADOR_DATA_END*/`; the sample `const DATA` it ships
28
+ with is the **offline fallback**. The skill replaces ONLY that region; it does not
29
+ touch the rest of the HTML.
30
+ - **Project name (top):** the mirador shows the project name prominently at the top, from
31
+ `config.project` in `uscha.config.json` (set it in `uscha-discovery`); if unset, it falls
32
+ back to the joined repo names (truth-pass — no invented name).
33
+ - **Execution policy (bird's-eye):** `dashboard --json` also exposes
34
+ `execution_policy` plus `phase.execution` for each trail node. This is routing metadata
35
+ from `config.defaults.execution_policy` (`method`, `tier`, `model`, `effort`,
36
+ `uncorrelated`), not a score. The template renders it as a separate panel so the human
37
+ sees which methodology/model/effort is selected per phase without mixing it into
38
+ readiness.
39
+ - **Discovery intake:** `dashboard --json` carries `discovery_intake` from readiness:
40
+ open `production_findings`, `spec_doubts`, and `spec_change_requests`. The current template keeps the top-level
41
+ verdict honest through readiness/caps; future panels may render the intake directly.
42
+ - **ADR experiments:** ADR rows may carry `adr_status: "experiment"`, `review_by`,
43
+ `review_trigger`, `experiment_valid`, `experiment_missing`, and `expired`; top-level
44
+ `adr_experiments` summarizes open/malformed/expired experiments. This is advisory
45
+ visibility for measured hypotheses, not readiness scoring.
46
+ - **Session telemetry (optional, vendor-reported):** if `.uscha/telemetry.jsonl` exists,
47
+ the skill aggregates it and MERGES a `telemetry` object into `DATA`. This is the ONE
48
+ panel that is **narrated by the vendor (Claude Code), not measured by the engine** —
49
+ tokens, wall time, model, effort — so it renders in a **segregated strip** labeled as
50
+ such, never mixed with the measured panels. `dashboard --json` NEVER emits it; it enters
51
+ only through this adapter. Absent file → no strip (degrades). This is the doctrinal
52
+ line: telemetry is neither a fact-gate nor a guess-advisor, so it must not dilute
53
+ *measured beats narrated*.
54
+
55
+ ## Flow
56
+
57
+ 1. **Resolve the engine.** Use the first one that exists:
58
+ - `./.claude/skills/uscha-devloop/qa_ledger.py` (per-project install)
59
+ - `~/plugins/uscha/skills/uscha-devloop/qa_ledger.py` (Codex plugin install)
60
+ - `~/.codex/skills/uscha-devloop/qa_ledger.py` (Codex raw-skills install)
61
+ - `~/.claude/skills/uscha-devloop/qa_ledger.py` (Claude global install)
62
+
63
+ 2. **Resolve the ledger.** `QA-LEDGER.json` in the cwd (or the `--ledger` the user
64
+ points to). If it does not exist, warn: you have to run `uscha-devloop` (or
65
+ `qa_ledger.py init`) first — with no ledger there is no state to look at.
66
+
67
+ (`<skill-dir>` below = this folder: `.claude/skills/uscha-mirador/` per-project, or `~/plugins/uscha/skills/uscha-mirador/` / `~/.codex/skills/uscha-mirador/` / `~/.claude/skills/uscha-mirador/` global.)
68
+
69
+ 3. **(Optional) record this session's telemetry.** For the vendor-telemetry strip, append
70
+ the current Claude Code session to the sidecar:
71
+ ```bash
72
+ python3 <skill-dir>/telemetry-extract.py <path-to-CC-transcript.jsonl>
73
+ ```
74
+ It **upserts by session** (safe to re-run — a watch loop won't inflate the totals). Skip
75
+ this for a pure measured view.
76
+
77
+ 4. **Render `mirador.html`** with the standalone renderer — it runs `dashboard --json`,
78
+ merges the sidecar telemetry if present, injects `const DATA`, and writes the file:
79
+ ```bash
80
+ python3 <skill-dir>/mirador-render.py --engine <engine> --ledger QA-LEDGER.json \
81
+ --template <skill-dir>/mirador.template.html --out mirador.html
82
+ ```
83
+ The engine stays model-agnostic — telemetry is merged by the renderer (the adapter), NOT
84
+ by `dashboard`. If the ledger is missing, warn and stop (run `uscha-devloop` first). For
85
+ the time-lapse, `qa_ledger.py readiness --record` (opt-in) fills the history prospectively.
86
+
87
+ 5. **Open it** (best-effort; never fail — headless/CI): `start "" mirador.html` (Windows) /
88
+ `open mirador.html` (macOS) / `xdg-open mirador.html` (Linux). ALWAYS print the absolute
89
+ path of `mirador.html`.
90
+
91
+ ## Live second-screen view
92
+
93
+ For a mirador that updates while you keep coding in the terminal, run the watch loop in a
94
+ spare terminal and open `mirador.html` on a second monitor:
95
+
96
+ ```bash
97
+ # Windows: powershell -NoProfile -File <skill-dir>\mirador-watch.ps1 -Interval 30
98
+ # Unix: bash <skill-dir>/mirador-watch.sh 30
99
+ ```
100
+
101
+ It re-renders `mirador.html` every N seconds (default 30) from the current ledger, rendered
102
+ with a matching `<meta http-equiv="refresh">` so the open page reloads on its own — **no
103
+ server**. It is only as live as the **ledger**: the picture changes at the dev-loop's
104
+ measured checkpoints (snapshots, gates, `readiness --record`), not per keystroke. Honest by
105
+ design — it shows measured state, which changes at measured moments.
106
+
107
+ ## Session telemetry — sidecar contract
108
+
109
+ `.uscha/telemetry.jsonl` is an **append-only** file, one JSON object per session/run:
110
+
111
+ ```jsonl
112
+ {"at": "2026-07-05T23:40:00Z", "model": "claude-opus-4-8", "tokens_in": 240000, "tokens_out": 41000, "ms": 4200000, "by_model": [{"model": "claude-opus-4-8", "tokens_in": 240000, "tokens_out": 41000}], "note": "mirador build"}
113
+ ```
114
+
115
+ `effort` and `note` are **optional** and are NOT produced by `telemetry-extract.py` (a Claude
116
+ Code transcript carries no reliable effort label) — add them by hand if you want them; the
117
+ strip shows `—` when `effort` is absent. `by_model` is per-**tokens** (session-level `ms` only).
118
+
119
+ - **Who writes it:** the AGENT / operator, NEVER the engine (the engine is model-agnostic
120
+ and cannot see tokens). Two honest sources:
121
+ - **`telemetry-extract.py <transcript.jsonl>`** (shipped in this skill folder): parses a
122
+ Claude Code session transcript — each assistant turn carries a `usage` block
123
+ (`input_tokens` / `cache_*_input_tokens` / `output_tokens`) and a `model` — sums per
124
+ model, computes wall time from the timestamps, and appends one line (with a `by_model`
125
+ breakdown). Real, vendor-native data. Best-effort: unknown/older schemas degrade, never
126
+ crash; no usage found → nothing appended.
127
+ - **Manual append**: paste the session's numbers from Claude Code's own cost view.
128
+ - **Persistence:** the file itself IS the history (portable, diffable, greppable). Add it to
129
+ the project's `.gitignore` — it is per-machine telemetry, not repo state. (A pure-client
130
+ alternative is `localStorage` inside `mirador.html`, but the sidecar survives regeneration,
131
+ so it is the default.)
132
+ - **Boundary (non-negotiable):** this is the ONLY place vendor-narrated numbers enter the
133
+ mirador. They are shown, never gated, never fed into readiness — telemetry answers "what
134
+ did it cost", the measured panels answer "is it correct". Keep them apart.
135
+
136
+ ## Rules
137
+
138
+ - **Zero computation in the skill.** The numbers come from the ledger. If a panel looks
139
+ empty, it's because the engine doesn't have that source yet (truth-pass), not because
140
+ painting it was missed.
141
+ - **Read-only.** The skill does not run gates or modify the ledger. `dashboard` is
142
+ read-only; the only one that writes (opt-in) is `readiness --record`, and that is the
143
+ loop's decision, not this skill's.
144
+ - **`mirador.html` is a generated artifact** — suggest adding it to the project's
145
+ `.gitignore`; it regenerates whenever you want.
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env python3
2
+ """mirador-render.py -- render mirador.html from the live ledger (uscha-kit 1.34.0).
3
+
4
+ Standalone renderer used by the uscha-mirador skill AND by mirador-watch (the live
5
+ second-screen loop). It:
6
+ 1. runs `qa_ledger.py dashboard --json` (read-only engine call),
7
+ 2. if the telemetry sidecar exists, aggregates it (per-model merge) and merges a
8
+ `telemetry` object into DATA -- vendor-reported, NEVER from the engine,
9
+ 3. injects `const DATA` between the template's markers and writes mirador.html.
10
+
11
+ The engine stays model-agnostic: telemetry is merged HERE, in the adapter, never in
12
+ qa_ledger.py.
13
+
14
+ Usage:
15
+ python3 mirador-render.py --engine <qa_ledger.py> --ledger QA-LEDGER.json \
16
+ --template mirador.template.html --out mirador.html \
17
+ [--sidecar .uscha/telemetry.jsonl] [--refresh 30]
18
+ """
19
+ import argparse
20
+ import json
21
+ import os
22
+ import re
23
+ import subprocess
24
+ import sys
25
+
26
+
27
+ def aggregate_telemetry(path):
28
+ """Sum the sidecar's per-session lines into one telemetry object, merging by_model
29
+ (tokens per model) across sessions. Returns None if the file is absent/empty."""
30
+ if not path or not os.path.isfile(path):
31
+ return None
32
+ lines = []
33
+ for line in open(path, encoding="utf-8"):
34
+ line = line.strip()
35
+ if not line:
36
+ continue
37
+ try:
38
+ lines.append(json.loads(line))
39
+ except ValueError:
40
+ continue
41
+ if not lines:
42
+ return None
43
+ tin = sum(l.get("tokens_in") or 0 for l in lines)
44
+ tout = sum(l.get("tokens_out") or 0 for l in lines)
45
+ ms = sum(l.get("ms") or 0 for l in lines)
46
+ by = {}
47
+ for l in lines:
48
+ for m in l.get("by_model") or []:
49
+ agg = by.setdefault(m.get("model", "unknown"), [0, 0])
50
+ agg[0] += m.get("tokens_in") or 0
51
+ agg[1] += m.get("tokens_out") or 0
52
+ by_model = [{"model": k, "tokens_in": v[0], "tokens_out": v[1]}
53
+ for k, v in sorted(by.items())]
54
+ effort = next((l.get("effort") for l in reversed(lines) if l.get("effort")), None)
55
+ model = (by_model[0]["model"] if len(by_model) == 1
56
+ else "+".join(m["model"] for m in by_model) if by_model else None)
57
+ return {"source": "Claude Code", "sessions": len(lines),
58
+ "tokens_in": tin, "tokens_out": tout, "ms": ms or None,
59
+ "model": model, "effort": effort, "by_model": by_model}
60
+
61
+
62
+ def main():
63
+ ap = argparse.ArgumentParser(description="render mirador.html from the ledger (+ optional vendor telemetry)")
64
+ ap.add_argument("--engine", required=True, help="path to qa_ledger.py")
65
+ ap.add_argument("--ledger", default="QA-LEDGER.json")
66
+ ap.add_argument("--template", required=True, help="path to mirador.template.html")
67
+ ap.add_argument("--out", default="mirador.html")
68
+ ap.add_argument("--sidecar", default=os.path.join(".uscha", "telemetry.jsonl"))
69
+ ap.add_argument("--refresh", type=int, default=0,
70
+ help="if >0, the page auto-reloads every N seconds (live second-screen view)")
71
+ args = ap.parse_args()
72
+
73
+ try:
74
+ raw = subprocess.check_output(
75
+ [sys.executable, args.engine, "dashboard", "--ledger", args.ledger, "--json"],
76
+ text=True, encoding="utf-8")
77
+ except (subprocess.CalledProcessError, OSError) as e:
78
+ print(f"[mirador-render] dashboard failed: {e}", file=sys.stderr)
79
+ return 1
80
+ try:
81
+ data = json.loads(raw)
82
+ except ValueError as e:
83
+ print(f"[mirador-render] dashboard returned invalid JSON: {e}", file=sys.stderr)
84
+ return 1
85
+
86
+ tel = aggregate_telemetry(args.sidecar)
87
+ if tel:
88
+ data["telemetry"] = tel # vendor-reported, merged in the adapter -- never by the engine
89
+
90
+ try:
91
+ tpl = open(args.template, encoding="utf-8").read()
92
+ except OSError as e:
93
+ print(f"[mirador-render] cannot read template {args.template}: {e}", file=sys.stderr)
94
+ return 1
95
+ payload = ("/*MIRADOR_DATA_START*/\nconst DATA = "
96
+ + json.dumps(data, ensure_ascii=False) + ";\n/*MIRADOR_DATA_END*/")
97
+ out = re.sub(r"/\*MIRADOR_DATA_START\*/.*?/\*MIRADOR_DATA_END\*/",
98
+ lambda m: payload, tpl, count=1, flags=re.S)
99
+ if args.refresh and args.refresh > 0:
100
+ # opt-in live reload: a meta-refresh, injected only when watching
101
+ out = out.replace("</head>",
102
+ f'<meta http-equiv="refresh" content="{args.refresh}">\n</head>', 1)
103
+ with open(args.out, "w", encoding="utf-8") as f:
104
+ f.write(out)
105
+ score = (data.get("readiness") or {}).get("score")
106
+ print(f"[mirador-render] wrote {args.out} (readiness {score}, "
107
+ f"telemetry {'on' if tel else 'off'}, refresh {args.refresh or 'off'})")
108
+ return 0
109
+
110
+
111
+ if __name__ == "__main__":
112
+ sys.exit(main())
@@ -0,0 +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
20
+ if ($LASTEXITCODE -ne 0) { Write-Host "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying" }
21
+ Start-Sleep -Seconds $Interval
22
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ # mirador-watch.sh -- live second-screen mirador (uscha-kit 1.34.0).
3
+ # Regenerates mirador.html every N seconds from the current ledger; the page is rendered
4
+ # with a meta-refresh at the same interval, so a browser open on it updates on its own.
5
+ #
6
+ # Usage (run in a spare terminal, from the project root):
7
+ # bash <kit>/.claude/skills/uscha-mirador/mirador-watch.sh [interval_seconds]
8
+ # then open mirador.html in a browser on your second screen. Ctrl-C to stop.
9
+ #
10
+ # Overridable via env: ENGINE, LEDGER, TEMPLATE, OUT, PYTHON.
11
+ set -u
12
+ HERE="$(cd "$(dirname "$0")" && pwd)"
13
+ INTERVAL="${1:-30}"
14
+ ENGINE="${ENGINE:-$HERE/../uscha-devloop/qa_ledger.py}"
15
+ LEDGER="${LEDGER:-QA-LEDGER.json}"
16
+ TEMPLATE="${TEMPLATE:-$HERE/mirador.template.html}"
17
+ OUT="${OUT:-mirador.html}"
18
+ PY="${PYTHON:-python3}"
19
+ command -v "$PY" >/dev/null 2>&1 || PY=python
20
+ echo "mirador-watch: regenerating $OUT every ${INTERVAL}s (Ctrl-C to stop). Open $OUT in a browser."
21
+ while true; do
22
+ "$PY" "$HERE/mirador-render.py" --engine "$ENGINE" --ledger "$LEDGER" \
23
+ --template "$TEMPLATE" --out "$OUT" --refresh "$INTERVAL" || \
24
+ echo "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying"
25
+ sleep "$INTERVAL"
26
+ done