@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
@@ -75,23 +75,38 @@ wires the JSON the engine emits into the template. Read-only.
75
75
  this for a pure measured view.
76
76
 
77
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:
78
+ merges the sidecar telemetry if present, injects `const DATA`, writes the file, prints its
79
+ absolute path (`OPEN IT: ...`), and opens it in the default browser. From the project root,
80
+ with no long paths — `--engine` and `--template` default to the renderer's sibling skill
81
+ files (kit 1.41.2):
79
82
  ```bash
80
- python3 <skill-dir>/mirador-render.py --engine <engine> --ledger QA-LEDGER.json \
81
- --template <skill-dir>/mirador.template.html --out mirador.html
83
+ python3 <skill-dir>/mirador-render.py --ledger QA-LEDGER.json
82
84
  ```
83
85
  The engine stays model-agnostic — telemetry is merged by the renderer (the adapter), NOT
84
86
  by `dashboard`. If the ledger is missing, warn and stop (run `uscha-devloop` first). For
85
87
  the time-lapse, `qa_ledger.py readiness --record` (opt-in) fills the history prospectively.
86
88
 
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`.
89
+ 5. **Where to look:** the renderer already opened `mirador.html` and printed its absolute path
90
+ on the `OPEN IT:` line — surface that path to the operator. Pass `--no-open` to write the
91
+ file without opening a browser (headless/CI, or the watch loop below, which passes it).
92
+
93
+ ## For a human at a terminal: `uscha mirador`
94
+
95
+ The one-liner above is what THIS skill runs. A human who just wants the dashboard — without a
96
+ Claude Code session — has a zero-friction verb instead (kit 1.43.0): from the project root,
97
+
98
+ ```bash
99
+ uscha mirador # render + open, defaults to the QA-LEDGER.json convention
100
+ uscha mirador --watch # live second-screen view (auto-refresh, one self-reloading tab)
101
+ npx @andresmassello/uscha mirador # same, no install
102
+ ```
103
+
104
+ No python, no paths: the verb resolves the engine, template and ledger on its own.
90
105
 
91
106
  ## Live second-screen view
92
107
 
93
108
  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:
109
+ spare terminal and open `mirador.html` on a second monitor (or just use `uscha mirador --watch`):
95
110
 
96
111
  ```bash
97
112
  # Windows: powershell -NoProfile -File <skill-dir>\mirador-watch.ps1 -Interval 30
@@ -59,15 +59,45 @@ def aggregate_telemetry(path):
59
59
  "model": model, "effort": effort, "by_model": by_model}
60
60
 
61
61
 
62
+ def _json_for_html_script(value):
63
+ """Serialize JSON without allowing data to terminate the enclosing script."""
64
+ return (json.dumps(value, ensure_ascii=False)
65
+ .replace("&", r"\u0026")
66
+ .replace("<", r"\u003c")
67
+ .replace(">", r"\u003e")
68
+ .replace("\u2028", r"\u2028")
69
+ .replace("\u2029", r"\u2029"))
70
+
71
+
72
+ def _open_best_effort(path):
73
+ """Open the rendered file in the default browser; never fail (headless/CI)."""
74
+ try:
75
+ if sys.platform.startswith("win"):
76
+ os.startfile(path) # type: ignore[attr-defined] # Windows-only
77
+ elif sys.platform == "darwin":
78
+ subprocess.Popen(["open", path])
79
+ else:
80
+ subprocess.Popen(["xdg-open", path])
81
+ except Exception:
82
+ pass # the absolute path was already printed
83
+
84
+
62
85
  def main():
86
+ here = os.path.dirname(os.path.abspath(__file__))
63
87
  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")
88
+ # engine + template default to the SIBLING skill files, so from any project you can just
89
+ # run this script (or `/uscha-mirador`) with no long paths (kit 1.41.2).
90
+ ap.add_argument("--engine", default=os.path.join(here, os.pardir, "uscha-devloop", "qa_ledger.py"),
91
+ help="path to qa_ledger.py (default: the sibling uscha-devloop engine)")
65
92
  ap.add_argument("--ledger", default="QA-LEDGER.json")
