kaizen-3c-cli 1.0.0__py3-none-any.whl

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 (75) hide show
  1. cli/__init__.py +2 -0
  2. cli/approval.py +72 -0
  3. cli/bench/__init__.py +13 -0
  4. cli/bench/value_add_fingerprint.py +327 -0
  5. cli/commands/__init__.py +2 -0
  6. cli/commands/bench.py +333 -0
  7. cli/commands/decompose.py +233 -0
  8. cli/commands/demo.py +312 -0
  9. cli/commands/init.py +299 -0
  10. cli/commands/mcp_serve.py +116 -0
  11. cli/commands/memsafe_roadmap.py +562 -0
  12. cli/commands/migrate_plan.py +586 -0
  13. cli/commands/priors.py +86 -0
  14. cli/commands/recompose.py +234 -0
  15. cli/commands/resume.py +399 -0
  16. cli/commands/status.py +130 -0
  17. cli/commands/web.py +104 -0
  18. cli/config.py +278 -0
  19. cli/demo_assets/README.md +68 -0
  20. cli/demo_assets/__init__.py +2 -0
  21. cli/demo_assets/slugify_demo.tar.gz +0 -0
  22. cli/events.py +248 -0
  23. cli/main.py +140 -0
  24. cli/mcp_server/__init__.py +20 -0
  25. cli/mcp_server/server.py +543 -0
  26. cli/mcp_server/tests/__init__.py +0 -0
  27. cli/mcp_server/tests/test_server.py +224 -0
  28. cli/output.py +241 -0
  29. cli/pipeline/__init__.py +9 -0
  30. cli/pipeline/decompose_v2.py +746 -0
  31. cli/pipeline/oneshot_baseline.py +229 -0
  32. cli/pipeline/recompose_v2.py +520 -0
  33. cli/pipeline/roundtrip_diff.py +126 -0
  34. cli/pipeline/specialist_review.py +162 -0
  35. cli/review.py +246 -0
  36. cli/tests/__init__.py +0 -0
  37. cli/tests/test_approval.py +182 -0
  38. cli/tests/test_bench.py +261 -0
  39. cli/tests/test_config.py +244 -0
  40. cli/tests/test_demo.py +214 -0
  41. cli/tests/test_events.py +118 -0
  42. cli/tests/test_init.py +211 -0
  43. cli/tests/test_resume.py +259 -0
  44. cli/tests/test_review.py +333 -0
  45. cli/web_server/__init__.py +14 -0
  46. cli/web_server/routes/__init__.py +2 -0
  47. cli/web_server/routes/adr.py +33 -0
  48. cli/web_server/routes/decompose.py +110 -0
  49. cli/web_server/routes/memsafe.py +138 -0
  50. cli/web_server/routes/migrate.py +133 -0
  51. cli/web_server/routes/priors.py +50 -0
  52. cli/web_server/routes/providers.py +44 -0
  53. cli/web_server/routes/recompose.py +105 -0
  54. cli/web_server/routes/runs.py +69 -0
  55. cli/web_server/routes/status.py +55 -0
  56. cli/web_server/routes/version.py +15 -0
  57. cli/web_server/server.py +94 -0
  58. cli/web_server/settings.py +53 -0
  59. cli/web_server/sse.py +101 -0
  60. cli/web_server/static/.build-info +4 -0
  61. cli/web_server/static/assets/index-BsmHrNwi.js +187 -0
  62. cli/web_server/static/assets/index-BsmHrNwi.js.map +1 -0
  63. cli/web_server/static/assets/index-FELcRPb3.css +1 -0
  64. cli/web_server/static/index.html +14 -0
  65. cli/web_server/tests/__init__.py +0 -0
  66. cli/web_server/tests/test_integration.py +123 -0
  67. cli/web_server/tests/test_routes.py +189 -0
  68. cli/web_server/tests/test_sse.py +168 -0
  69. kaizen_3c_cli-1.0.0.dist-info/METADATA +123 -0
  70. kaizen_3c_cli-1.0.0.dist-info/RECORD +75 -0
  71. kaizen_3c_cli-1.0.0.dist-info/WHEEL +5 -0
  72. kaizen_3c_cli-1.0.0.dist-info/entry_points.txt +2 -0
  73. kaizen_3c_cli-1.0.0.dist-info/licenses/LICENSE +193 -0
  74. kaizen_3c_cli-1.0.0.dist-info/licenses/NOTICE +119 -0
  75. kaizen_3c_cli-1.0.0.dist-info/top_level.txt +1 -0
