kaizen-agent 0.7.2 → 0.7.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 CHANGED
@@ -252,28 +252,37 @@ The stage agents and slash commands are Claude Code's own formats and are not co
252
252
  elsewhere; under the others you invoke the skill by name. `.kaizen/` is plain files
253
253
  either way, so a run started in one tool can be finished in another.
254
254
 
255
- Once installed, `kaizen` on its own opens a dashboard rather than installing again:
255
+ Run `kaizen` anytime to open the terminal dashboard:
256
256
 
257
257
  ```
258
- kaizen 0.5.0 ~/Projects/app
259
-
260
- Runs
261
- ● 2026-09-07-fix-discount waiting on you — approvals.plan
262
- ● 2026-09-06-cache-headers in flight — review
263
- ● 3 done
264
-
265
- Backlog 4 open
266
-
267
- Installed for Claude Code, Codex, Antigravity
268
-
269
- › Settings modes, approvals, who builds
258
+ ⠀⠀⠀⣰⠖⠾⣟⣛⠋⢉⣩⠽⢛⡽⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
259
+ ⠀⠀⢰⢻⠀⠀⢀⡬⠟⠉⢀⠴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
260
+ ⠀⢠⠇⣾⡠⠞⢩⠤⢄⡚⣁⣀⣀⣀⢀⣠⠤⡄⠀⠀⠀⠀⠀⠀ █ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █
261
+ ⢠⣏⠔⣫⣀⡤⢸⠀⠀⠉⠁⠀⠀⠈⠉⠀⠀⡇⠀⠀⠀⠀⠀⠀ █▀▄ █▀█ █ ▄▀ ██▄ █ ▀█
262
+ ⢾⠥⢾⡅⢧⠀⢸⠃⡤⠖⠢⡀⢀⠔⠲⢤⠘⡇⠀⠀⣀⣀⠀⠀ █ █ █ █ █ █▄▄ █▄▄ █ █
263
+ ⠀⠀⠈⣇⠈⢠⣫⠞⠀⠘⢱⢣⡜⡎⠃⠀⠳⣝⡄⢠⠇⢻⢉⣷
264
+ ⠀⠀⠀⠘⢆⠀⠑⢦⣀⡀⠈⠓⠚⠁⢀⣀⣤⣊⣠⢾⡀⢠⣿⠃ 0.7.2 plan · approve · build · review
265
+ ⠀⠀⠀⠀⠈⠓⢄⠀⠀⠉⣉⣭⣭⣉⠉⠀⠈⢀⣠⢔⡩⠥⢥⡀ ~/Projects/app
266
+ ⠀⠀⠀⠀⠀⠀⢠⡇⠀⡞⠁⠀⠀⠈⢳⠀⢰⣍⣠⠚⠧⡀⠀⡷ Claude Code, Codex, Antigravity, OpenCode, Gemini CLI
267
+ ⠀⠀⠀⠀⠀⠀⠸⡇⠀⣇⠀⠀⠀⠀⣸⠀⢠⡏⠀⠀⢀⣳⠞⠁
268
+ ⠀⠀⠀⠀⠀⠀⠀⠳⣀⣨⠥⠤⠶⠾⣅⣀⠞⠉⠉⠉⠉⠀⠀⠀
269
+
270
+ ╭─ Runs ──────────────────────────────────╮ ╭─ Backlog — 4 open ──────────────────────────╮
271
+ │ ● fix-discount waiting on you │ │ cart.py:12 low use > not >= │
272
+ │ ● cache-headers in review │ │ headline med legal review │
273
+ │ ● 3 done │ │ … 2 more │
274
+ ╰─────────────────────────────────────────╯ ╰─────────────────────────────────────────────╯
275
+
276
+ › All projects every project kaizen knows about
277
+ Runs
278
+ Backlog
279
+ Settings
270
280
  Upgrade
271
281
  Quit
272
282
  ```
273
283
 
274
- It reads the project you are standing in, so it answers "what is this repo waiting on
275
- me for" without opening an agent. Installing also puts that `kaizen` command on your
276
- PATH, so updating later is:
284
+ It monitors your runs and backlog across all your projects without having to open an agent.
285
+ Installing puts the `kaizen` command on your PATH, so updating later is:
277
286
 
278
287
  ```bash
279
288
  kaizen upgrade
@@ -408,17 +417,20 @@ change written as you make it, esc to close:
408
417
  ```
