@andresmassello/uscha 1.51.1 → 1.51.3
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 -1
- package/package.json +3 -2
- package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +11 -4
- package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +16 -8
- package/uscha-kit/.claude-plugin/plugin.json +2 -2
- package/uscha-kit/.codex-plugin/plugin.json +2 -2
- package/uscha-kit/INSTALL.md +3 -0
- package/uscha-kit/README.md +1 -1
- package/uscha-kit/VERSION +1 -1
- package/uscha-kit/install-uscha.py +9 -3
- package/uscha-kit/skills/uscha-mirador/SKILL.md +11 -4
- package/uscha-kit/skills/uscha-mirador/mirador-render.py +16 -8
- package/uscha-kit/uscha.config.json +1 -1
- package/uscha-kit/CHANGELOG-1.10.0.md +0 -84
- package/uscha-kit/CHANGELOG-1.11.0.md +0 -67
- package/uscha-kit/CHANGELOG-1.12.0.md +0 -46
- package/uscha-kit/CHANGELOG-1.13.0.md +0 -33
- package/uscha-kit/CHANGELOG-1.14.0.md +0 -42
- package/uscha-kit/CHANGELOG-1.15.0.md +0 -58
- package/uscha-kit/CHANGELOG-1.16.0.md +0 -55
- package/uscha-kit/CHANGELOG-1.17.0.md +0 -44
- package/uscha-kit/CHANGELOG-1.18.0.md +0 -42
- package/uscha-kit/CHANGELOG-1.19.0.md +0 -41
- package/uscha-kit/CHANGELOG-1.2.2.md +0 -16
- package/uscha-kit/CHANGELOG-1.2.3.md +0 -20
- package/uscha-kit/CHANGELOG-1.2.4.md +0 -10
- package/uscha-kit/CHANGELOG-1.2.5.md +0 -23
- package/uscha-kit/CHANGELOG-1.2.6.md +0 -11
- package/uscha-kit/CHANGELOG-1.2.7.md +0 -15
- package/uscha-kit/CHANGELOG-1.2.8.md +0 -24
- package/uscha-kit/CHANGELOG-1.2.9.md +0 -4
- package/uscha-kit/CHANGELOG-1.20.0.md +0 -29
- package/uscha-kit/CHANGELOG-1.21.0.md +0 -33
- package/uscha-kit/CHANGELOG-1.22.0.md +0 -60
- package/uscha-kit/CHANGELOG-1.23.0.md +0 -75
- package/uscha-kit/CHANGELOG-1.24.0.md +0 -50
- package/uscha-kit/CHANGELOG-1.25.0.md +0 -55
- package/uscha-kit/CHANGELOG-1.26.0.md +0 -70
- package/uscha-kit/CHANGELOG-1.27.0.md +0 -45
- package/uscha-kit/CHANGELOG-1.28.0.md +0 -35
- package/uscha-kit/CHANGELOG-1.29.0.md +0 -20
- package/uscha-kit/CHANGELOG-1.3.0.md +0 -74
- package/uscha-kit/CHANGELOG-1.30.0.md +0 -46
- package/uscha-kit/CHANGELOG-1.31.0.md +0 -59
- package/uscha-kit/CHANGELOG-1.32.0.md +0 -50
- package/uscha-kit/CHANGELOG-1.33.0.md +0 -46
- package/uscha-kit/CHANGELOG-1.34.0.md +0 -55
- package/uscha-kit/CHANGELOG-1.35.0.md +0 -30
- package/uscha-kit/CHANGELOG-1.36.0.md +0 -33
- package/uscha-kit/CHANGELOG-1.37.0.md +0 -41
- package/uscha-kit/CHANGELOG-1.38.0.md +0 -11
- package/uscha-kit/CHANGELOG-1.39.0.md +0 -14
- package/uscha-kit/CHANGELOG-1.4.0.md +0 -68
- package/uscha-kit/CHANGELOG-1.40.0.md +0 -16
- package/uscha-kit/CHANGELOG-1.40.1.md +0 -11
- package/uscha-kit/CHANGELOG-1.40.2.md +0 -13
- package/uscha-kit/CHANGELOG-1.41.0.md +0 -18
- package/uscha-kit/CHANGELOG-1.41.1.md +0 -53
- package/uscha-kit/CHANGELOG-1.41.2.md +0 -34
- package/uscha-kit/CHANGELOG-1.41.3.md +0 -30
- package/uscha-kit/CHANGELOG-1.42.0.md +0 -41
- package/uscha-kit/CHANGELOG-1.43.0.md +0 -37
- package/uscha-kit/CHANGELOG-1.44.0.md +0 -90
- package/uscha-kit/CHANGELOG-1.44.1.md +0 -26
- package/uscha-kit/CHANGELOG-1.45.0.md +0 -58
- package/uscha-kit/CHANGELOG-1.46.0.md +0 -50
- package/uscha-kit/CHANGELOG-1.46.1.md +0 -35
- package/uscha-kit/CHANGELOG-1.47.0.md +0 -45
- package/uscha-kit/CHANGELOG-1.48.0.md +0 -35
- package/uscha-kit/CHANGELOG-1.48.1.md +0 -55
- package/uscha-kit/CHANGELOG-1.48.2.md +0 -47
- package/uscha-kit/CHANGELOG-1.49.0.md +0 -45
- package/uscha-kit/CHANGELOG-1.5.0.md +0 -64
- package/uscha-kit/CHANGELOG-1.50.0.md +0 -52
- package/uscha-kit/CHANGELOG-1.50.1.md +0 -52
- package/uscha-kit/CHANGELOG-1.50.2.md +0 -62
- package/uscha-kit/CHANGELOG-1.51.0.md +0 -44
- package/uscha-kit/CHANGELOG-1.51.1.md +0 -33
- package/uscha-kit/CHANGELOG-1.6.0.md +0 -57
- package/uscha-kit/CHANGELOG-1.7.0.md +0 -74
- package/uscha-kit/CHANGELOG-1.8.0.md +0 -46
- package/uscha-kit/CHANGELOG-1.9.0.md +0 -112
package/README.md
CHANGED
|
@@ -7,6 +7,9 @@ never what was claimed.
|
|
|
7
7
|
|
|
8
8
|
> The tool executes · the method governs · evidence decides · the human approves.
|
|
9
9
|
|
|
10
|
+
**[uscha.dev](https://uscha.dev)** — the method, the five rules, the skills, the library
|
|
11
|
+
(essay, 2-day dev course, reference, paper).
|
|
12
|
+
|
|
10
13
|
```bash
|
|
11
14
|
npx --yes @andresmassello/uscha@latest install --target claude
|
|
12
15
|
npx --yes @andresmassello/uscha@latest doctor --target claude
|
|
@@ -23,7 +26,9 @@ Requires **Python 3.8+** on the machine (the engine is Python stdlib — no pip
|
|
|
23
26
|
runtime dependencies). The npm package is a thin router; the canonical installer is
|
|
24
27
|
`uscha-kit/install-uscha.py`.
|
|
25
28
|
|
|
26
|
-
**Kit v1.51.
|
|
29
|
+
**Kit v1.51.3** <!-- uscha:version --> · [uscha.dev](https://uscha.dev) ·
|
|
30
|
+
[changelog](https://github.com/andresmassello/uscha/blob/main/uscha-kit/CHANGELOG-1.51.3.md)
|
|
31
|
+
(the per-release changelogs live in the repo, not in the npm tarball)
|
|
27
32
|
|
|
28
33
|
---
|
|
29
34
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresmassello/uscha",
|
|
3
|
-
"version": "1.51.
|
|
3
|
+
"version": "1.51.3",
|
|
4
4
|
"description": "Spec-driven development for LLM coding agents: 9 skills + a stdlib evidence engine. Facts block, guesses advise; the human approves.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"uscha": "bin/uscha.js",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"!uscha-kit/**/__pycache__/",
|
|
14
14
|
"!uscha-kit/**/*.pyc",
|
|
15
15
|
"!uscha-kit/**/*.pyo",
|
|
16
|
+
"!uscha-kit/CHANGELOG-*.md",
|
|
16
17
|
"README.md",
|
|
17
18
|
"LICENSE"
|
|
18
19
|
],
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
"type": "git",
|
|
21
22
|
"url": "git+https://github.com/andresmassello/uscha.git"
|
|
22
23
|
},
|
|
23
|
-
"homepage": "https://
|
|
24
|
+
"homepage": "https://uscha.dev",
|
|
24
25
|
"bugs": {
|
|
25
26
|
"url": "https://github.com/andresmassello/uscha/issues"
|
|
26
27
|
},
|
|
@@ -76,7 +76,8 @@ wires the JSON the engine emits into the template. Read-only.
|
|
|
76
76
|
|
|
77
77
|
4. **Render `mirador.html`** with the standalone renderer — it runs `dashboard --json`,
|
|
78
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
|
|
79
|
+
absolute path (`OPEN IT: ...`), and opens it in the default browser **the first time it
|
|
80
|
+
creates the file** (see step 5 — a re-render updates the open tab instead). From the project root,
|
|
80
81
|
with no long paths — `--engine` and `--template` default to the renderer's sibling skill
|
|
81
82
|
files (kit 1.41.2):
|
|
82
83
|
```bash
|
|
@@ -87,9 +88,15 @@ wires the JSON the engine emits into the template. Read-only.
|
|
|
87
88
|
time-lapse feeds from `qa_ledger.py readiness --record` — since kit 1.47.0 the dev-loop
|
|
88
89
|
records at every pass close, so history accumulates without extra ceremony.
|
|
89
90
|
|
|
90
|
-
5. **Where to look:** the renderer
|
|
91
|
-
on the `OPEN IT:` line
|
|
92
|
-
|
|
91
|
+
5. **Where to look:** the renderer opens `mirador.html` **once — only the first time it is
|
|
92
|
+
created** — and always prints its absolute path on the `OPEN IT:` line; surface that path to
|
|
93
|
+
the operator. Re-rendering (this skill invoked again on a later pass, or the watch loop)
|
|
94
|
+
rewrites the SAME file, and the page's built-in auto-refresh reloads the already-open tab in
|
|
95
|
+
place, so a re-render never spawns a new browser tab. On a fresh session the file is already
|
|
96
|
+
on disk, so nothing pops — open the printed path once, or pass `--open` (`uscha mirador
|
|
97
|
+
--open`) to force a reopen when you closed the tab. `--no-open` suppresses opening
|
|
98
|
+
everywhere and wins over `--open` (headless/CI, or the watch loop, which passes it);
|
|
99
|
+
`--refresh 0` writes a frozen snapshot with no auto-reload.
|
|
93
100
|
|
|
94
101
|
## For a human at a terminal: `uscha mirador`
|
|
95
102
|
|
|
@@ -94,10 +94,13 @@ def main():
|
|
|
94
94
|
help="path to mirador.template.html (default: the sibling template)")
|
|
95
95
|
ap.add_argument("--out", default="mirador.html")
|
|
96
96
|
ap.add_argument("--sidecar", default=os.path.join(".uscha", "telemetry.jsonl"))
|
|
97
|
-
ap.add_argument("--refresh", type=int, default=
|
|
98
|
-
help="
|
|
97
|
+
ap.add_argument("--refresh", type=int, default=10,
|
|
98
|
+
help="auto-reload the open tab every N seconds so a SINGLE tab stays live "
|
|
99
|
+
"(0 = frozen snapshot; default 10)")
|
|
99
100
|
ap.add_argument("--no-open", action="store_true",
|
|
100
101
|
help="write the file but do not open it in a browser")
|
|
102
|
+
ap.add_argument("--open", dest="force_open", action="store_true",
|
|
103
|
+
help="open the browser even if the file already existed (the tab was closed)")
|
|
101
104
|
args = ap.parse_args()
|
|
102
105
|
|
|
103
106
|
try:
|
|
@@ -127,10 +130,12 @@ def main():
|
|
|
127
130
|
out = re.sub(r"/\*MIRADOR_DATA_START\*/.*?/\*MIRADOR_DATA_END\*/",
|
|
128
131
|
lambda m: payload, tpl, count=1, flags=re.S)
|
|
129
132
|
if args.refresh and args.refresh > 0:
|
|
130
|
-
#
|
|
133
|
+
# a meta-refresh so the ONE open tab reloads itself in place (on by default) --
|
|
134
|
+
# pass --refresh 0 for a frozen snapshot
|
|
131
135
|
out = out.replace("</head>",
|
|
132
136
|
f'<meta http-equiv="refresh" content="{args.refresh}">\n</head>', 1)
|
|
133
|
-
|
|
137
|
+
# capture BEFORE writing: auto-open fires only when the mirador is FIRST materialized
|
|
138
|
+
pre_existed = os.path.exists(args.out)
|
|
134
139
|
with open(args.out, "w", encoding="utf-8") as f:
|
|
135
140
|
f.write(out)
|
|
136
141
|
score = (data.get("readiness") or {}).get("score")
|
|
@@ -139,10 +144,13 @@ def main():
|
|
|
139
144
|
print(f"[mirador-render] readiness {score} | telemetry {'on' if tel else 'off'} | "
|
|
140
145
|
f"refresh {args.refresh or 'off'}")
|
|
141
146
|
print(f"[mirador-render] OPEN IT: {out_abs}")
|
|
142
|
-
# Auto-open
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
147
|
+
# Auto-open ONLY the first time the file is created. Repeated renders (the agent per
|
|
148
|
+
# pass, or a watch loop) rewrite the SAME file, and the meta-refresh reloads the already-
|
|
149
|
+
# open tab in place -- so a re-render never spawns a new browser tab (kit 1.51.2; 1.41.3
|
|
150
|
+
# gated this on --refresh, which spammed a tab per one-shot re-render). `pre_existed`
|
|
151
|
+
# tracks the FILE, not a live tab, so --open forces a reopen when the tab was closed;
|
|
152
|
+
# --no-open suppresses everywhere and wins over it. The OPEN IT path is always printed.
|
|
153
|
+
if not args.no_open and (args.force_open or not pre_existed):
|
|
146
154
|
_open_best_effort(out_abs)
|
|
147
155
|
return 0
|
|
148
156
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "uscha",
|
|
4
|
-
"version": "1.51.
|
|
4
|
+
"version": "1.51.3",
|
|
5
5
|
"displayName": "Uscha",
|
|
6
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, 29 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"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://uscha.dev",
|
|
12
12
|
"repository": "https://github.com/andresmassello/uscha",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"keywords": [
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uscha",
|
|
3
|
-
"version": "1.51.
|
|
3
|
+
"version": "1.51.3",
|
|
4
4
|
"description": "Uscha spec-driven development methodology for coding agents. Includes npm/npx router.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Andres Massello",
|
|
7
7
|
"url": "https://github.com/andresmassello"
|
|
8
8
|
},
|
|
9
|
-
"homepage": "https://
|
|
9
|
+
"homepage": "https://uscha.dev",
|
|
10
10
|
"repository": "https://github.com/andresmassello/uscha",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"keywords": [
|
package/uscha-kit/INSTALL.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
Uscha installs as a machine-level helper for coding agents. The recommended path
|
|
4
4
|
is npm/npx because it works the same on a fresh Codex or Claude Code machine.
|
|
5
5
|
|
|
6
|
+
The method itself — the paradigm, the five rules, the skills and the library — lives at
|
|
7
|
+
**[uscha.dev](https://uscha.dev)**.
|
|
8
|
+
|
|
6
9
|
## Quick path
|
|
7
10
|
|
|
8
11
|
### Codex Desktop
|
package/uscha-kit/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# uscha-kit
|
|
2
2
|
|
|
3
|
-
**Kit version:** v1.51.
|
|
3
|
+
**Kit version:** v1.51.3 <!-- 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`,
|
package/uscha-kit/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
uscha-kit 1.51.
|
|
1
|
+
uscha-kit 1.51.3
|
|
@@ -760,8 +760,11 @@ def cmd_mirador(args):
|
|
|
760
760
|
raise SystemExit(1)
|
|
761
761
|
base = [sys.executable, str(render), "--ledger", args.ledger, "--out", args.out]
|
|
762
762
|
if not args.watch:
|
|
763
|
-
# one-shot: the renderer writes the file and opens it
|
|
764
|
-
|
|
763
|
+
# one-shot: the renderer writes the file and opens it only when FIRST materializing it
|
|
764
|
+
# -- a re-render updates the already-open tab in place instead of spawning another
|
|
765
|
+
# (kit 1.51.2). --no-open suppresses; --open forces a reopen when the tab was closed.
|
|
766
|
+
extra = ["--no-open"] if args.no_open else (["--open"] if args.force_open else [])
|
|
767
|
+
rc = subprocess.call(base + extra)
|
|
765
768
|
if rc:
|
|
766
769
|
raise SystemExit(rc)
|
|
767
770
|
return
|
|
@@ -788,7 +791,8 @@ def next_steps(target):
|
|
|
788
791
|
if "codex" in picked: steps.append("Codex: restart or open a new thread, then install/use uscha from the Personal marketplace if needed.")
|
|
789
792
|
if "claude" in picked: steps.append("Claude: restart Claude Code so global skills/hooks are reloaded.")
|
|
790
793
|
if "pi" in picked: steps.append("pi: restart pi; the 9 uscha-* skills load from ~/.agents/skills. INV-GOLDEN-01 is advisory until the tool_call extension is installed (doctor reports golden_guard).")
|
|
791
|
-
return steps + ["Run: python install-uscha.py doctor --target %s" % target
|
|
794
|
+
return steps + ["Run: python install-uscha.py doctor --target %s" % target,
|
|
795
|
+
"Learn the method: https://uscha.dev"]
|
|
792
796
|
|
|
793
797
|
|
|
794
798
|
def emit(data, as_json):
|
|
@@ -820,6 +824,8 @@ def build_parser():
|
|
|
820
824
|
mirador.add_argument("--watch", action="store_true", help="live second-screen view: re-render every --interval seconds")
|
|
821
825
|
mirador.add_argument("--interval", type=int, default=30)
|
|
822
826
|
mirador.add_argument("--no-open", action="store_true", help="write the file but do not open a browser")
|
|
827
|
+
mirador.add_argument("--open", dest="force_open", action="store_true",
|
|
828
|
+
help="open the browser even if mirador.html already existed (you closed the tab)")
|
|
823
829
|
mirador.set_defaults(func=cmd_mirador)
|
|
824
830
|
return parser
|
|
825
831
|
|
|
@@ -76,7 +76,8 @@ wires the JSON the engine emits into the template. Read-only.
|
|
|
76
76
|
|
|
77
77
|
4. **Render `mirador.html`** with the standalone renderer — it runs `dashboard --json`,
|
|
78
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
|
|
79
|
+
absolute path (`OPEN IT: ...`), and opens it in the default browser **the first time it
|
|
80
|
+
creates the file** (see step 5 — a re-render updates the open tab instead). From the project root,
|
|
80
81
|
with no long paths — `--engine` and `--template` default to the renderer's sibling skill
|
|
81
82
|
files (kit 1.41.2):
|
|
82
83
|
```bash
|
|
@@ -87,9 +88,15 @@ wires the JSON the engine emits into the template. Read-only.
|
|
|
87
88
|
time-lapse feeds from `qa_ledger.py readiness --record` — since kit 1.47.0 the dev-loop
|
|
88
89
|
records at every pass close, so history accumulates without extra ceremony.
|
|
89
90
|
|
|
90
|
-
5. **Where to look:** the renderer
|
|
91
|
-
on the `OPEN IT:` line
|
|
92
|
-
|
|
91
|
+
5. **Where to look:** the renderer opens `mirador.html` **once — only the first time it is
|
|
92
|
+
created** — and always prints its absolute path on the `OPEN IT:` line; surface that path to
|
|
93
|
+
the operator. Re-rendering (this skill invoked again on a later pass, or the watch loop)
|
|
94
|
+
rewrites the SAME file, and the page's built-in auto-refresh reloads the already-open tab in
|
|
95
|
+
place, so a re-render never spawns a new browser tab. On a fresh session the file is already
|
|
96
|
+
on disk, so nothing pops — open the printed path once, or pass `--open` (`uscha mirador
|
|
97
|
+
--open`) to force a reopen when you closed the tab. `--no-open` suppresses opening
|
|
98
|
+
everywhere and wins over `--open` (headless/CI, or the watch loop, which passes it);
|
|
99
|
+
`--refresh 0` writes a frozen snapshot with no auto-reload.
|
|
93
100
|
|
|
94
101
|
## For a human at a terminal: `uscha mirador`
|
|
95
102
|
|
|
@@ -94,10 +94,13 @@ def main():
|
|
|
94
94
|
help="path to mirador.template.html (default: the sibling template)")
|
|
95
95
|
ap.add_argument("--out", default="mirador.html")
|
|
96
96
|
ap.add_argument("--sidecar", default=os.path.join(".uscha", "telemetry.jsonl"))
|
|
97
|
-
ap.add_argument("--refresh", type=int, default=
|
|
98
|
-
help="
|
|
97
|
+
ap.add_argument("--refresh", type=int, default=10,
|
|
98
|
+
help="auto-reload the open tab every N seconds so a SINGLE tab stays live "
|
|
99
|
+
"(0 = frozen snapshot; default 10)")
|
|
99
100
|
ap.add_argument("--no-open", action="store_true",
|
|
100
101
|
help="write the file but do not open it in a browser")
|
|
102
|
+
ap.add_argument("--open", dest="force_open", action="store_true",
|
|
103
|
+
help="open the browser even if the file already existed (the tab was closed)")
|
|
101
104
|
args = ap.parse_args()
|
|
102
105
|
|
|
103
106
|
try:
|
|
@@ -127,10 +130,12 @@ def main():
|
|
|
127
130
|
out = re.sub(r"/\*MIRADOR_DATA_START\*/.*?/\*MIRADOR_DATA_END\*/",
|
|
128
131
|
lambda m: payload, tpl, count=1, flags=re.S)
|
|
129
132
|
if args.refresh and args.refresh > 0:
|
|
130
|
-
#
|
|
133
|
+
# a meta-refresh so the ONE open tab reloads itself in place (on by default) --
|
|
134
|
+
# pass --refresh 0 for a frozen snapshot
|
|
131
135
|
out = out.replace("</head>",
|
|
132
136
|
f'<meta http-equiv="refresh" content="{args.refresh}">\n</head>', 1)
|
|
133
|
-
|
|
137
|
+
# capture BEFORE writing: auto-open fires only when the mirador is FIRST materialized
|
|
138
|
+
pre_existed = os.path.exists(args.out)
|
|
134
139
|
with open(args.out, "w", encoding="utf-8") as f:
|
|
135
140
|
f.write(out)
|
|
136
141
|
score = (data.get("readiness") or {}).get("score")
|
|
@@ -139,10 +144,13 @@ def main():
|
|
|
139
144
|
print(f"[mirador-render] readiness {score} | telemetry {'on' if tel else 'off'} | "
|
|
140
145
|
f"refresh {args.refresh or 'off'}")
|
|
141
146
|
print(f"[mirador-render] OPEN IT: {out_abs}")
|
|
142
|
-
# Auto-open
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
|
|
147
|
+
# Auto-open ONLY the first time the file is created. Repeated renders (the agent per
|
|
148
|
+
# pass, or a watch loop) rewrite the SAME file, and the meta-refresh reloads the already-
|
|
149
|
+
# open tab in place -- so a re-render never spawns a new browser tab (kit 1.51.2; 1.41.3
|
|
150
|
+
# gated this on --refresh, which spammed a tab per one-shot re-render). `pre_existed`
|
|
151
|
+
# tracks the FILE, not a live tab, so --open forces a reopen when the tab was closed;
|
|
152
|
+
# --no-open suppresses everywhere and wins over it. The OPEN IT path is always printed.
|
|
153
|
+
if not args.no_open and (args.force_open or not pre_existed):
|
|
146
154
|
_open_best_effort(out_abs)
|
|
147
155
|
return 0
|
|
148
156
|
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# dev-loop-kit 1.10.0 — acceptance trazable: AC-n cierra por testcase MEDIDO (2026-07-02)
|
|
2
|
-
|
|
3
|
-
Primera mejora del backlog PragProg (M2 de `docs/analisis-pragmatic-programmer.md`;
|
|
4
|
-
Topic 50 "Do What Works" + la anécdota Jeffries/Sudoku + Tip 94 "Find Bugs Once").
|
|
5
|
-
Ataca el modo de falla típico del agente: **pulir la métrica sin acercarse a la
|
|
6
|
-
solución**. El readiness deja de estar dominado por coverage/tests-verdes y pasa a
|
|
7
|
-
estar dominado por **criterios de aceptación cerrados con evidencia medida**.
|
|
8
|
-
Smoke suite: 68/68.
|
|
9
|
-
|
|
10
|
-
## La idea (measured beats narrated, ahora a nivel CRITERIO)
|
|
11
|
-
|
|
12
|
-
- Cada criterio de `ACCEPTANCE.md` lleva un ID estable: `- [ ] AC-01 — cuando X
|
|
13
|
-
entonces Y`.
|
|
14
|
-
- Un criterio cierra **MEDIDO** solo cuando existe ≥1 testcase VERDE cuyo nombre
|
|
15
|
-
lleva el tag (`test_ac1_x`, `testAC01X`, `"AC-01: ..."`) en los reportes JUnit
|
|
16
|
-
que el engine ya ingiere — y **ningún** testcase taggeado en rojo (evidencia
|
|
17
|
-
roja veta: fail-closed).
|
|
18
|
-
- El checkbox es RELATO; el testcase es HECHO. Un `[x]` sin test verde se
|
|
19
|
-
reporta como `narrated_only` y NO cierra.
|
|
20
|
-
|
|
21
|
-
## Engine (qa_ledger.py)
|
|
22
|
-
|
|
23
|
-
- `_parse_acceptance_items()`: parser de checkboxes con ID opcional; IDs
|
|
24
|
-
normalizados por número (`AC-01 == AC_1 == ac1` — los nombres de test de
|
|
25
|
-
python/go no admiten `-`).
|
|
26
|
-
- `_ac_tags()`: scan de NOMBRES de testcase en los reportes JUnit por type
|
|
27
|
-
(reusa el selector de ubicaciones vía `_junit_report_files()`, extraído para
|
|
28
|
-
no duplicar la lista — surefire/gradle per-clase, junit-family, dual-file
|
|
29
|
-
swift; flutter no emite JUnit → sus criterios no cierran medido, documentado).
|
|
30
|
-
Boundaries explícitos en el regex del tag: `\b` NO sirve (`_` es word char y
|
|
31
|
-
`test_ac1` quedaría invisible); soporta separador no-alfanumérico y camelCase.
|
|
32
|
-
- `readiness`: nueva dimensión **acceptance** (dominante, peso 30) = criterios
|
|
33
|
-
cerrados medidos / criterios totales (un criterio sin ID no puede cerrar →
|
|
34
|
-
cuenta como abierto). Pesos default rebalanceados:
|
|
35
|
-
acceptance 30 · adr 15 · coverage 15 · static 20 · convergencia 10 ·
|
|
36
|
-
integración 10 — el techo a coverage/verde es el anti-Goodhart. JSON expone
|
|
37
|
-
`traceable/ids/measured_closed/narrated_only/measured_unchecked/untagged`;
|
|
38
|
-
warnings en texto para narrated-only y sin-IDs.
|
|
39
|
-
- **Fallback legacy**: ACCEPTANCE sin ningún AC-ID → la dimensión cae al ratio
|
|
40
|
-
de checkboxes con warning (adopción incremental, no rotura retroactiva).
|
|
41
|
-
- `spec-check --acceptance ACCEPTANCE.md`: la trazabilidad es estructura =
|
|
42
|
-
FACT → bloquea: archivo ausente, cero criterios, CERO criterios trazables,
|
|
43
|
-
IDs duplicados (normalizados). Criterios sueltos sin ID = advisory. Puede
|
|
44
|
-
correr solo (sin `--spec`).
|
|
45
|
-
|
|
46
|
-
## Skills / docs
|
|
47
|
-
|
|
48
|
-
- `discovery`: ACCEPTANCE se genera con AC-NN secuenciales, nunca reusados;
|
|
49
|
-
cada criterio pensado para ser cubrible por un test con nombre.
|
|
50
|
-
- `dev-loop`: al escribir los tests de un criterio, el tag AC-n va en el nombre
|
|
51
|
-
del test; `spec-check --acceptance` al arrancar.
|
|
52
|
-
|
|
53
|
-
## Hardening (review fresco pre-commit, 10 hallazgos aplicados)
|
|
54
|
-
|
|
55
|
-
- `_ac_tags`: el tag ahora lee SOLO el nombre del testcase (nunca classname) —
|
|
56
|
-
un módulo/clase que matchea "ACn" por coincidencia (`test_ac3_flow.py`) ya no
|
|
57
|
-
contamina los OTROS tests del mismo archivo.
|
|
58
|
-
- `_AC_ID`: tolera IDs markdown-formateados (`**AC-01**`, `` `AC-01` ``) — antes
|
|
59
|
-
degradaban en silencio a `id=None` y toda la trazabilidad caía a legacy.
|
|
60
|
-
- `readiness`: IDs duplicados en ACCEPTANCE cuentan **una sola vez** (antes un
|
|
61
|
-
test verde podía cerrar "medido" tantos criterios como copias del ID).
|
|
62
|
-
- `readiness`: config pre-1.10.0 con `readiness_weights` explícitos que no
|
|
63
|
-
conocían `acceptance` ya no la reciben inyectada por default — se excluye
|
|
64
|
-
(peso 0) con warning hasta que el usuario la agregue o taggee AC-IDs (si no,
|
|
65
|
-
duplicaba el peso de `adr` en silencio).
|
|
66
|
-
- `readiness`: `--section` sin match ahora avisa (`0 criterios en scope`) en
|
|
67
|
-
vez de zonear en silencio adr+acceptance.
|
|
68
|
-
- `readiness`: ledger sin `config.repos` ya no crashea (KeyError) — usa
|
|
69
|
-
`.get("repos", [])` como el resto del comando.
|
|
70
|
-
- `spec-check --acceptance`: pipear un SPEC por stdin junto con `--acceptance`
|
|
71
|
-
ya no se descarta en silencio — se lee stdin salvo modo interactivo puro
|
|
72
|
-
acceptance-only.
|
|
73
|
-
- `spec-check --acceptance --strict`: los criterios sin AC-ID ahora gatean
|
|
74
|
-
`--strict` (antes el verdict imprimía "OK" con advisories pendientes).
|
|
75
|
-
- Documentado (no resuelto): reportes JUnit stale de maven/gradle pueden
|
|
76
|
-
vetear/cerrar un AC sin evidencia vigente — mismo límite que
|
|
77
|
-
`junit_test_count`, ahora con blast radius mayor. Mitigación real (mtime +
|
|
78
|
-
correlación con el árbol de fuentes) diferida.
|
|
79
|
-
|
|
80
|
-
## Diferido consciente
|
|
81
|
-
|
|
82
|
-
- El resto del backlog PragProg (M1 regression-capture, M3 ledger atómico,
|
|
83
|
-
M8 secret-scan, M9 tests fuera del presupuesto de simplicity, etc.) sigue en
|
|
84
|
-
`docs/analisis-pragmatic-programmer.md` — una mejora por release.
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# dev-loop-kit 1.11.0 — tests fuera del presupuesto de simplicity (2026-07-03)
|
|
2
|
-
|
|
3
|
-
Segunda mejora del backlog PragProg (M9 de `docs/analisis-pragmatic-programmer.md`;
|
|
4
|
-
Topic 51: *"un buen proyecto puede tener MÁS código de test que de producción, y
|
|
5
|
-
vale la pena"*). Elimina un **incentivo perverso activo**: el simplicity-check
|
|
6
|
-
contaba las líneas de test junto a las de producción contra un único presupuesto —
|
|
7
|
-
el gate castigaba escribir tests y empujaba al agente a testear menos para pasar.
|
|
8
|
-
Smoke suite: 73/73.
|
|
9
|
-
|
|
10
|
-
## La idea
|
|
11
|
-
|
|
12
|
-
- Escribir tests **nunca** acerca un diff a OVERBUILT. Los archivos de test se
|
|
13
|
-
detectan, se cuentan y se **reportan aparte** (`test_lines_added`,
|
|
14
|
-
`test_files_changed`) — pero no gatean ninguna dimensión del score.
|
|
15
|
-
- La otra dirección ya estaba protegida: **borrar** tests lo bloquea gate-check.
|
|
16
|
-
Con esto el incentivo queda alineado en ambas direcciones.
|
|
17
|
-
|
|
18
|
-
## Engine (qa_ledger.py)
|
|
19
|
-
|
|
20
|
-
- `_is_simplicity_test_file()`: clasificador type-agnóstico (el diff no trae
|
|
21
|
-
`repo_type`) — unión de las convenciones de los 9 stacks: dirs
|
|
22
|
-
`test/tests/__tests__/Tests/*.Tests`, source sets Gradle (`src/*Test/`),
|
|
23
|
-
`test_*.py`, `*_test.go`, `*.test.ts`/`*.spec.js` (multi-dot incluido),
|
|
24
|
-
`*Test.java`/`*Tests.cs` CamelCase **case-sensitive** — `backtest.cpp` /
|
|
25
|
-
`protest.cc` siguen contando como producción (misma trampa que ya evitan
|
|
26
|
-
dotnet/cpp en `_is_test_path`).
|
|
27
|
-
- Dirección de fallo benigna y documentada: un falso positivo solo EXIME del
|
|
28
|
-
presupuesto — nunca bloquea ni borra nada.
|
|
29
|
-
- `_simplicity_metrics()`: tercer estado de conteo (`prod`/`test`/fuera);
|
|
30
|
-
las líneas de test no alimentan `lines_added`, `net_lines`, `files_changed`,
|
|
31
|
-
`max_nesting`, `max_hunk_added` ni abstracciones. Output humano: línea
|
|
32
|
-
informativa "tests FUERA del presupuesto: +N líneas en M archivo(s)".
|
|
33
|
-
|
|
34
|
-
## Smoke
|
|
35
|
-
|
|
36
|
-
- **T32**: diff sintético 6 líneas prod + 302 de test → el presupuesto ve 6/1;
|
|
37
|
-
batería del clasificador (9 convenciones positivas + backtest/protest/Engine
|
|
38
|
-
negativas).
|
|
39
|
-
- **T31** (edges 1.10.0, deuda del release anterior): batería de falsos
|
|
40
|
-
positivos del tag regex (`HVAC2`, `mac1`, `track12` no taggean), classname
|
|
41
|
-
jamás taggea, y semántica flaky de surefire (`<flakyFailure>` que pasó tras
|
|
42
|
-
retry = verde; `<failure>`+`<rerunFailure>` = rojo, veta).
|
|
43
|
-
|
|
44
|
-
## Hardening (review fresco pre-commit)
|
|
45
|
-
|
|
46
|
-
- El review detectó que gate-check tenía SU PROPIO clasificador de tests
|
|
47
|
-
(`_gc_is_test_file`) más débil: no reconocía `foo_test.go` (Go),
|
|
48
|
-
`*.Tests/*.cs` (dotnet), `*.spec.tsx` ni `__tests__/` — así que "borrar
|
|
49
|
-
tests lo bloquea gate-check" era overclaim para 4+ stacks. Fix: unión
|
|
50
|
-
fail-closed — gate-check reusa el clasificador compartido de los 9 stacks
|
|
51
|
-
MÁS sus sufijos legacy; solo se AMPLÍA qué cuenta como test, ningún path
|
|
52
|
-
antes protegido se desprotege.
|
|
53
|
-
- `_GC_TESTDEF` ampliado con las definiciones de test que faltaban:
|
|
54
|
-
`func TestX` (Go), `[Fact]`/`[Theory]` (xunit), `#[test]` (rust),
|
|
55
|
-
`it(`/`test(`/`describe(` (js) — seguro porque TESTDEF solo se evalúa
|
|
56
|
-
dentro de archivos ya clasificados como test.
|
|
57
|
-
- Smoke **T33**: borrado de tests Go/dotnet/JS → BLOCKER (antes invisible).
|
|
58
|
-
- Corrección truth-pass en `dev-loop-kit/README.md`: los pesos documentados
|
|
59
|
-
de simplicity (`diff_size 30, nesting 25, abstraction 20...`) no coincidían
|
|
60
|
-
con el engine (`35/30/20/8/7`, abstraction advisory sin peso) — drift
|
|
61
|
-
pre-existente, alineado acá.
|
|
62
|
-
|
|
63
|
-
## Diferido consciente
|
|
64
|
-
|
|
65
|
-
- El resto del backlog PragProg (M1 regression-capture, M3 ledger atómico,
|
|
66
|
-
M8 secret-scan, etc.) sigue en `docs/analisis-pragmatic-programmer.md` —
|
|
67
|
-
una mejora por release.
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# dev-loop-kit 1.12.0 — secret-scan en gate-check (2026-07-03)
|
|
2
|
-
|
|
3
|
-
Tercera mejora del backlog PragProg (M8 de `docs/analisis-pragmatic-programmer.md`;
|
|
4
|
-
Topic 43 *"Stay Safe Out There — nunca commitear secretos, API keys ni
|
|
5
|
-
credenciales"*). Un secreto agregado al diff bloquea como HECHO, exactamente
|
|
6
|
-
igual que hoy bloquea borrar tests o bajar thresholds. Python stdlib puro.
|
|
7
|
-
Smoke suite: 79/79.
|
|
8
|
-
|
|
9
|
-
## La idea (facts block, guesses advise — aplicado a secretos)
|
|
10
|
-
|
|
11
|
-
- **Alta precisión = BLOCKER**: clave privada PEM, AWS access key (`AKIA…`),
|
|
12
|
-
GitHub token (`ghp_`/`github_pat_`), Slack (`xox?-`), Google API key
|
|
13
|
-
(`AIza…`), y archivos contenedores de claves (`.p12/.pfx/.jks/.keystore/.key`)
|
|
14
|
-
agregados o modificados — también en modo binario (línea `Binary files`,
|
|
15
|
-
que no trae `+++`).
|
|
16
|
-
- **Genérico = advisory**: literales tipo `password = "…"` y JWTs — en
|
|
17
|
-
fixtures de test abundan placeholders y bloquearlos castigaría escribir
|
|
18
|
-
tests. `--strict` los gatea.
|
|
19
|
-
- Solo se escanean líneas **agregadas**: sacar un secreto del código es bueno
|
|
20
|
-
y el diff que lo saca no debe frenarse. Borrar un `.p12` tampoco bloquea
|
|
21
|
-
(el lado `b/` del diff es `/dev/null` y no matchea).
|
|
22
|
-
|
|
23
|
-
## Engine (qa_ledger.py)
|
|
24
|
-
|
|
25
|
-
- `_GC_SECRETS_HARD` (lista etiquetada de patrones) + `_GC_SECRET_SOFT` +
|
|
26
|
-
`_GC_KEYFILE`, cableados al loop de `cmd_gate_check`: `secrets_added` suma
|
|
27
|
-
al veredicto hard, `secret_literals` al soft. JSON expone ambos.
|
|
28
|
-
- Los patrones no se auto-matchean como source (verificado): después de
|
|
29
|
-
`-----BEGIN ` en el código viene `(?:RSA`, no `PRIVATE KEY`.
|
|
30
|
-
- Límite conocido (corner self-hosting): los fixtures del smoke contienen la
|
|
31
|
-
AKIA de ejemplo canónica de AWS — un gate-check del diff del PROPIO kit
|
|
32
|
-
la flaggea. Correcto: el gate no exime archivos de test a propósito
|
|
33
|
-
(un secreto en un test sigue siendo un secreto).
|
|
34
|
-
|
|
35
|
-
## Smoke
|
|
36
|
-
|
|
37
|
-
- **T34**: AKIA agregada → BLOCKER · PEM privado → BLOCKER · `.p12` binario
|
|
38
|
-
agregado → BLOCKER · borrado de `.p12` → CLEAN · literal password →
|
|
39
|
-
REVIEW exit 0 / `--strict` exit 1.
|
|
40
|
-
|
|
41
|
-
## Diferido consciente
|
|
42
|
-
|
|
43
|
-
- Entropía/base64 genérico (detectores adivinos) NO entra: violaría
|
|
44
|
-
"facts block, guesses advise" — solo patrones con lectura inequívoca
|
|
45
|
-
bloquean. El resto del backlog PragProg sigue en
|
|
46
|
-
`docs/analisis-pragmatic-programmer.md`.
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# dev-loop-kit 1.13.0 — ledger atómico: checksum de integridad (2026-07-03)
|
|
2
|
-
|
|
3
|
-
Cuarta mejora del backlog PragProg (M3 de `docs/analisis-pragmatic-programmer.md`;
|
|
4
|
-
Topic 34 *"los recursos compartidos mutables incluyen ARCHIVOS"*). Todo el
|
|
5
|
-
edificio "measured beats narrated" se apoya en `QA-LEDGER.json` — y hasta hoy
|
|
6
|
-
ese JSON podía corromperse o mutarse en silencio. Smoke suite: 85/85.
|
|
7
|
-
|
|
8
|
-
## La idea
|
|
9
|
-
|
|
10
|
-
- La **escritura ya era atómica** (write-temp + `os.replace`, desde antes) —
|
|
11
|
-
lo que faltaba era el otro lado: detectar al CARGAR que el archivo quedó
|
|
12
|
-
inconsistente.
|
|
13
|
-
- `_save` ahora escribe un campo `integrity` con **sha256 canónico** del
|
|
14
|
-
contenido (claves ordenadas — el hash no depende del orden del dict).
|
|
15
|
-
- `_load` **verifica** el checksum cuando el campo existe: una mutación externa
|
|
16
|
-
(edición a mano, merge accidental) o una escritura parcial **bloquea** con
|
|
17
|
-
mensaje de recuperación (`git checkout -- QA-LEDGER.json`). Aceptar una
|
|
18
|
-
edición externa deliberada = borrar el campo `integrity` (acto humano
|
|
19
|
-
explícito, mismo espíritu que INV-GOLDEN-01).
|
|
20
|
-
- JSON corrupto/truncado = hecho bloqueante con mensaje claro, **no un
|
|
21
|
-
traceback crudo**.
|
|
22
|
-
- **Legacy**: ledgers pre-1.13.0 sin `integrity` cargan sin verificar
|
|
23
|
-
(adopción incremental, no rotura retroactiva). `init` re-inicializa sobre
|
|
24
|
-
un ledger corrupto sin leerlo.
|
|
25
|
-
|
|
26
|
-
## Diferido consciente
|
|
27
|
-
|
|
28
|
-
- File-lock inter-proceso (la parte "opcional" de M3): omitido — stdlib
|
|
29
|
-
portable Windows/Linux no lo da barato (`fcntl` no existe en Windows) y el
|
|
30
|
-
loop es single-writer por diseño. Si aparece un caso real de escritura
|
|
31
|
-
concurrente, se re-evalúa.
|
|
32
|
-
- El baseline de `rebuild` (artefacto aparte) no lleva checksum — mismo
|
|
33
|
-
candidato si el rebuild gana peso.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# dev-loop-kit 1.14.0 — plateau y stop-signal en readiness (2026-07-03)
|
|
2
|
-
|
|
3
|
-
Quinta mejora del backlog PragProg (M6 de `docs/analisis-pragmatic-programmer.md`;
|
|
4
|
-
Topic 37 *"Listen to Your Lizard Brain"* + Topic 5 *"Know When to Stop"*).
|
|
5
|
-
La convergencia per-tool existía, pero el engine nunca decía las dos cosas que
|
|
6
|
-
un senior sí dice: "esto no está convergiendo — el problema es de diseño" y
|
|
7
|
-
"esto ya está — cortá". **ADVISORY puro: recomienda, jamás gatea.**
|
|
8
|
-
Smoke suite: 89/89.
|
|
9
|
-
|
|
10
|
-
## La idea
|
|
11
|
-
|
|
12
|
-
- **stall-check**: findings gateados por CICLO de agente (suma sobre las tools
|
|
13
|
-
del ciclo). Si los últimos 3 ciclos muestran la serie plana o SUBIENDO — y
|
|
14
|
-
todavía hay findings — iterar más no está acercando la solución: el engine
|
|
15
|
-
deja de sugerir implícitamente "seguí iterando" y recomienda **volver a
|
|
16
|
-
ADR / re-planear con el humano**. Una serie bajando es progreso y no dispara.
|
|
17
|
-
Con `qa_tools_order` configurado solo cuentan ciclos **COMPLETOS** (todas las
|
|
18
|
-
tools logueadas) — un ciclo a medio correr suma parcial y podría enmascarar
|
|
19
|
-
o inventar el stall (hallazgo del review fresco, aplicado).
|
|
20
|
-
- **stop-signal**: todos los repos convergieron, cero caps activos y cero
|
|
21
|
-
findings gateados abiertos — no queda ningún fact bloqueante. Lo que falte
|
|
22
|
-
es deuda medible (coverage/acceptance), no findings: **candidato a cortar e
|
|
23
|
-
ir a PR**, decisión del humano.
|
|
24
|
-
|
|
25
|
-
## Engine (qa_ledger.py)
|
|
26
|
-
|
|
27
|
-
- `_stall_series()` / `_is_stalled()` (ventana `STALL_WINDOW = 3`, constante:
|
|
28
|
-
es un advisory, no un gate parametrizable — cf. tensión ETC/M5, deliberado).
|
|
29
|
-
- `readiness`: JSON expone `advice: {stalled_repos, stop_signal}`; el texto
|
|
30
|
-
imprime ambos avisos marcados "(advisory)".
|
|
31
|
-
|
|
32
|
-
## Smoke
|
|
33
|
-
|
|
34
|
-
- **T36**: serie 4→5→6 dispara stall · un ciclo 4 PARCIAL no contamina la
|
|
35
|
-
serie · serie 5→3→1 (progreso) NO dispara · repo único convergido con cero
|
|
36
|
-
facts bloqueantes emite `stop_signal: true`.
|
|
37
|
-
|
|
38
|
-
## Diferido consciente
|
|
39
|
-
|
|
40
|
-
- El stall mide findings gateados por ciclo, no el score de readiness
|
|
41
|
-
persistido por iteración (el ledger no guarda score histórico — si algún
|
|
42
|
-
día lo guarda, el detector puede leer el KPI directo).
|