kaizen-agent 0.7.2 → 0.7.4

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`,
@@ -250,8 +267,32 @@ async function anyKey() {
250
267
 
251
268
  const repo = await resolveRepo();
252
269
  const root = await chooseRoot();
270
+ const presetChoice = await choosePreset();
253
271
  const targets = found(root);
254
272
 
273
+ async function choosePreset(): Promise<PresetName> {
274
+ if (presetArg) return presetArg;
275
+ if (!interactive) return "medium";
276
+
277
+ return select("Choose workflow preset", [
278
+ {
279
+ label: "Medium (Recommended)",
280
+ hint: "balanced — subagents, plan approval, auto-fix high/critical",
281
+ value: "medium",
282
+ },
283
+ {
284
+ label: "Low",
285
+ hint: "inline & manual — lite runner, inline builder, manual approvals, no auto-fix",
286
+ value: "low",
287
+ },
288
+ {
289
+ label: "Ultra",
290
+ hint: "autonomous — subagents, auto mode, auto-fix all findings (up to 4 rounds)",
291
+ value: "ultra",
292
+ },
293
+ ]);
294
+ }
295
+
255
296
  // ---------------------------------------------------------------- repo
256
297
 
257
298
  // Settings only needs the clone's default config, not a pull.
@@ -392,6 +433,18 @@ function installLauncher() {
392
433
 
393
434
  const inProject = process.cwd() !== home;
394
435
  const kaizenDir = join(process.cwd(), ".kaizen");
436
+ const globalKaizen = join(home, ".kaizen");
437
+ const globalConfig = join(globalKaizen, "config.yml");
438
+
439
+ if (!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 conf = applyPreset(base, presetChoice, def);
445
+ writeFileSync(globalConfig, conf);
446
+ step(`configured ${c.bold(presetChoice)} preset in ${tilde(globalConfig)}`);
447
+ }
395
448
 
396
449
  // A .kaizen/ in a parent already owns this folder's state, and setting up a child
397
450
  // would shadow it, hiding the parent's runs and leaving the child out of the root
@@ -413,25 +466,35 @@ function kaizenAbove() {
413
466
  }
414
467
 
415
468
  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();
469
+ const now = root === process.cwd()
470
+ ? true
471
+ : interactive
472
+ ? await select(`Set up ${basename(process.cwd())}/ for kaizen now`, [
473
+ // "Not now" leads: the offer now appears in any folder outside home, so the
474
+ // safe answer is the one under the cursor.
475
+ { label: "Not now", hint: "the first /kaizen run will do it", value: false },
476
+ { label: "Yes", hint: "writes .kaizen/ and a CLAUDE.md line so it runs by default", value: true },
477
+ ])
478
+ : false;
479
+ if (now) initProject(presetChoice);
425
480
  } else if (existsSync(kaizenDir)) {
426
481
  step(`${basename(process.cwd())}/.kaizen already set up`);
427
482
  }
428
483
 
429
- function initProject() {
484
+ function initProject(preset?: PresetName | null) {
430
485
  mkdirSync(kaizenDir, { recursive: true });
431
486
  for (const f of readdirSync(join(repo, "skills/kaizen")).filter((f) => f.startsWith("spec"))) {
432
487
  copyFileSync(join(repo, "skills/kaizen", f), join(kaizenDir, f));
433
488
  }
434
- copyFileSync(join(repo, "skills/kaizen/config.default.yml"), join(kaizenDir, "config.yml"));
489
+ const defPath = join(repo, "skills/kaizen/config.default.yml");
490
+ const def = readFileSync(defPath, "utf8");
491
+ let chosen: PresetName = preset ?? "medium";
492
+ if (!preset && existsSync(globalConfig)) {
493
+ const detected = detectPreset(readFileSync(globalConfig, "utf8"), def);
494
+ if (detected !== "custom") chosen = detected;
495
+ }
496
+ const configContent = applyPreset(def, chosen, def);
497
+ writeFileSync(join(kaizenDir, "config.yml"), configContent);
435
498
 
436
499
  // Run state is local; the workflow itself is shared, so those files stay tracked.
437
500
  // 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.4",
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 |