kaizen-agent 0.3.0 → 0.5.0

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.
@@ -0,0 +1,160 @@
1
+ // Full-screen settings browser for .kaizen/config.yml.
2
+ // Arrow keys move and change; every change is written straight to the file.
3
+ import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { homedir } from "node:os";
6
+
7
+ type Setting = { key: string; values: string[]; help: string };
8
+
9
+ const SETTINGS: Setting[] = [
10
+ { key: "mode", values: ["approve", "auto", "plan-only", "review-only"], help: "Where a run stops" },
11
+ { key: "build.executor", values: ["subagent", "inline", "ask"], help: "Who carries out the approved plan" },
12
+ { key: "approvals.plan", values: ["true", "false"], help: "Stop and show the plan before anything is built" },
13
+ { key: "approvals.review", values: ["true", "false"], help: "Stop after the review, before the run is done" },
14
+ { key: "approvals.each_file", values: ["true", "false"], help: "Confirm every individual edit" },
15
+ { key: "auto_fix.enabled", values: ["true", "false"], help: "Fix findings without asking" },
16
+ { key: "auto_fix.min_severity", values: ["critical", "high", "medium", "low"], help: "How bad a finding must be to be fixed automatically" },
17
+ { key: "auto_fix.max_iterations", values: ["1", "2", "3", "4", "5"], help: "Fix and recheck rounds before escalating" },
18
+ { key: "review.write_memory", values: ["true", "false"], help: "Let the reviewer append lessons to memory.md" },
19
+ { key: "review.memory_max_lines", values: ["50", "100", "200", "400"], help: "Size cap on memory.md, a tax on every run" },
20
+ { key: "track.default", values: ["", "software", "writing", "communication", "operations", "research"], help: "Pin a track, or leave empty to infer per run" },
21
+ { key: "state.keep_runs", values: ["5", "10", "20", "50"], help: "Completed runs kept before the oldest is pruned" },
22
+ { key: "git.auto_commit", values: ["false", "true"], help: "Commit the work when a run finishes" },
23
+ { key: "git.branch_before_implement", values: ["true", "false"], help: "Branch before building when on the default branch" },
24
+ ];
25
+
26
+ const c = {
27
+ dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
28
+ bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
29
+ cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
30
+ green: (s: string) => `\x1b[32m${s}\x1b[0m`,
31
+ inv: (s: string) => `\x1b[7m${s}\x1b[0m`,
32
+ };
33
+
34
+ export async function settings(repoRoot: string, standalone = true) {
35
+ const file = locate();
36
+ if (!existsSync(file)) {
37
+ mkdirSync(dirname(file), { recursive: true });
38
+ copyFileSync(join(repoRoot, "skills/kaizen/config.default.yml"), file);
39
+ }
40
+
41
+ const { stdin, stdout } = process;
42
+ if (!stdin.isTTY) { console.log(`Settings live in ${file}`); return; }
43
+
44
+ let active = 0, saved = "";
45
+ const draw = () => {
46
+ const text = readFileSync(file, "utf8");
47
+ const rows = SETTINGS.map((s) => ({ s, value: read(text, s.key) ?? c.dim("(default)") }));
48
+ const width = Math.max(...SETTINGS.map((s) => s.key.length)) + 4;
49
+
50
+ stdout.write("\x1b[H\x1b[2J"); // home, clear
51
+ stdout.write(`\n ${c.bold("kaizen settings")} ${c.dim(tilde(file))}\n\n`);
52
+ for (const [i, { s, value }] of rows.entries()) {
53
+ const on = i === active;
54
+ const name = s.key.padEnd(width);
55
+ stdout.write(on
56
+ ? ` ${c.cyan("›")} ${c.bold(name)}${c.inv(` ${value} `)}\n`
57
+ : ` ${c.dim(name)}${value}\n`);
58
+ }
59
+ stdout.write(`\n ${c.dim(SETTINGS[active]!.help)}\n`);
60
+ stdout.write(`\n ${c.dim("↑↓ move · ←→ change · backspace back")} ${saved}\n`);
61
+ };
62
+
63
+ stdout.write("\x1b[?1049h\x1b[?25l"); // alternate screen, hide cursor
64
+ stdin.setRawMode(true);
65
+ stdin.resume();
66
+ draw();
67
+
68
+ await new Promise<void>((resolve) => {
69
+ const onData = (chunk: Buffer) => {
70
+ const keys = chunk.toString();
71
+ for (let i = 0; i < keys.length; i++) {
72
+ const rest = keys.slice(i);
73
+ // esc, q, backspace, and left-at-the-edge all mean "back to where I came
74
+ // from" -- the settings screen is always something you opened from
75
+ // somewhere else.
76
+ if (rest.startsWith("\x03") || rest.startsWith("q") || rest === "\x1b"
77
+ || rest.startsWith("\x7f") || rest.startsWith("\b")) {
78
+ stdin.off("data", onData); return resolve();
79
+ }
80
+ if (rest.startsWith("\x1b[A")) { active = (active - 1 + SETTINGS.length) % SETTINGS.length; i += 2; }
81
+ else if (rest.startsWith("\x1b[B")) { active = (active + 1) % SETTINGS.length; i += 2; }
82
+ else if (rest.startsWith("\x1b[C") || rest.startsWith("\x1b[D") || rest.startsWith("\r")) {
83
+ const step = rest.startsWith("\x1b[D") ? -1 : 1;
84
+ const s = SETTINGS[active]!;
85
+ const text = readFileSync(file, "utf8");
86
+ const now = read(text, s.key) ?? s.values[0]!;
87
+ const at = s.values.indexOf(now);
88
+ const next = s.values[((at < 0 ? 0 : at) + step + s.values.length) % s.values.length]!;
89
+ writeFileSync(file, write(text, s.key, next));
90
+ saved = c.green(`saved ${s.key} = ${next || '""'}`);
91
+ if (!rest.startsWith("\r")) i += 2;
92
+ } else if (rest.startsWith("k")) active = (active - 1 + SETTINGS.length) % SETTINGS.length;
93
+ else if (rest.startsWith("j")) active = (active + 1) % SETTINGS.length;
94
+ else continue;
95
+ draw();
96
+ }
97
+ };
98
+ stdin.on("data", onData);
99
+ });
100
+
101
+ stdin.setRawMode(false);
102
+ stdin.pause();
103
+ stdout.write("\x1b[?25h\x1b[?1049l"); // restore cursor and screen
104
+ // Returning to the dashboard means returning to its alternate screen, where a
105
+ // line printed here would never be seen.
106
+ if (standalone) console.log(` ${c.green("+")} settings saved to ${tilde(file)}\n`);
107
+ }
108
+
109
+ // The nearest .kaizen wins, exactly as the workflow resolves it.
110
+ function locate() {
111
+ let dir = process.cwd();
112
+ for (;;) {
113
+ if (existsSync(join(dir, ".kaizen"))) return join(dir, ".kaizen", "config.yml");
114
+ if (existsSync(join(dir, ".git"))) break;
115
+ const up = dirname(dir);
116
+ if (up === dir) break;
117
+ dir = up;
118
+ }
119
+ return join(homedir(), ".kaizen", "config.yml");
120
+ }
121
+
122
+ // Read and write a dotted key by walking indentation, so the comment above every
123
+ // key survives. Parsing to an object and dumping it back would delete all of them.
124
+ function lineOf(text: string, key: string) {
125
+ const parts = key.split(".");
126
+ const lines = text.split("\n");
127
+ let depth = 0, from = 0;
128
+ for (const [pi, part] of parts.entries()) {
129
+ const want = new RegExp(`^ {${depth}}${part}:`);
130
+ let found = -1;
131
+ for (let i = from; i < lines.length; i++) {
132
+ if (want.test(lines[i]!)) { found = i; break; }
133
+ if (pi > 0 && /^\S/.test(lines[i]!) && i > from) break; // left the parent block
134
+ }
135
+ if (found < 0) return { lines, index: -1, indent: depth };
136
+ from = found + 1; depth += 2;
137
+ }
138
+ return { lines, index: from - 1, indent: depth - 2 };
139
+ }
140
+
141
+ function read(text: string, key: string) {
142
+ const { lines, index } = lineOf(text, key);
143
+ if (index < 0) return null;
144
+ const v = lines[index]!.split(":").slice(1).join(":").split("#")[0]!.trim();
145
+ return v.replace(/^["']|["']$/g, "");
146
+ }
147
+
148
+ function write(text: string, key: string, value: string) {
149
+ const { lines, index, indent } = lineOf(text, key);
150
+ if (index < 0) return text;
151
+ const name = key.split(".").pop()!;
152
+ const shown = value === "" ? '""' : value;
153
+ lines[index] = `${" ".repeat(indent)}${name}: ${shown}`;
154
+ return lines.join("\n");
155
+ }
156
+
157
+ function tilde(p: string) {
158
+ const h = homedir();
159
+ return p.startsWith(h) ? "~" + p.slice(h.length) : p;
160
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Show the kaizen settings for this project and change them interactively
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `config`.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "0.3.0",
4
- "description": "Staged AI development workflow: plan, human approval, implement, independent review, bounded fix loop.",
3
+ "version": "0.5.0",
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": {
7
- "kaizen-agent": "./cli/install.ts"
7
+ "kaizen-agent": "./cli/install.ts",
8
+ "kaizen": "./cli/install.ts"
8
9
  },
9
10
  "files": [
10
11
  "cli",
@@ -30,6 +31,8 @@
30
31
  "skill",
31
32
  "codex",
32
33
  "antigravity",
33
- "code-review"
34
+ "code-review",
35
+ "writing",
36
+ "research"
34
37
  ]
35
38
  }
@@ -1,12 +1,14 @@
1
1
  ---
2
2
  name: kaizen
3
3
  description: >
4
- Staged AI development workflow: plan, human approval, implementation, then
5
- independent bug/security review with a bounded fix loop. Each stage runs as its own
4
+ Staged workflow for any kind of work: plan, human approval, build, then independent
5
+ review with a bounded fix loop. Not only for code — a run infers its own track (code,
6
+ writing, communication, operations, research) and every stage reads "deliverable",
7
+ "verification", and "finding" in that track's terms. Each stage runs as its own
6
8
  subagent with clean context, and all state lives in a portable `.kaizen/` directory so
7
9
  a run can be resumed later, by another session, or by another AI tool (Codex,
8
10
  Antigravity). Use when the user says "kaizen", "/kaizen", asks to plan-then-build-then-review
9
- a change, wants approval steps before code is written, or wants to resume or check an
11
+ something, wants approval steps before work begins, or wants to resume or check an
10
12
  existing kaizen run. Also use to install the workflow into a repository.
11
13
  ---
12
14
 
@@ -44,6 +46,7 @@ Porting the workflow to Codex or Antigravity is covered in [`adapters.md`](adapt
44
46
  | `/kaizen approve` | Approve whatever the current run is waiting on |
45
47
  | `/kaizen reject <reason>` | Reject it; the reason is fed back to the stage that produced it |
46
48
  | `/kaizen abort` | Mark the current run abandoned |
49
+ | `/kaizen config` | Show this project's settings and change them, one picker per setting |
47
50
  | `/kaizen init` | Install `.kaizen/` with the `spec*.md` files, config, and the gitignore entry into the current repository |
48
51
  | `/kaizen install` | Install the workflow itself globally or per-project (see below) |
49
52
 
@@ -64,6 +67,48 @@ If the user typed `/kaizen` with no argument and a run is in progress, treat it
64
67
  4. Read `.kaizen/memory.md` if present. It holds cross-run lessons and carries real
65
68
  weight: it is what previous reviewers learned about this codebase.
66
69
 
70
+ ## Settings
71
+
72
+ `/kaizen config` reads `.kaizen/config.yml`, falling back to `config.default.yml` for
73
+ anything it does not set, and prints the current value of each setting below with a
74
+ one-line meaning. Then it offers the settings as choices — the tool's structured
75
+ question mechanism, one option per legal value, never free text — and writes back
76
+ whichever the user picks.
77
+
78
+ | Setting | Values | What it decides |
79
+ |---|---|---|
80
+ | `mode` | `approve`, `auto`, `plan-only`, `review-only` | Where a run stops |
81
+ | `build.executor` | `subagent`, `inline`, `ask` | Who carries out the approved plan |
82
+ | `approvals.plan` | `true`, `false` | Stop and show the plan before anything is built |
83
+ | `approvals.review` | `true`, `false` | Stop after the review, before the run is called done |
84
+ | `approvals.each_file` | `true`, `false` | Confirm every individual edit |
85
+ | `auto_fix.enabled` | `true`, `false` | Fix findings without asking, or hand every one over |
86
+ | `auto_fix.min_severity` | `critical`, `high`, `medium`, `low` | How bad a finding must be to be fixed automatically |
87
+ | `auto_fix.max_iterations` | `1`–`5` | Fix and recheck rounds before what is left is escalated |
88
+ | `review.write_memory` | `true`, `false` | Let the reviewer append lessons to `memory.md` |
89
+ | `git.auto_commit` | `true`, `false` | Commit the work when a run finishes |
90
+ | `git.branch_before_implement` | `true`, `false` | Branch before building when on the default branch |
91
+
92
+ Rules for writing the file:
93
+
94
+ - **Change the value in place.** `config.yml` ships with a comment above every key
95
+ explaining it; rewriting the file from a parsed object throws all of them away.
96
+ Edit the one line.
97
+ - **Only what the user picked.** A key the user did not touch stays absent if it was
98
+ absent — an absent key inherits the default, and writing every key out freezes
99
+ today's defaults into the project forever.
100
+ - If `.kaizen/` does not exist yet, run `init` first, then continue.
101
+ - After writing, print the setting and its new value, and nothing else. No summary of
102
+ the whole file.
103
+
104
+ Settings not in the table — the review checks, backlog and state paths — are edited by
105
+ hand in `config.yml`, which is commented throughout. Say so rather than offering a
106
+ picker with thirty options.
107
+
108
+ `kaizen settings` in a terminal opens the same settings as a full-screen browser, for
109
+ someone who would rather see the whole file at once. Mention it when the user is
110
+ changing several at a time.
111
+
67
112
  ## Modes
68
113
 
69
114
  Set by `mode` in config, overridable per invocation.
@@ -171,6 +216,13 @@ are never renamed or moved, so a path written into another file cannot break.
171
216
 
172
217
  ## Running a stage
173
218
 
219
+ **If the tool you are running in has no subagents** — Codex, and anything else that
220
+ runs one session at a time — do not try to fake them. Run the stages sequentially in
221
+ the one session, following [`adapters.md`](adapters.md): start each stage by reading
222
+ only its declared inputs, write its artifact before moving on, and when reviewing,
223
+ judge the work on its own terms rather than defending the reasoning you used writing
224
+ it. Everything else in this file holds unchanged.
225
+
174
226
  Each stage runs as a subagent via the Agent tool, with the agent type named below.
175
227
  Pass it the run directory path and let it read its own inputs from there — do not
176
228
  paste plans or diffs into the prompt, since the file is the shared source of truth
@@ -180,9 +232,15 @@ file, never the whole set.
180
232
  | Stage | Agent type | Writes |
181
233
  |---|---|---|
182
234
  | Plan | `kaizen-planner` | `01-plan.md` |
183
- | Implement | `kaizen-builder` | `03-impl.md` + the actual code changes |
235
+ | Implement | `kaizen-builder`, or the main thread when `build.executor` is `inline` | `03-impl.md` + the actual changes |
184
236
  | Review | `kaizen-reviewer` | `04-review.md` |
185
237
 
238
+ `build.executor` decides who implements: `subagent` (default), `inline` — the main
239
+ thread does it against the same contract in `spec-build.md` — or `ask`, which puts
240
+ the choice at the plan approval. Building inline does not make the review less
241
+ independent: the reviewer is a separate agent in every case, and never sees the
242
+ conversation the work happened in.
243
+
186
244
  After a subagent returns, update `state.json` yourself in the main thread. Subagents
187
245
  report; the main thread owns the state machine.
188
246
 
@@ -14,6 +14,19 @@ approvals:
14
14
  # Confirm every individual file edit. Maximum control, slowest.
15
15
  each_file: false
16
16
 
17
+ build:
18
+ # Who carries out the approved plan.
19
+ # subagent - dispatch the builder as its own agent. The main thread keeps its
20
+ # context and the work is reported back. The default, and the only
21
+ # sane option for a large run.
22
+ # inline - the main thread does the work itself, against the same plan and the
23
+ # same contract. You watch it happen and can interrupt mid-step, at
24
+ # the cost of main-thread context.
25
+ # ask - offer the choice at the plan approval.
26
+ # The reviewer is a separate agent in every case; that is not configurable, because
27
+ # an agent reviewing work it just did will defend it.
28
+ executor: subagent
29
+
17
30
  auto_fix:
18
31
  # false: every finding stops the run and becomes a user decision.
19
32
  # true: findings at or above min_severity are fixed and re-reviewed automatically.
@@ -10,7 +10,17 @@ builder's contract. The planner and reviewer do not read it.
10
10
  **Output:** the deliverable itself, plus `03-impl.md`.
11
11
 
12
12
  The builder produces whatever the track's deliverable is — code, a chapter, the
13
- campaign copy, the runbook — and nothing else. Rules:
13
+ campaign copy, the runbook — and nothing else.
14
+
15
+ This file is the contract whether the builder is a dispatched agent or the main
16
+ thread working inline (see `build.executor`). Working inline changes who is typing,
17
+ not what is owed: the plan is still the scope, a step that turns out wrong still
18
+ stops the stage instead of being improvised around, and `03-impl.md` is still written
19
+ before the reviewer is called. The temptation inline is to skip the file because the
20
+ work is already visible in the conversation — but the reviewer never sees that
21
+ conversation, and reads the file instead.
22
+
23
+ Rules:
14
24
 
15
25
  - Work only within the plan's scope. Something out of scope that clearly needs doing
16
26
  is written to `03-impl.md` as a note, not produced.
@@ -90,6 +90,23 @@ Present to the user, in this order:
90
90
  be readable on its own, without the plan's reasoning around it.
91
91
  3. The open questions, **as a pick list** — see below.
92
92
 
93
+ **Who builds it.** Governed by `build.executor`. On `subagent` or `inline` the
94
+ approval says which is about to happen in one line and moves on; on `ask` it is one
95
+ more choice at this approval:
96
+
97
+ - **subagent** (default) — dispatch the builder as its own agent. The main thread
98
+ keeps its context, and the work is reported back rather than watched. Right for
99
+ most runs, and the only sane option for a large one.
100
+ - **inline** — the main thread does the work itself, against this plan. You watch it
101
+ happen and can interrupt mid-step, and it already holds the conversation the plan
102
+ came out of. It costs main-thread context, and a long run will exhaust it.
103
+
104
+ Either way the builder's contract is unchanged: the plan is the scope, `03-impl.md`
105
+ is written the same way, and the gate is answered with the same evidence. **The
106
+ reviewer is always a separate agent, in both.** Its independence is what the review
107
+ is worth, and it is not the user's to trade away — an inline build is judged by an
108
+ agent that never saw it happen, exactly as a dispatched one is.
109
+
93
110
  Do not dump the whole plan into the terminal; point at its path for the detail.
94
111
 
95
112
  **Ask with options, not open prose.** Every question the plan raises is put to the
@@ -30,6 +30,7 @@ anything.
30
30
  | `/kaizen approve` | Approve whatever the current run is waiting on |
31
31
  | `/kaizen reject <reason>` | Reject it; the reason decides revise (back to planner) vs kill (abandon) |
32
32
  | `/kaizen abort` | Mark the current run abandoned |
33
+ | `/kaizen config` | Show this project's settings and change them, one picker per setting |
33
34
  | `/kaizen init` | Install `.kaizen/` with `spec.md`, config, and the gitignore entry into the current repository |
34
35
  | `/kaizen install` | Install the workflow itself globally or per-project |
35
36
 
@@ -74,11 +75,12 @@ Each iteration is written to `runs/<id>/05-iterations/`.
74
75
  |---|---|---|
75
76
  | `mode` | `approve` | Default run mode |
76
77
  | `approvals.plan` | `true` | Stop after the plan |
77
- | `approvals.review` | `true` | Stop after the review |
78
+ | `approvals.review` | `false` | Stop after the review |
78
79
  | `approvals.each_file` | `false` | Confirm every file edit |
80
+ | `build.executor` | `subagent` | Who builds: `subagent`, `inline` (main thread), or `ask` at the approval |
79
81
  | `auto_fix.enabled` | `true` | Auto-fix findings vs. escalate everything |
80
82
  | `auto_fix.min_severity` | `high` | Threshold for auto-fix (`critical\|high\|medium\|low`) |
81
- | `auto_fix.max_iterations` | `3` | Fix/recheck rounds before escalating remaining findings |
83
+ | `auto_fix.max_iterations` | `2` | Fix/recheck rounds before escalating remaining findings |
82
84
  | `track.default` | `""` (empty, inferred per run) | Pin a track for a repo that is always one kind of work |
83
85
  | `review.write_memory` | `true` | Let the reviewer append lessons to `.kaizen/memory.md` |
84
86
  | `backlog.enabled` | `true` | Capture what a run deliberately did not do |