409
418
  kaizen settings ~/Projects/app/.kaizen/config.yml
410
419
 
420
+ › preset medium
411
421
  mode approve
412
- › build.executor inline
422
+ runner full
423
+ build.executor subagent
413
424
  approvals.plan true
414
425
  approvals.review false
426
+ auto_fix.enabled true
415
427
  auto_fix.min_severity high
416
428
  auto_fix.max_iterations 2
417
429
  ...
418
430
 
419
- Who carries out the approved plan
431
+ Quick preset — low: inline & manual, medium: balanced, ultra: autonomous subagents
420
432
 
421
- ↑↓ move · ←→ change · esc close saved build.executor = inline
433
+ ↑↓ move · ←→ change · esc close saved preset = medium
422
434
  ```
423
435
 
424
436
  It edits each line in place, so the comment above every key survives. `/kaizen-config`
@@ -442,6 +454,7 @@ with a comment on each key, if you would rather edit the file.
442
454
  | `--project` | Into the current directory only |
443
455
  | `--check` | Report what is linked, exit non-zero if anything is missing |
444
456
  | `--force` | Replace a real file sitting where a link belongs |
457
+ | `--preset <name>` | Pre-select workflow preset (`low`, `medium`, or `ultra`) |
445
458
  | `--yes` | Take every default, ask nothing |
446
459
  | `--verbose` | List every link instead of a one-line summary |
447
460
  | `upgrade` | Pull, relink, and clear the installer cache. No prompts. |
package/cli/install.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  } from "node:fs";
6
6
  import { join, dirname, basename } from "node:path";
7
7
  import { homedir } from "node:os";
8
+ import { applyPreset, detectPreset, PRESETS, type PresetName } from "./settings.ts";
8
9
 
9
10
  const home = homedir();
10
11
  const REPO_URL = "https://github.com/hfadhlullah/kaizen.git";
@@ -16,6 +17,22 @@ const force = args.has("--force");
16
17
  const verbose = args.has("--verbose");
17
18
  const interactive = process.stdin.isTTY && !check && !upgrade && !args.has("--yes");
18
19
 
20
+ function parsePresetArg(): PresetName | null {
21
+ for (const a of Bun.argv.slice(2)) {
22
+ if (a.startsWith("--preset=")) {
23
+ const v = a.split("=")[1]?.toLowerCase();
24
+ if (v === "low" || v === "medium" || v === "ultra") return v as PresetName;
25
+ }
26
+ }
27
+ const idx = Bun.argv.indexOf("--preset");
28
+ if (idx !== -1 && Bun.argv[idx + 1]) {
29
+ const v = Bun.argv[idx + 1]?.toLowerCase();
30
+ if (v === "low" || v === "medium" || v === "ultra") return v as PresetName;
31
+ }
32
+ return null;
33
+ }
34
+ const presetArg = parsePresetArg();
35
+
19
36
  const c = {
20
37
  dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
21
38
  bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
@@ -392,6 +409,43 @@ function installLauncher() {
392
409
 
393
410
  const inProject = process.cwd() !== home;
394
411
  const kaizenDir = join(process.cwd(), ".kaizen");
412
+ const globalKaizen = join(home, ".kaizen");
413
+ const globalConfig = join(globalKaizen, "config.yml");
414
+ const hasConfig = (args.has("onboard") || args.has("--onboard"))
415
+ ? false
416
+ : (inProject ? existsSync(localConfig) : existsSync(globalConfig));
417
+
418
+ let presetChoice: PresetName | null = presetArg;
419
+ if (!hasConfig && !presetChoice && interactive) {
420
+ presetChoice = await select("Choose workflow preset", [
421
+ {
422
+ label: "Medium (Recommended)",
423
+ hint: "balanced — subagents, plan approval, auto-fix high/critical",
424
+ value: "medium",
425
+ },
426
+ {
427
+ label: "Low",
428
+ hint: "inline & manual — lite runner, inline builder, manual approvals, no auto-fix",
429
+ value: "low",
430
+ },
431
+ {
432
+ label: "Ultra",
433
+ hint: "autonomous — subagents, auto mode, auto-fix all findings (up to 4 rounds)",
434
+ value: "ultra",
435
+ },
436
+ ]);
437
+ }
438
+
439
+ if ((!existsSync(globalConfig) && presetChoice) || (presetArg && !args.has("--project"))) {
440
+ mkdirSync(globalKaizen, { recursive: true });
441
+ const defPath = join(repo, "skills/kaizen/config.default.yml");
442
+ const def = readFileSync(defPath, "utf8");
443
+ const base = existsSync(globalConfig) ? readFileSync(globalConfig, "utf8") : def;
444
+ const target = presetChoice ?? "medium";
445
+ const conf = applyPreset(base, target, def);
446
+ writeFileSync(globalConfig, conf);
447
+ step(`configured ${c.bold(target)} preset in ${tilde(globalConfig)}`);
448
+ }
395
449
 
396
450
  // A .kaizen/ in a parent already owns this folder's state, and setting up a child
397
451
  // would shadow it, hiding the parent's runs and leaving the child out of the root
@@ -413,25 +467,35 @@ function kaizenAbove() {
413
467
  }
414
468
 
415
469
  if (inProject && !kaizenAbove()) {
416
- const now = interactive
417
- ? await select(`Set up ${basename(process.cwd())}/ for kaizen now`, [
418
- // "Not now" leads: the offer now appears in any folder outside home, so the
419
- // safe answer is the one under the cursor.
420
- { label: "Not now", hint: "the first /kaizen run will do it", value: false },
421
- { label: "Yes", hint: "writes .kaizen/ and a CLAUDE.md line so it runs by default", value: true },
422
- ])
423
- : false;
424
- if (now) initProject();
470
+ const now = root === process.cwd()
471
+ ? true
472
+ : interactive
473
+ ? await select(`Set up ${basename(process.cwd())}/ for kaizen now`, [
474
+ // "Not now" leads: the offer now appears in any folder outside home, so the
475
+ // safe answer is the one under the cursor.
476
+ { label: "Not now", hint: "the first /kaizen run will do it", value: false },
477
+ { label: "Yes", hint: "writes .kaizen/ and a CLAUDE.md line so it runs by default", value: true },
478
+ ])
479
+ : false;
480
+ if (now) initProject(presetChoice);
425
481
  } else if (existsSync(kaizenDir)) {
426
482
  step(`${basename(process.cwd())}/.kaizen already set up`);
427
483
  }
428
484
 
429
- function initProject() {
485
+ function initProject(preset?: PresetName | null) {
430
486
  mkdirSync(kaizenDir, { recursive: true });
431
487
  for (const f of readdirSync(join(repo, "skills/kaizen")).filter((f) => f.startsWith("spec"))) {
432
488
  copyFileSync(join(repo, "skills/kaizen", f), join(kaizenDir, f));
433
489
  }
434
- copyFileSync(join(repo, "skills/kaizen/config.default.yml"), join(kaizenDir, "config.yml"));
490
+ const defPath = join(repo, "skills/kaizen/config.default.yml");
491
+ const def = readFileSync(defPath, "utf8");
492
+ let chosen: PresetName = preset ?? "medium";
493
+ if (!preset && existsSync(globalConfig)) {
494
+ const detected = detectPreset(readFileSync(globalConfig, "utf8"), def);
495
+ if (detected !== "custom") chosen = detected;
496
+ }
497
+ const configContent = applyPreset(def, chosen, def);
498
+ writeFileSync(join(kaizenDir, "config.yml"), configContent);
435
499
 
436
500
  // Run state is local; the workflow itself is shared, so those files stay tracked.
437
501
  // Nothing to ignore where there is no git, so a plain folder gets no .gitignore.
package/cli/settings.ts CHANGED
@@ -6,7 +6,94 @@ import { homedir } from "node:os";
6
6
 
7
7
  type Setting = { key: string; values: string[]; help: string };
8
8
 
9
+ export type PresetName = "low" | "medium" | "ultra";
10
+
11
+ export const PRESETS: Record<PresetName, {
12
+ label: string;
13
+ hint: string;
14
+ settings: Record<string, string>;
15
+ }> = {
16
+ low: {
17
+ label: "Low",
18
+ hint: "inline & manual — lite runner, inline builder, manual approvals, no auto-fix",
19
+ settings: {
20
+ mode: "approve",
21
+ runner: "lite",
22
+ "build.executor": "inline",
23
+ "approvals.plan": "true",
24
+ "approvals.review": "true",
25
+ "approvals.each_file": "false",
26
+ "auto_fix.enabled": "false",
27
+ "auto_fix.min_severity": "high",
28
+ "auto_fix.max_iterations": "1",
29
+ "git.auto_commit": "false",
30
+ "git.branch_before_implement": "false",
31
+ },
32
+ },
33
+ medium: {
34
+ label: "Medium",
35
+ hint: "balanced (recommended) — full runner, subagents, plan approval, auto-fix high/critical",
36
+ settings: {
37
+ mode: "approve",
38
+ runner: "full",
39
+ "build.executor": "subagent",
40
+ "approvals.plan": "true",
41
+ "approvals.review": "false",
42
+ "approvals.each_file": "false",
43
+ "auto_fix.enabled": "true",
44
+ "auto_fix.min_severity": "high",
45
+ "auto_fix.max_iterations": "2",
46
+ "git.auto_commit": "false",
47
+ "git.branch_before_implement": "true",
48
+ },
49
+ },
50
+ ultra: {
51
+ label: "Ultra",
52
+ hint: "autonomous — full runner, subagents, auto mode, auto-fix all findings (up to 4 rounds)",
53
+ settings: {
54
+ mode: "auto",
55
+ runner: "full",
56
+ "build.executor": "subagent",
57
+ "approvals.plan": "false",
58
+ "approvals.review": "false",
59
+ "approvals.each_file": "false",
60
+ "auto_fix.enabled": "true",
61
+ "auto_fix.min_severity": "low",
62
+ "auto_fix.max_iterations": "4",
63
+ "git.auto_commit": "true",
64
+ "git.branch_before_implement": "true",
65
+ },
66
+ },
67
+ };
68
+
69
+ export function detectPreset(text: string, defaults?: string): PresetName | "custom" {
70
+ for (const name of ["low", "medium", "ultra"] as const) {
71
+ const p = PRESETS[name];
72
+ let match = true;
73
+ for (const [k, expected] of Object.entries(p.settings)) {
74
+ const actual = read(text, k) ?? (defaults ? read(defaults, k) : null);
75
+ if (actual !== expected) {
76
+ match = false;
77
+ break;
78
+ }
79
+ }
80
+ if (match) return name;
81
+ }
82
+ return "custom";
83
+ }
84
+
85
+ export function applyPreset(text: string, preset: PresetName, defaults: string): string {
86
+ let updated = text;
87
+ const p = PRESETS[preset];
88
+ for (const [k, v] of Object.entries(p.settings)) {
89
+ updated = write(updated, k, v, defaults);
90
+ }
91
+ updated = write(updated, "preset", preset, defaults);
92
+ return updated;
93
+ }
94
+
9
95
  const SETTINGS: Setting[] = [
96
+ { key: "preset", values: ["low", "medium", "ultra", "custom"], help: "Quick preset — low: inline & manual, medium: balanced, ultra: autonomous subagents" },
10
97
  { key: "mode", values: ["approve", "auto", "plan-only", "review-only"], help: "Where a run stops" },
11
98
  { key: "runner", values: ["full", "lite"], help: "full: every stage its own cold agent. lite: this session runs them all, cheaper, reviewer has seen the work" },
12
99
  { key: "build.executor", values: ["subagent", "inline", "ask"], help: "Who carries out the approved plan" },
@@ -42,10 +129,15 @@ export async function settings(repoRoot: string, standalone = true) {
42
129
  const { stdin, stdout } = process;
43
130
  if (!stdin.isTTY) { console.log(`Settings live in ${file}`); return; }
44
131
 
132
+ const defaults = readFileSync(join(repoRoot, "skills/kaizen/config.default.yml"), "utf8");
45
133
  let active = 0, saved = "";
46
134
  const draw = () => {
47
135
  const text = readFileSync(file, "utf8");
48
- const rows = SETTINGS.map((s) => ({ s, value: read(text, s.key) ?? c.dim("(default)") }));
136
+ const currentPreset = detectPreset(text, defaults);
137
+ const rows = SETTINGS.map((s) => ({
138
+ s,
139
+ value: s.key === "preset" ? currentPreset : (read(text, s.key) ?? c.dim("(default)")),
140
+ }));
49
141
  const width = Math.max(...SETTINGS.map((s) => s.key.length)) + 4;
50
142
 
51
143
  stdout.write("\x1b[H\x1b[2J"); // home, clear
@@ -84,16 +176,28 @@ export async function settings(repoRoot: string, standalone = true) {
84
176
  const step = rest.startsWith("\x1b[D") ? -1 : 1;
85
177
  const s = SETTINGS[active]!;
86
178
  const text = readFileSync(file, "utf8");
87
- const now = read(text, s.key) ?? s.values[0]!;
88
- const at = s.values.indexOf(now);
89
- const next = s.values[((at < 0 ? 0 : at) + step + s.values.length) % s.values.length]!;
90
- const updated = write(text, s.key, next, readFileSync(join(repoRoot, "skills/kaizen/config.default.yml"), "utf8"));
91
- writeFileSync(file, updated);
92
- // Only claim a save that happened. Reporting one that did not is worse
93
- // than failing loudly: the setting reads back unchanged and nobody knows why.
94
- saved = read(updated, s.key) === next
95
- ? c.green(`saved ${s.key} = ${next || '""'}`)
96
- : `\x1b[33mcould not write ${s.key}\x1b[0m`;
179
+ if (s.key === "preset") {
180
+ const current = detectPreset(text, defaults);
181
+ const cycle: PresetName[] = ["low", "medium", "ultra"];
182
+ const at = cycle.indexOf(current as PresetName);
183
+ const next = cycle[((at < 0 ? 0 : at) + step + cycle.length) % cycle.length]!;
184
+ const updated = applyPreset(text, next, defaults);
185
+ writeFileSync(file, updated);
186
+ saved = c.green(`applied preset: ${next}`);
187
+ } else {
188
+ const now = read(text, s.key) ?? s.values[0]!;
189
+ const at = s.values.indexOf(now);
190
+ const next = s.values[((at < 0 ? 0 : at) + step + s.values.length) % s.values.length]!;
191
+ let updated = write(text, s.key, next, defaults);
192
+ const detected = detectPreset(updated, defaults);
193
+ updated = write(updated, "preset", detected, defaults);
194
+ writeFileSync(file, updated);
195
+ // Only claim a save that happened. Reporting one that did not is worse
196
+ // than failing loudly: the setting reads back unchanged and nobody knows why.
197
+ saved = read(updated, s.key) === next
198
+ ? c.green(`saved ${s.key} = ${next || '""'} (preset: ${detected})`)
199
+ : `\x1b[33mcould not write ${s.key}\x1b[0m`;
200
+ }
97
201
  if (!rest.startsWith("\r")) i += 2;
98
202
  } else if (rest.startsWith("k")) active = (active - 1 + SETTINGS.length) % SETTINGS.length;
99
203
  else if (rest.startsWith("j")) active = (active + 1) % SETTINGS.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "Staged workflow for AI agents \u2014 code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,6 +81,7 @@ whichever the user picks.
81
81
 
82
82
  | Setting | Values | What it decides |
83
83
  |---|---|---|
84
+ | `preset` | `low`, `medium`, `ultra` | Quick preset: low (inline/manual), medium (balanced), ultra (autonomous) |
84
85
  | `mode` | `approve`, `auto`, `plan-only`, `review-only` | Where a run stops |
85
86
  | `runner` | `full`, `lite` | Whether each stage is its own cold agent, or this session runs them all |
86
87
  | `build.executor` | `subagent`, `inline`, `ask` | Who carries out the approved plan |
@@ -1,6 +1,11 @@
1
1
  # Kaizen defaults. Copy to .kaizen/config.yml in a repository and override only the
2
2
  # keys you care about; anything absent falls back to this file.
3
3
 
4
+ # Quick preset: low | medium | ultra | custom
5
+ # Selecting low, medium, or ultra applies its bundled defaults for runner, executor,
6
+ # approvals, and auto-fix. Modifying individual keys sets this to custom.
7
+ preset: medium
8
+
4
9
  # plan-only | approve | auto | review-only
5
10
  mode: approve
6
11
 
@@ -75,6 +75,7 @@ Each iteration is written to `runs/<id>/05-iterations/`.
75
75
 
76
76
  | Key | Default | Meaning |
77
77
  |---|---|---|
78
+ | `preset` | `medium` | Quick preset (`low`, `medium`, `ultra`, `custom`) |
78
79
  | `mode` | `approve` | Default run mode |
79
80
  | `runner` | `full` | `full`: each stage a cold subagent. `lite`: this session runs every stage |
80
81
  | `approvals.plan` | `true` | Stop after the plan |