66
- ap.add_argument("--template", required=True, help="path to mirador.template.html")
93
+ ap.add_argument("--template", default=os.path.join(here, "mirador.template.html"),
94
+ help="path to mirador.template.html (default: the sibling template)")
67
95
  ap.add_argument("--out", default="mirador.html")
68
96
  ap.add_argument("--sidecar", default=os.path.join(".uscha", "telemetry.jsonl"))
69
97
  ap.add_argument("--refresh", type=int, default=0,
70
98
  help="if >0, the page auto-reloads every N seconds (live second-screen view)")
99
+ ap.add_argument("--no-open", action="store_true",
100
+ help="write the file but do not open it in a browser")
71
101
  args = ap.parse_args()
72
102
 
73
103
  try:
@@ -93,18 +123,27 @@ def main():
93
123
  print(f"[mirador-render] cannot read template {args.template}: {e}", file=sys.stderr)
94
124
  return 1
95
125
  payload = ("/*MIRADOR_DATA_START*/\nconst DATA = "
96
- + json.dumps(data, ensure_ascii=False) + ";\n/*MIRADOR_DATA_END*/")
126
+ + _json_for_html_script(data) + ";\n/*MIRADOR_DATA_END*/")
97
127
  out = re.sub(r"/\*MIRADOR_DATA_START\*/.*?/\*MIRADOR_DATA_END\*/",
98
128
  lambda m: payload, tpl, count=1, flags=re.S)
99
129
  if args.refresh and args.refresh > 0:
100
130
  # opt-in live reload: a meta-refresh, injected only when watching
101
131
  out = out.replace("</head>",
102
132
  f'<meta http-equiv="refresh" content="{args.refresh}">\n</head>', 1)
133
+ live = bool(args.refresh and args.refresh > 0)
103
134
  with open(args.out, "w", encoding="utf-8") as f:
104
135
  f.write(out)
105
136
  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'})")
137
+ out_abs = os.path.abspath(args.out)
138
+ print(f"[mirador-render] wrote {out_abs}")
139
+ print(f"[mirador-render] readiness {score} | telemetry {'on' if tel else 'off'} | "
140
+ f"refresh {args.refresh or 'off'}")
141
+ print(f"[mirador-render] OPEN IT: {out_abs}")
142
+ # Auto-open only a ONE-SHOT view. In live mode (--refresh) the page carries its own
143
+ # meta-refresh and reloads in the SAME tab, so opening on every render cycle would
144
+ # spawn a new browser tab each time (kit 1.41.3). Open it once by hand and let it live.
145
+ if not args.no_open and not live:
146
+ _open_best_effort(out_abs)
108
147
  return 0
109
148
 
110
149
 
@@ -16,7 +16,7 @@ $out = if ($env:OUT) { $env:OUT } else { "mirador.html" }
16
16
  $py = if ($env:PYTHON) { $env:PYTHON } else { "python" }
17
17
  Write-Host "mirador-watch: regenerating $out every ${Interval}s (Ctrl-C to stop). Open $out in a browser."
18
18
  while ($true) {
19
- & $py (Join-Path $here "mirador-render.py") --engine $engine --ledger $ledger --template $template --out $out --refresh $Interval
19
+ & $py (Join-Path $here "mirador-render.py") --engine $engine --ledger $ledger --template $template --out $out --refresh $Interval --no-open
20
20
  if ($LASTEXITCODE -ne 0) { Write-Host "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying" }
21
21
  Start-Sleep -Seconds $Interval
22
22
  }
@@ -20,7 +20,7 @@ command -v "$PY" >/dev/null 2>&1 || PY=python
20
20
  echo "mirador-watch: regenerating $OUT every ${INTERVAL}s (Ctrl-C to stop). Open $OUT in a browser."
21
21
  while true; do
22
22
  "$PY" "$HERE/mirador-render.py" --engine "$ENGINE" --ledger "$LEDGER" \
23
- --template "$TEMPLATE" --out "$OUT" --refresh "$INTERVAL" || \
23
+ --template "$TEMPLATE" --out "$OUT" --refresh "$INTERVAL" --no-open || \
24
24
  echo "mirador-watch: render failed (ledger missing? run uscha-devloop first) -- retrying"
25
25
  sleep "$INTERVAL"
26
26
  done