@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.
Files changed (82) hide show
  1. package/README.md +6 -1
  2. package/package.json +3 -2
  3. package/uscha-kit/.claude/skills/uscha-mirador/SKILL.md +11 -4
  4. package/uscha-kit/.claude/skills/uscha-mirador/mirador-render.py +16 -8
  5. package/uscha-kit/.claude-plugin/plugin.json +2 -2
  6. package/uscha-kit/.codex-plugin/plugin.json +2 -2
  7. package/uscha-kit/INSTALL.md +3 -0
  8. package/uscha-kit/README.md +1 -1
  9. package/uscha-kit/VERSION +1 -1
  10. package/uscha-kit/install-uscha.py +9 -3
  11. package/uscha-kit/skills/uscha-mirador/SKILL.md +11 -4
  12. package/uscha-kit/skills/uscha-mirador/mirador-render.py +16 -8
  13. package/uscha-kit/uscha.config.json +1 -1
  14. package/uscha-kit/CHANGELOG-1.10.0.md +0 -84
  15. package/uscha-kit/CHANGELOG-1.11.0.md +0 -67
  16. package/uscha-kit/CHANGELOG-1.12.0.md +0 -46
  17. package/uscha-kit/CHANGELOG-1.13.0.md +0 -33
  18. package/uscha-kit/CHANGELOG-1.14.0.md +0 -42
  19. package/uscha-kit/CHANGELOG-1.15.0.md +0 -58
  20. package/uscha-kit/CHANGELOG-1.16.0.md +0 -55
  21. package/uscha-kit/CHANGELOG-1.17.0.md +0 -44
  22. package/uscha-kit/CHANGELOG-1.18.0.md +0 -42
  23. package/uscha-kit/CHANGELOG-1.19.0.md +0 -41
  24. package/uscha-kit/CHANGELOG-1.2.2.md +0 -16
  25. package/uscha-kit/CHANGELOG-1.2.3.md +0 -20
  26. package/uscha-kit/CHANGELOG-1.2.4.md +0 -10
  27. package/uscha-kit/CHANGELOG-1.2.5.md +0 -23
  28. package/uscha-kit/CHANGELOG-1.2.6.md +0 -11
  29. package/uscha-kit/CHANGELOG-1.2.7.md +0 -15
  30. package/uscha-kit/CHANGELOG-1.2.8.md +0 -24
  31. package/uscha-kit/CHANGELOG-1.2.9.md +0 -4
  32. package/uscha-kit/CHANGELOG-1.20.0.md +0 -29
  33. package/uscha-kit/CHANGELOG-1.21.0.md +0 -33
  34. package/uscha-kit/CHANGELOG-1.22.0.md +0 -60
  35. package/uscha-kit/CHANGELOG-1.23.0.md +0 -75
  36. package/uscha-kit/CHANGELOG-1.24.0.md +0 -50
  37. package/uscha-kit/CHANGELOG-1.25.0.md +0 -55
  38. package/uscha-kit/CHANGELOG-1.26.0.md +0 -70
  39. package/uscha-kit/CHANGELOG-1.27.0.md +0 -45
  40. package/uscha-kit/CHANGELOG-1.28.0.md +0 -35
  41. package/uscha-kit/CHANGELOG-1.29.0.md +0 -20
  42. package/uscha-kit/CHANGELOG-1.3.0.md +0 -74
  43. package/uscha-kit/CHANGELOG-1.30.0.md +0 -46
  44. package/uscha-kit/CHANGELOG-1.31.0.md +0 -59
  45. package/uscha-kit/CHANGELOG-1.32.0.md +0 -50
  46. package/uscha-kit/CHANGELOG-1.33.0.md +0 -46
  47. package/uscha-kit/CHANGELOG-1.34.0.md +0 -55
  48. package/uscha-kit/CHANGELOG-1.35.0.md +0 -30
  49. package/uscha-kit/CHANGELOG-1.36.0.md +0 -33
  50. package/uscha-kit/CHANGELOG-1.37.0.md +0 -41
  51. package/uscha-kit/CHANGELOG-1.38.0.md +0 -11
  52. package/uscha-kit/CHANGELOG-1.39.0.md +0 -14
  53. package/uscha-kit/CHANGELOG-1.4.0.md +0 -68
  54. package/uscha-kit/CHANGELOG-1.40.0.md +0 -16
  55. package/uscha-kit/CHANGELOG-1.40.1.md +0 -11
  56. package/uscha-kit/CHANGELOG-1.40.2.md +0 -13
  57. package/uscha-kit/CHANGELOG-1.41.0.md +0 -18
  58. package/uscha-kit/CHANGELOG-1.41.1.md +0 -53
  59. package/uscha-kit/CHANGELOG-1.41.2.md +0 -34
  60. package/uscha-kit/CHANGELOG-1.41.3.md +0 -30
  61. package/uscha-kit/CHANGELOG-1.42.0.md +0 -41
  62. package/uscha-kit/CHANGELOG-1.43.0.md +0 -37
  63. package/uscha-kit/CHANGELOG-1.44.0.md +0 -90
  64. package/uscha-kit/CHANGELOG-1.44.1.md +0 -26
  65. package/uscha-kit/CHANGELOG-1.45.0.md +0 -58
  66. package/uscha-kit/CHANGELOG-1.46.0.md +0 -50
  67. package/uscha-kit/CHANGELOG-1.46.1.md +0 -35
  68. package/uscha-kit/CHANGELOG-1.47.0.md +0 -45
  69. package/uscha-kit/CHANGELOG-1.48.0.md +0 -35
  70. package/uscha-kit/CHANGELOG-1.48.1.md +0 -55
  71. package/uscha-kit/CHANGELOG-1.48.2.md +0 -47
  72. package/uscha-kit/CHANGELOG-1.49.0.md +0 -45
  73. package/uscha-kit/CHANGELOG-1.5.0.md +0 -64
  74. package/uscha-kit/CHANGELOG-1.50.0.md +0 -52
  75. package/uscha-kit/CHANGELOG-1.50.1.md +0 -52
  76. package/uscha-kit/CHANGELOG-1.50.2.md +0 -62
  77. package/uscha-kit/CHANGELOG-1.51.0.md +0 -44
  78. package/uscha-kit/CHANGELOG-1.51.1.md +0 -33
  79. package/uscha-kit/CHANGELOG-1.6.0.md +0 -57
  80. package/uscha-kit/CHANGELOG-1.7.0.md +0 -74
  81. package/uscha-kit/CHANGELOG-1.8.0.md +0 -46
  82. 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.1** <!-- uscha:version --> · [changelog](uscha-kit/CHANGELOG-1.51.1.md)
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.1",
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://github.com/andresmassello/uscha#readme",
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. From the project root,
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 already opened `mirador.html` and printed its absolute path
91
- on the `OPEN IT:` line surface that path to the operator. Pass `--no-open` to write the
92
- file without opening a browser (headless/CI, or the watch loop below, which passes it).
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=0,
98
- help="if >0, the page auto-reloads every N seconds (live second-screen view)")
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
- # opt-in live reload: a meta-refresh, injected only when watching
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
- live = bool(args.refresh and args.refresh > 0)
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 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:
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.1",
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://github.com/andresmassello/uscha",
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.1",
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://github.com/andresmassello/uscha",
9
+ "homepage": "https://uscha.dev",
10
10
  "repository": "https://github.com/andresmassello/uscha",
11
11
  "license": "MIT",
12
12
  "keywords": [
@@ -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
@@ -1,6 +1,6 @@
1
1
  # uscha-kit
2
2
 
3
- **Kit version:** v1.51.1 <!-- uscha:version -->
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
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 (unless --no-open)
764
- rc = subprocess.call(base + (["--no-open"] if args.no_open else []))
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. From the project root,
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 already opened `mirador.html` and printed its absolute path
91
- on the `OPEN IT:` line surface that path to the operator. Pass `--no-open` to write the
92
- file without opening a browser (headless/CI, or the watch loop below, which passes it).
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=0,
98
- help="if >0, the page auto-reloads every N seconds (live second-screen view)")
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
- # opt-in live reload: a meta-refresh, injected only when watching
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
- live = bool(args.refresh and args.refresh > 0)
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 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:
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,5 +1,5 @@
1
1
  {
2
- "version": "1.51.1",
2
+ "version": "1.51.3",
3
3
  "project": null,
4
4
  "defaults": {
5
5
  "coverage_threshold": 60,
@@ -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).