cli/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ __version__ = "1.0.0"
cli/approval.py ADDED
@@ -0,0 +1,72 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """User approval prompt for interactive CLI checkpoints.
3
+
4
+ Provides a single interactive approval function for commands that have optional
5
+ stages (e.g., recompose after generating a roadmap/plan). Handles non-TTY
6
+ environments (CI, piped input) gracefully.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+
13
+
14
+ def is_tty() -> bool:
15
+ """Return True if stdin is connected to a terminal (interactive session).
16
+
17
+ Wrapped in try/except for odd environments (e.g., some test harnesses).
18
+ """
19
+ try:
20
+ return sys.stdin.isatty()
21
+ except Exception:
22
+ return False
23
+
24
+
25
+ def approval_prompt(
26
+ message: str,
27
+ *,
28
+ yolo: bool = False,
29
+ default: bool = False,
30
+ ) -> bool:
31
+ """Prompt the user for approval, with graceful fallbacks for non-TTY.
32
+
33
+ Args:
34
+ message: The prompt message to display (e.g. "Continue to recompose?").
35
+ yolo: If True, return True immediately without prompting (opt-out mode).
36
+ default: The return value if stdin is not a TTY or user hits enter.
37
+
38
+ Returns:
39
+ True if user approves (or yolo=True), False otherwise.
40
+
41
+ Behavior:
42
+ - If yolo=True: return True immediately (no prompt).
43
+ - If stdin is not a TTY: log "(non-interactive; skipping prompt, proceeding=<default>)"
44
+ to stderr and return `default`.
45
+ - Otherwise: print message + prompt to stderr, read a line from stdin.
46
+ - "y" or "yes" (case-insensitive) -> True
47
+ - Empty line -> `default`
48
+ - Anything else -> False
49
+ - Ctrl-C (KeyboardInterrupt) -> print "aborted" and return False.
50
+ """
51
+ if yolo:
52
+ return True
53
+
54
+ if not is_tty():
55
+ sys.stderr.write(f"(non-interactive; skipping prompt, proceeding={default})\n")
56
+ return default
57
+
58
+ try:
59
+ prompt_suffix = " [Y/n] " if default else " [y/N] "
60
+ sys.stderr.write(message + prompt_suffix)
61
+ sys.stderr.flush()
62
+
63
+ line = sys.stdin.readline().strip().lower()
64
+
65
+ if not line:
66
+ return default
67
+ if line in ("y", "yes"):
68
+ return True
69
+ return False
70
+ except KeyboardInterrupt:
71
+ sys.stderr.write("\naborted\n")
72
+ return False
cli/bench/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Vendored benchmark analysis scripts.
3
+
4
+ These are copied verbatim from `Kaizen-3C/benchmarks/commit0/baselines/`
5
+ to keep `kaizen bench` self-contained — no separate clone, no PyPI
6
+ dependency. Sync periodically via scripts/sync-bench-vendor.py
7
+ (forthcoming) when upstream changes.
8
+
9
+ Vendored modules:
10
+ - value_add_fingerprint: per-cell architectural weakness matrix
11
+ - compare_baselines: per-arch aggregate comparisons
12
+ - (more to add as they prove useful at the CLI surface)
13
+ """
@@ -0,0 +1,327 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # VENDORED FROM: Kaizen-3C/benchmarks/commit0/baselines/value_add_fingerprint.py
3
+ # Sync manually; kept self-contained so `pip install kaizen-3c-cli` works without
4
+ # requiring a separate clone of the benchmarks repo.
5
+ """Value-add architectural fingerprint table.
6
+
7
+ For each (architecture x model x library) cell:
8
+ value_add_pp = arch_pass_rate - single_shot_LLM_pass_rate (same model)
9
+ value_add_$_pp = arch_cost / max(value_add_pp, 0.01)
10
+ llm_lean = arch_cost / single_shot_LLM_cost (same model, same lib)
11
+
12
+ Reads from benchmarks/commit0/results/. Outputs to stdout.
13
+ Also flags architectural weakness signatures per ADR-0063 §weakness_fingerprints.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from pathlib import Path
20
+
21
+ # Default results directory — relative to the original script location in the
22
+ # benchmarks repo. main() accepts an override via results_dir parameter.
23
+ R = Path(__file__).resolve().parents[2] / "commit0" / "results"
24
+
25
+ LIBS = ["wcwidth", "deprecated", "cachetools", "voluptuous", "portalocker",
26
+ "pyjwt", "chardet", "tinydb", "simpy", "imapclient", "parsel",
27
+ "marshmallow", "cookiecutter", "babel", "jinja", "minitorch"]
28
+ FLOOR = {"chardet", "marshmallow", "babel", "jinja", "minitorch"}
29
+
30
+
31
+ def loadj(p: Path) -> dict:
32
+ return json.loads(p.read_text()) if p.exists() else {}
33
+
34
+
35
+ def lib_passrate(per_lib: dict, lib: str) -> tuple[int, int, float | None]:
36
+ """Return (passed, attempted, rate or None if not run)."""
37
+ d = (per_lib or {}).get(lib, {}) or {}
38
+ c = d.get("counts") or d.get("final_counts") or {}
39
+ p, f, e = c.get("passed", 0), c.get("failed", 0), c.get("errors", 0)
40
+ a = p + f + e
41
+ if a == 0 and not c:
42
+ return 0, 0, None # not run
43
+ return p, a, ((100 * p / a) if a else 0)
44
+
45
+
46
+ def lib_cost(per_lib: dict, lib: str, source_provider: str) -> float | None:
47
+ """Cost from per-library JSON. Some baselines store cost differently."""
48
+ d = (per_lib or {}).get(lib, {}) or {}
49
+ if not d:
50
+ return None
51
+ # Prefer pre-computed cost
52
+ if "totals" in d and isinstance(d["totals"], dict):
53
+ c = d["totals"].get("cost_usd")
54
+ if c is not None:
55
+ return c
56
+ # B2: compute from tokens
57
+ fresh_in = d.get("input_tokens", 0)
58
+ out = d.get("output_tokens", 0)
59
+ cached = d.get("cached_input_tokens", 0)
60
+ if source_provider == "anthropic":
61
+ return (fresh_in * 3 + out * 15) / 1_000_000
62
+ return ((fresh_in - cached) * 1.25 + cached * 0.125 + out * 10) / 1_000_000
63
+
64
+
65
+ def oh_status(report: dict, lib: str) -> tuple[str, float | None]:
66
+ """OH instance status: RES / no / FAIL. Cost from report metrics."""
67
+ if not report:
68
+ return "FAIL", None
69
+ if lib in report.get("resolved_ids", []):
70
+ return "RES", None
71
+ if lib in report.get("unresolved_ids", []):
72
+ return "no", None
73
+ if lib in report.get("completed_ids", []):
74
+ return "?", None
75
+ return "FAIL", None
76
+
77
+
78
+ def oh_lib_cost_from_jsonl(jsonl: Path, lib: str) -> float | None:
79
+ if not jsonl.exists():
80
+ return None
81
+ for line in jsonl.read_text(encoding="utf-8", errors="replace").splitlines():
82
+ if not line.strip():
83
+ continue
84
+ try:
85
+ obj = json.loads(line)
86
+ except json.JSONDecodeError:
87
+ continue
88
+ iid = obj.get("instance_id") or ""
89
+ if iid.endswith(lib):
90
+ return (obj.get("metrics") or {}).get("accumulated_cost", 0)
91
+ return None
92
+
93
+
94
+ def merge_oh_dirs(results_root: Path, *dir_names: str) -> dict:
95
+ """Merge multiple OH result dirs into one (lib -> (status, cost))."""
96
+ out = {}
97
+ for name in dir_names:
98
+ d = results_root / name
99
+ rep_p = d / "output.report.json"
100
+ jsonl_p = d / "output.jsonl"
101
+ if not rep_p.exists():
102
+ continue
103
+ rep = json.loads(rep_p.read_text())
104
+ for lib in (rep.get("completed_ids", []) or []):
105
+ status, _ = oh_status(rep, lib)
106
+ cost = oh_lib_cost_from_jsonl(jsonl_p, lib) if jsonl_p.exists() else None
107
+ out[lib] = (status, cost)
108
+ return out
109
+
110
+
111
+ def kd_per_lib(results_root: Path, provider: str) -> dict:
112
+ """Load KD per-lib JSONs (separate files, not aggregated dict)."""
113
+ out = {}
114
+ for lib in LIBS:
115
+ p = results_root / f"{lib}_kaizen_delta_{provider}.json"
116
+ if p.exists():
117
+ d = json.loads(p.read_text())
118
+ if "final_counts" in d:
119
+ d["counts"] = d["final_counts"]
120
+ out[lib] = d
121
+ return out
122
+
123
+
124
+ def compute_cell(arch_per_lib: dict, lib: str, single_shot_per_lib: dict,
125
+ source_provider: str) -> dict | None:
126
+ """For KD-style architectures with full per-lib pass-rate."""
127
+ p, a, rate = lib_passrate(arch_per_lib, lib)
128
+ sp, sa, srate = lib_passrate(single_shot_per_lib, lib)
129
+ if rate is None or srate is None:
130
+ return None
131
+ cost = lib_cost(arch_per_lib, lib, source_provider) or 0
132
+ s_cost = lib_cost(single_shot_per_lib, lib, source_provider) or 0.001
133
+ value_add_pp = rate - srate
134
+ value_add_dollar_pp = (cost / max(value_add_pp, 0.01)) if value_add_pp > 0 else None
135
+ llm_lean = cost / max(s_cost, 0.001)
136
+ return {
137
+ "passed": p, "attempted": a, "rate": rate,
138
+ "cost": cost, "value_add_pp": value_add_pp,
139
+ "value_add_dollar_pp": value_add_dollar_pp, "llm_lean": llm_lean,
140
+ }
141
+
142
+
143
+ def compute_oh_cell(oh_dict: dict, lib: str, single_shot_per_lib: dict,
144
+ source_provider: str) -> dict | None:
145
+ """For OH (binary RES/no/FAIL)."""
146
+ if lib not in oh_dict:
147
+ return None
148
+ status, cost = oh_dict[lib]
149
+ cost = cost or 0
150
+ sp, sa, srate = lib_passrate(single_shot_per_lib, lib)
151
+ if srate is None:
152
+ return None
153
+ s_cost = lib_cost(single_shot_per_lib, lib, source_provider) or 0.001
154
+ # OH's pass-rate is binary: RES = 100, anything else = unknown
155
+ if status == "RES":
156
+ rate = 100.0
157
+ value_add_pp = 100 - srate
158
+ elif status == "no":
159
+ rate = None
160
+ value_add_pp = None
161
+ else:
162
+ rate = None
163
+ value_add_pp = None
164
+ value_add_dollar_pp = (
165
+ (cost / max(value_add_pp, 0.01))
166
+ if (value_add_pp and value_add_pp > 0)
167
+ else None
168
+ )
169
+ llm_lean = cost / max(s_cost, 0.001)
170
+ return {
171
+ "status": status, "rate": rate, "cost": cost,
172
+ "value_add_pp": value_add_pp,
173
+ "value_add_dollar_pp": value_add_dollar_pp, "llm_lean": llm_lean,
174
+ }
175
+
176
+
177
+ def fmt_cell(c: dict | None, kind: str = "kd") -> str:
178
+ if c is None:
179
+ return f"{' --':>16}"
180
+ if kind == "oh":
181
+ if c['status'] == "RES":
182
+ return f"RES +{c['value_add_pp']:>3.0f}pp x{c['llm_lean']:>4.0f}"
183
+ if c['status'] == "no":
184
+ return f" no ? x{c['llm_lean']:>4.0f}"
185
+ return "FAIL -- --"
186
+ # KD-style
187
+ va = c['value_add_pp']
188
+ sign = "+" if va >= 0 else ""
189
+ return f"{c['rate']:>3.0f}% {sign}{va:>+4.0f}pp x{c['llm_lean']:>4.1f}"
190
+
191
+
192
+ def run_fingerprint(results_root: Path) -> None:
193
+ """Compute and print the full value-add fingerprint table."""
194
+ # Load all baselines
195
+ b2s = loadj(results_root / "aggregate_lite_single_shot_sonnet.json").get("per_library", {})
196
+ b2g = loadj(results_root / "aggregate_lite_single_shot_openai.json").get("per_library", {})
197
+ b3s = loadj(results_root / "aggregate_lite_reflexion_sonnet.json").get("per_library", {})
198
+ b3g = loadj(results_root / "aggregate_lite_reflexion_openai.json").get("per_library", {})
199
+
200
+ kds = kd_per_lib(results_root, "anthropic")
201
+ kdg = kd_per_lib(results_root, "openai")
202
+
203
+ # OH merged across all subset dirs
204
+ oh_s = merge_oh_dirs(results_root, "b6_partial_pass1", "b6_4cheap_sonnet", "b6_t3_sonnet")
205
+ oh_g = merge_oh_dirs(results_root, "b6_partial_gpt54_3libs", "b6_4cheap_gpt54", "b6_10missing_gpt54")
206
+
207
+ print("=" * 145)
208
+ print("VALUE-ADD FINGERPRINT — each cell shows: pass-rate, value_add_pp vs same-model B2, llm_lean (cost ratio vs B2)")
209
+ print("=" * 145)
210
+ hdr = f"{'lib':12} {'F?':>2} | {'KD-S':>16} {'KD-G':>16} | {'OH-S':>17} {'OH-G':>17}"
211
+ print(hdr)
212
+ print("-" * len(hdr))
213
+
214
+ floor_unlocks = []
215
+ big_wins = []
216
+ big_losses = []
217
+ oh_resolved = []
218
+
219
+ for lib in LIBS:
220
+ f = "*" if lib in FLOOR else " "
221
+ kds_cell = compute_cell(kds, lib, b2s, "anthropic")
222
+ kdg_cell = compute_cell(kdg, lib, b2g, "openai")
223
+ ohs_cell = compute_oh_cell(oh_s, lib, b2s, "anthropic")
224
+ ohg_cell = compute_oh_cell(oh_g, lib, b2g, "openai")
225
+ print(
226
+ f"{lib:12} {f:>2} | "
227
+ f"{fmt_cell(kds_cell, 'kd'):>16} {fmt_cell(kdg_cell, 'kd'):>16} | "
228
+ f"{fmt_cell(ohs_cell, 'oh'):>17} {fmt_cell(ohg_cell, 'oh'):>17}"
229
+ )
230
+
231
+ # Collect findings
232
+ for cell, label in [(kds_cell, f"KD-S {lib}"), (kdg_cell, f"KD-G {lib}")]:
233
+ if cell and cell['value_add_pp'] is not None:
234
+ if cell['value_add_pp'] >= 10:
235
+ big_wins.append((label, cell))
236
+ if cell['value_add_pp'] <= -10:
237
+ big_losses.append((label, cell))
238
+ if lib in FLOOR and cell['rate'] > 0:
239
+ floor_unlocks.append((label, cell))
240
+ for cell, label in [(ohs_cell, f"OH-S {lib}"), (ohg_cell, f"OH-G {lib}")]:
241
+ if cell and cell.get('status') == 'RES':
242
+ oh_resolved.append((label, cell))
243
+ if lib in FLOOR:
244
+ floor_unlocks.append((label + " (RES)", cell))
245
+
246
+ print()
247
+ print("Legend:")
248
+ print(" KD cells: pass% +pp vs same-model B2 xN llm_lean (cost ratio)")
249
+ print(" OH cells: RES = resolved 100% / no = unresolved / FAIL = didn't complete")
250
+ print(" +/- pp: value-add over the LLM's single-shot baseline (same model)")
251
+ print(" llm_lean: cost ratio -- 1x means 'spent same as just calling the LLM once'")
252
+ print()
253
+
254
+ # Architectural-weakness signatures
255
+ print("=" * 100)
256
+ print("ARCHITECTURAL-WEAKNESS SIGNATURES (per ADR-0063 §weakness_fingerprints)")
257
+ print("=" * 100)
258
+
259
+ print()
260
+ print("OH WEAKNESS — high llm_lean for low/negative value-add")
261
+ print("(cells where OH spent much more than the LLM and didn't resolve):")
262
+ for lib in LIBS:
263
+ cell = compute_oh_cell(oh_s, lib, b2s, "anthropic")
264
+ if cell and cell['llm_lean'] > 5 and cell.get('status') in ("no", "FAIL"):
265
+ print(
266
+ f" OH-S {lib:13} {cell['status']:>4} "
267
+ f"llm_lean={cell['llm_lean']:>5.0f}x cost=${cell['cost']:.2f}"
268
+ )
269
+ cell = compute_oh_cell(oh_g, lib, b2g, "openai")
270
+ if cell and cell['llm_lean'] > 5 and cell.get('status') in ("no", "FAIL"):
271
+ print(
272
+ f" OH-G {lib:13} {cell['status']:>4} "
273
+ f"llm_lean={cell['llm_lean']:>5.0f}x cost=${cell['cost']:.2f}"
274
+ )
275
+
276
+ print()
277
+ print("KD WEAKNESS — negative value-add (per-file regen damaged working code):")
278
+ for label, cell in big_losses:
279
+ print(
280
+ f" {label:18} rate={cell['rate']:>3.0f}% "
281
+ f"value_add={cell['value_add_pp']:+.0f}pp "
282
+ f"cost=${cell['cost']:.2f} llm_lean={cell['llm_lean']:.1f}x"
283
+ )
284
+
285
+ print()
286
+ print("FLOOR-LIB UNLOCKS (only baseline >0% on a floor lib):")
287
+ for label, cell in floor_unlocks:
288
+ rate = cell.get('rate', 100 if cell.get('status') == 'RES' else None)
289
+ print(f" {label:30} rate={rate} cost=${cell['cost']:.2f}")
290
+
291
+ print()
292
+ print("BIG WINS (KD value-add >= 10pp):")
293
+ for label, cell in big_wins:
294
+ print(
295
+ f" {label:18} rate={cell['rate']:>3.0f}% "
296
+ f"value_add=+{cell['value_add_pp']:.0f}pp "
297
+ f"cost=${cell['cost']:.2f} llm_lean={cell['llm_lean']:.1f}x"
298
+ )
299
+
300
+
301
+ def main(results_dir: Path | None = None) -> int:
302
+ """Programmatic entry point. Returns exit code (0 = success).
303
+
304
+ Parameters
305
+ ----------
306
+ results_dir:
307
+ Override the default results directory. When *None* the module falls
308
+ back to the ``R`` global (which points at the benchmarks repo's own
309
+ ``commit0/results/`` tree when run from the monorepo, or wherever
310
+ ``kaizen bench fingerprint --results`` points).
311
+ """
312
+ global R
313
+ if results_dir is not None:
314
+ R = Path(results_dir).resolve()
315
+
316
+ results_root = R
317
+ if not results_root.exists():
318
+ print(f"Results directory not found: {results_root}")
319
+ print("Pass --results <dir> pointing at a commit0 results directory.")
320
+ return 1
321
+
322
+ run_fingerprint(results_root)
323
+ return 0
324
+
325
+
326
+ if __name__ == "__main__":
327
+ raise SystemExit(main())
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Subcommand implementations for the kaizen CLI."""