kaizen-agent 1.7.8 → 1.8.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.
Files changed (47) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +694 -658
  3. package/agents/kaizen-builder.md +82 -77
  4. package/agents/kaizen-planner.md +250 -250
  5. package/agents/kaizen-reviewer.md +127 -127
  6. package/cli/board.test.ts +55 -0
  7. package/cli/dashboard.ts +1188 -1187
  8. package/cli/install.ts +76 -3
  9. package/cli/publish-gh.sh +27 -27
  10. package/cli/release.sh +45 -45
  11. package/cli/settings.ts +7 -3
  12. package/cli/sources.test.ts +494 -0
  13. package/cli/sources.ts +323 -0
  14. package/cli/state.test.ts +422 -197
  15. package/cli/state.ts +989 -700
  16. package/cli/web.ts +182 -11
  17. package/commands/kaizen-abort.md +5 -5
  18. package/commands/kaizen-approve.md +5 -5
  19. package/commands/kaizen-auto.md +6 -6
  20. package/commands/kaizen-backlog.md +5 -5
  21. package/commands/kaizen-config.md +5 -5
  22. package/commands/kaizen-full.md +5 -5
  23. package/commands/kaizen-gather.md +5 -0
  24. package/commands/kaizen-init.md +5 -5
  25. package/commands/kaizen-install.md +5 -5
  26. package/commands/kaizen-lite.md +5 -5
  27. package/commands/kaizen-plan.md +6 -6
  28. package/commands/kaizen-reject.md +6 -6
  29. package/commands/kaizen-review.md +6 -6
  30. package/commands/kaizen-run.md +5 -5
  31. package/commands/kaizen-status.md +5 -5
  32. package/package.json +3 -1
  33. package/skills/kaizen/README.md +283 -281
  34. package/skills/kaizen/SKILL.md +334 -440
  35. package/skills/kaizen/adapters.md +84 -77
  36. package/skills/kaizen/config.default.yml +181 -167
  37. package/skills/kaizen/gather.md +50 -0
  38. package/skills/kaizen/install.md +42 -0
  39. package/skills/kaizen/settings.md +52 -0
  40. package/skills/kaizen/spec-build.md +131 -119
  41. package/skills/kaizen/spec-main.md +109 -104
  42. package/skills/kaizen/spec-plan.md +201 -201
  43. package/skills/kaizen/spec-review.md +121 -121
  44. package/skills/kaizen/spec.md +154 -153
  45. package/skills/kaizen/status.md +43 -0
  46. package/skills/kaizen-help/SKILL.md +131 -129
  47. package/web/board.html +295 -126
package/cli/web.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  // `kaizen web`: the board in a browser. A loopback HTTP server over state.ts and
2
2
  // one HTML page; the page never sees the filesystem, only JSON.
3
- import { existsSync, readFileSync, readdirSync, statSync, watch, appendFileSync, type FSWatcher } from "node:fs";
3
+ import { existsSync, readFileSync, readdirSync, statSync, watch, appendFileSync, mkdirSync, type FSWatcher } from "node:fs";
4
4
  import { join, dirname } from "node:path";
5
5
  import {
6
- home, type Item, boardCards, knownProjects, remember, locate, label, tilde,
6
+ home, type Item, boardCards, knownProjects, remember, locate, label, tilde, searchRoots, findProjects,
7
7
  readInbox, writeInbox, replaceIdea, abandonRun, launchRun, parseItem, short, setArchived, writeNotes,
8
- appendNote, saveAttachment, pidOnPort,
8
+ appendNote, saveAttachment, pidOnPort, runGit, readCommit, commitPush, notice, notifier, LOGO,
9
+ reviewFindings, pickFindings, fixPrompt,
9
10
  } from "./state.ts";
11
+ import { readSources, addSource, removeSource, moveSource, gatherSource, gatherAll, gatherDirs, due } from "./sources.ts";
10
12
 
11
13
  const PAGE = join(dirname(import.meta.dir), "web", "board.html");
12
14
  const DEFAULT_PORT = 7420;
@@ -25,9 +27,32 @@ export async function web(repoDir: string, opts: Opts = {}) {
25
27
  if (ev === "uncaughtException") process.exit(1);
26
28
  });
27
29
  }
28
- const state = locate();
30
+ // Not fixed for the life of the board: picking a project in the page moves here.
31
+ let state = locate();
29
32
  if (state && dirname(state) !== home) remember(dirname(state));
30
- const project = state ? label(state) : null;
33
+ let project = state ? label(state) : null;
34
+ // Folders under the "Projects folder" setting that have no .kaizen yet, offered in
35
+ // the project list so one can be chosen without a terminal. A folder that does not
36
+ // look like a project itself (no .git, no package.json) is taken for a group of
37
+ // them, and its own folders are offered too: ~/Projects/<client>/<project>.
38
+ const fresh = async () => {
39
+ const { listSettings } = await import("./settings.ts");
40
+ let root = listSettings(repoDir).rows.find((r) => r.key === "board.projects_dir")?.value ?? "";
41
+ if (root.startsWith("~")) root = home + root.slice(1);
42
+ if (!root) return [];
43
+ const dirs = (d: string) => {
44
+ try {
45
+ return readdirSync(d).filter((n) => !n.startsWith(".") && n !== "node_modules")
46
+ .map((n) => join(d, n)).filter((p) => { try { return statSync(p).isDirectory(); } catch { return false; } });
47
+ } catch { return []; }
48
+ };
49
+ // .kaizen is not a sign of one: a group folder that was chosen once has it, and
50
+ // must keep showing what is inside.
51
+ const isProject = (d: string) => [".git", "package.json"].some((m) => existsSync(join(d, m)));
52
+ // One already set up but never opened joins the known projects instead.
53
+ return dirs(root).flatMap((d) => isProject(d) ? [d] : [d, ...dirs(d)])
54
+ .filter((d) => existsSync(join(d, ".kaizen")) ? (remember(d), false) : true).sort();
55
+ };
31
56
 
32
57
  const states = (all: boolean) => (all || !state
33
58
  ? [...knownProjects().map((d) => join(d, ".kaizen")), join(home, ".kaizen")]
@@ -78,7 +103,7 @@ export async function web(repoDir: string, opts: Opts = {}) {
78
103
  const fingerprint = () => {
79
104
  let sum = 0;
80
105
  for (const dir of states(true)) {
81
- for (const f of ["inbox.md", "backlog.md", "archive.md"]) { try { sum += statSync(join(dir, f)).mtimeMs; } catch { /* absent */ } }
106
+ for (const f of ["inbox.md", "backlog.md", "archive.md", "sources.json"]) { try { sum += statSync(join(dir, f)).mtimeMs; } catch { /* absent */ } }
82
107
  const runs = join(dir, "runs");
83
108
  if (!existsSync(runs)) continue;
84
109
  for (const id of readdirSync(runs)) { try { sum += statSync(join(runs, id, "state.json")).mtimeMs; } catch { /* absent */ } }
@@ -93,6 +118,28 @@ export async function web(repoDir: string, opts: Opts = {}) {
93
118
  const fp = fingerprint(); if (fp !== last) { last = fp; changed(); }
94
119
  }, 2000);
95
120
 
121
+ // The schedule: once a minute, gather every request source that is due. It writes
122
+ // inbox.md and sources.json only, never a state.json, and starts nothing. All of
123
+ // it sits in a try: an uncaught error here would end the board.
124
+ const every = async () => {
125
+ const { listSettings } = await import("./settings.ts");
126
+ return listSettings(repoDir).rows.find((r) => r.key === "sources.every")?.value ?? "off";
127
+ };
128
+ let busy = false;
129
+ const gather = setInterval(async () => {
130
+ if (busy) return;
131
+ busy = true;
132
+ try {
133
+ const ms = (parseInt(await every()) || 0) * 3600_000; // "off" parses to nothing
134
+ if (ms) for (const dir of states(true)) {
135
+ let list;
136
+ try { list = readSources(dir); } catch { continue; } // unreadable: left as it is
137
+ for (const s of list) if (due(s, ms, Date.now()) && (await gatherSource(dir, s.url)).added) changed();
138
+ }
139
+ } catch { /* the next tick tries again */ }
140
+ busy = false;
141
+ }, 60_000);
142
+
96
143
  const BOOT = Date.now().toString(36);
97
144
  const ping = setInterval(() => { for (const c of clients) { try { c.enqueue(": ping\n\n"); } catch { clients.delete(c); } } }, 8000);
98
145
  // A launcher clicked while a board is already up should show that board, not die.
@@ -133,18 +180,28 @@ export async function web(repoDir: string, opts: Opts = {}) {
133
180
 
134
181
  if (req.method === "GET" && path === "/") {
135
182
  if (!existsSync(PAGE)) return new Response("web/board.html missing", { status: 500 });
136
- return new Response(readFileSync(PAGE), {
183
+ // The page gets the notify rule from state.ts rather than keeping its own copy.
184
+ return new Response(readFileSync(PAGE, "utf8").replace("/*notifier*/", () => notifier.toString()), {
137
185
  headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
138
186
  });
139
187
  }
140
188
 
189
+ // The icon on a web notification; this one file and nothing beside it.
190
+ if (req.method === "GET" && path === "/logo.png") {
191
+ if (!existsSync(LOGO)) return bad("no logo", 404);
192
+ return new Response(Bun.file(LOGO), { headers: { "content-type": "image/png", "cache-control": "max-age=86400" } });
193
+ }
194
+
141
195
  if (req.method === "GET" && path === "/state") {
142
196
  const all = url.searchParams.get("all") === "1";
197
+ // Before the project list: it registers folders that turn out to be set up.
198
+ const unset = await fresh();
143
199
  const dirs = states(all);
144
200
  return json({
145
- project, all,
201
+ project, all, dir: state,
146
202
  projects: states(true).map((d) => ({ dir: d, label: label(d) })),
147
- cards: boardCards(dirs, Date.now()),
203
+ fresh: unset.map((d) => ({ dir: join(d, ".kaizen"), label: tilde(d) })),
204
+ cards: boardCards(dirs, Date.now()).map((c) => ({ ...c, notice: notice(c) })),
148
205
  now: Date.now(),
149
206
  });
150
207
  }
@@ -161,9 +218,27 @@ export async function web(repoDir: string, opts: Opts = {}) {
161
218
  request: read("00-request.md"), plan: read("01-plan.md"), approval: read("02-approval.md"),
162
219
  impl: read("03-impl.md"), review: read("04-review.md"), backlog: read("06-backlog.md"),
163
220
  notes: read("notes.md"),
221
+ // Nothing to pick in the global state dir: /fix refuses it.
222
+ findings: dir === join(home, ".kaizen") ? [] : reviewFindings(dir, id),
223
+ // The global state dir sits in $HOME, which is not a project to commit.
224
+ commit: readCommit(dir, id), git: dir === join(home, ".kaizen") ? { repo: false } : runGit(dir, id),
164
225
  });
165
226
  }
166
227
 
228
+ if (req.method === "GET" && path === "/sources") {
229
+ // One project's sources, or with no dir every known project's: each entry
230
+ // says which project it belongs to. Nothing is assumed from where the board started.
231
+ const dir = url.searchParams.get("dir");
232
+ if (dir && !states(true).includes(dir)) return bad("unknown state dir", 404);
233
+ const sources = [], failed = [];
234
+ for (const d of dir ? [dir] : states(true)) {
235
+ // The seen list is bookkeeping, not something the page shows.
236
+ try { sources.push(...readSources(d).map(({ seen, ...s }) => ({ ...s, dir: d }))); }
237
+ catch { failed.push(label(d)); }
238
+ }
239
+ return json({ sources, failed, every: await every() });
240
+ }
241
+
167
242
  if (req.method === "GET" && path === "/settings") {
168
243
  const { listSettings } = await import("./settings.ts");
169
244
  return json(listSettings(repoDir));
@@ -208,10 +283,38 @@ export async function web(repoDir: string, opts: Opts = {}) {
208
283
  if (!local(req)) return bad("cross-origin write refused", 403);
209
284
  let body: any;
210
285
  try { body = await req.json(); } catch { return bad("json body expected"); }
286
+
287
+ // The same search as the TUI's "find more": every .kaizen under $HOME, the
288
+ // drives, and where the board was started, added to the registry.
289
+ // ponytail: synchronous walk, the board stalls for its length; move to a worker if a big disk makes that seconds.
290
+ if (path === "/find") {
291
+ const before = knownProjects().length;
292
+ const roots = searchRoots([dirname(state ?? process.cwd()), process.cwd()]);
293
+ for (const d of new Set(roots.flatMap((r) => findProjects(r)))) remember(d);
294
+ changed();
295
+ return json({ ok: true, added: knownProjects().length - before });
296
+ }
297
+
211
298
  // No project here means the global inbox, which is where the TUI puts ideas too.
212
299
  const dir: string = body.dir ?? state ?? join(home, ".kaizen");
300
+ // Choosing a folder from the projects folder is what sets it up: an empty
301
+ // .kaizen, which the first run fills in. Only folders the board offered.
302
+ if (path === "/cd" && dir === join(dirname(dir), ".kaizen") && (await fresh()).includes(dirname(dir))) {
303
+ mkdirSync(dir, { recursive: true });
304
+ remember(dirname(dir));
305
+ }
213
306
  if (!dir || !states(true).includes(dir)) return bad("unknown state dir", 404);
214
307
 
308
+ // The board moves to the chosen project: new ideas and runs default to it,
309
+ // and the process follows so anything resolved from cwd agrees.
310
+ if (path === "/cd") {
311
+ state = dir;
312
+ project = label(dir);
313
+ try { process.chdir(dirname(dir)); } catch { /* the state dir is what matters */ }
314
+ changed();
315
+ return json({ ok: true, project });
316
+ }
317
+
215
318
  if (path === "/inbox") {
216
319
  const text = String(body.text ?? "").trim();
217
320
  // Absent means untouched; an empty string clears. replaceIdea keeps the
@@ -235,6 +338,40 @@ export async function web(repoDir: string, opts: Opts = {}) {
235
338
  return json({ ok: true });
236
339
  }
237
340
 
341
+ // Request sources. Gathering adds ideas and nothing else: no run starts here.
342
+ if (path === "/sources") {
343
+ const link = String(body.url ?? "").trim();
344
+ // A source belongs to the project the request names. The fallback above
345
+ // (where the board started, or the global dir) is a guess, and a guess
346
+ // here puts someone's requests in the wrong project.
347
+ const named = typeof body.dir === "string" && body.dir !== "";
348
+ try {
349
+ let results;
350
+ if (body.op === "gather" && !named && !link) {
351
+ results = await gatherDirs(states(true));
352
+ changed();
353
+ return json({ ok: true, results });
354
+ }
355
+ if (!named) return bad(body.op === "add" ? "choose the project this source belongs to" : "say which project's source this is");
356
+ if (body.op === "add") {
357
+ const why = addSource(dir, link, typeof body.label === "string" ? body.label : undefined);
358
+ if (why) return bad(why);
359
+ } else if (body.op === "remove") {
360
+ const why = removeSource(dir, link);
361
+ if (why) return bad(why, 404);
362
+ } else if (body.op === "move") {
363
+ if (typeof body.to !== "string" || !states(true).includes(body.to)) return bad("unknown state dir", 404);
364
+ const why = moveSource(dir, body.to, link);
365
+ if (why) return bad(why, why === "no such source" ? 404 : 409);
366
+ } else if (body.op === "gather") {
367
+ if (link && !readSources(dir).some((s) => s.url === link)) return bad("no such source", 404);
368
+ results = link ? [await gatherSource(dir, link)] : await gatherAll(dir);
369
+ } else return bad("unknown op");
370
+ changed();
371
+ return json({ ok: true, sources: readSources(dir).map(({ seen, ...s }) => s), results });
372
+ } catch (e) { return bad((e as Error).message, 500); }
373
+ }
374
+
238
375
  if (path === "/run") {
239
376
  const text = String(body.text ?? "").trim();
240
377
  const kind = body.kind === "full" || body.kind === "lite" ? body.kind : "";
@@ -299,6 +436,40 @@ export async function web(repoDir: string, opts: Opts = {}) {
299
436
  return failed ? bad(failed, 404) : json({ ok: true });
300
437
  }
301
438
 
439
+ // Only a finished run, or one waiting at the final approval, and never the
440
+ // global state dir: $HOME may be a repository, and it is not the run's project.
441
+ if (path === "/commit") {
442
+ const id = String(body.id ?? "");
443
+ if (!/^[\w.-]+$/.test(id)) return bad("bad run id");
444
+ if (dir === join(home, ".kaizen")) return bad("no project to commit", 409);
445
+ let run: any;
446
+ try { run = JSON.parse(readFileSync(join(dir, "runs", id, "state.json"), "utf8")); } catch { return bad("no such run", 404); }
447
+ if (run.stage !== "done" && run.awaiting !== "approvals.review") return bad("this run is not finished", 409);
448
+ const r = await commitPush(dir, id, String(body.message ?? ""), Array.isArray(body.files) ? body.files.map(String) : []);
449
+ changed();
450
+ return json(r, r.ok ? 200 : 409);
451
+ }
452
+
453
+ // Fix picked findings, on the same runs /commit takes and for the same reason
454
+ // never in the global state dir: the agent starts in a project, and the run is
455
+ // not there. Only numbers the review itself holds reach the prompt.
456
+ if (path === "/fix") {
457
+ const id = String(body.id ?? "");
458
+ if (!/^[\w.-]+$/.test(id)) return bad("bad run id");
459
+ if (dir === join(home, ".kaizen")) return bad("no project to fix in", 409);
460
+ let run: any;
461
+ try { run = JSON.parse(readFileSync(join(dir, "runs", id, "state.json"), "utf8")); } catch { return bad("no such run", 404); }
462
+ if (run.stage !== "done" && run.awaiting !== "approvals.review" && run.awaiting !== "findings") return bad("this run is still being worked on", 409);
463
+ const nums = pickFindings(dir, id, body.nums);
464
+ if (!nums) return bad("pick open findings of this review");
465
+ const text = fixPrompt(id, nums);
466
+ const r = launchRun({ severity: null, where: null, text, raw: text }, dir);
467
+ changed();
468
+ return json(r.ok
469
+ ? { ok: true, agent: r.agent }
470
+ : { ok: false, why: r.why, manual: r.manual, agent: r.agent }, r.ok ? 200 : 500);
471
+ }
472
+
302
473
  if (path === "/abort") {
303
474
  const id = String(body.id ?? "");
304
475
  const why = String(body.why ?? "").trim() || "abandoned from the web board";
@@ -320,7 +491,7 @@ export async function web(repoDir: string, opts: Opts = {}) {
320
491
  if (opts.open !== false) await openApp(url);
321
492
 
322
493
  await new Promise<void>((resolve) => {
323
- const stop = () => { clearInterval(poll); clearInterval(ping); for (const w of watchers) { try { w.close(); } catch { /* gone */ } } server.stop(true); resolve(); };
494
+ const stop = () => { clearInterval(poll); clearInterval(ping); clearInterval(gather); for (const w of watchers) { try { w.close(); } catch { /* gone */ } } server.stop(true); resolve(); };
324
495
  process.on("SIGINT", stop);
325
496
  process.on("SIGTERM", stop);
326
497
  });
@@ -378,7 +549,7 @@ function winChrome(exe: string) {
378
549
  export async function shortcut() {
379
550
  const bun = process.execPath;
380
551
  const script = Bun.argv[1]!;
381
- const icon = join(dirname(import.meta.dir), "assets", "kaizen-logo.png");
552
+ const icon = LOGO;
382
553
  const hasIcon = existsSync(icon);
383
554
  const { mkdirSync, writeFileSync, chmodSync } = await import("node:fs");
384
555
 
@@ -1,5 +1,5 @@
1
- ---
2
- description: Mark the current kaizen run abandoned
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `abort`.
1
+ ---
2
+ description: Mark the current kaizen run abandoned
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `abort`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Approve whatever the current kaizen run is waiting on
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `approve`.
1
+ ---
2
+ description: Approve whatever the current kaizen run is waiting on
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `approve`.
@@ -1,6 +1,6 @@
1
- ---
2
- description: Start a kaizen run in auto mode — every stage back to back, no approval stops
3
- argument-hint: "<request>"
4
- ---
5
-
6
- Invoke the `kaizen` skill with argument `auto $ARGUMENTS`.
1
+ ---
2
+ description: Start a kaizen run in auto mode — every stage back to back, no approval stops
3
+ argument-hint: "<request>"
4
+ ---
5
+
6
+ Invoke the `kaizen` skill with argument `auto $ARGUMENTS`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Print open kaizen backlog items across all runs, grouped by source run
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `backlog`.
1
+ ---
2
+ description: Print open kaizen backlog items across all runs, grouped by source run
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `backlog`.
@@ -1,5 +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`.
1
+ ---
2
+ description: Show the kaizen settings for this project and change them interactively
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `config`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Run kaizen with every stage as its own cold agent — the independent review
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `full $ARGUMENTS`.
1
+ ---
2
+ description: Run kaizen with every stage as its own cold agent — the independent review
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `full $ARGUMENTS`.
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: Gather new requests from the project's request sources into kaizen ideas
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `gather`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Set up this project folder for kaizen (spec, config, and a gitignore entry when it is a git repo)
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `init`.
1
+ ---
2
+ description: Set up this project folder for kaizen (spec, config, and a gitignore entry when it is a git repo)
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `init`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Install the kaizen workflow itself, globally or per-project
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `install`.
1
+ ---
2
+ description: Install the kaizen workflow itself, globally or per-project
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `install`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Run kaizen with every stage in this session — cheaper, and the reviewer has seen the work
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `lite $ARGUMENTS`.
1
+ ---
2
+ description: Run kaizen with every stage in this session — cheaper, and the reviewer has seen the work
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `lite $ARGUMENTS`.
@@ -1,6 +1,6 @@
1
- ---
2
- description: Start a kaizen run and stop after the plan (plan-only)
3
- argument-hint: "<request>"
4
- ---
5
-
6
- Invoke the `kaizen` skill with argument `plan $ARGUMENTS`.
1
+ ---
2
+ description: Start a kaizen run and stop after the plan (plan-only)
3
+ argument-hint: "<request>"
4
+ ---
5
+
6
+ Invoke the `kaizen` skill with argument `plan $ARGUMENTS`.
@@ -1,6 +1,6 @@
1
- ---
2
- description: Reject the kaizen approval the current run is waiting on — reason decides revise (back to planner) vs kill (abandon)
3
- argument-hint: "<reason>"
4
- ---
5
-
6
- Invoke the `kaizen` skill with argument `reject $ARGUMENTS`.
1
+ ---
2
+ description: Reject the kaizen approval the current run is waiting on — reason decides revise (back to planner) vs kill (abandon)
3
+ argument-hint: "<reason>"
4
+ ---
5
+
6
+ Invoke the `kaizen` skill with argument `reject $ARGUMENTS`.
@@ -1,6 +1,6 @@
1
- ---
2
- description: Review-only kaizen run — audit existing code/work, no plan, no implementation
3
- argument-hint: "[target]"
4
- ---
5
-
6
- Invoke the `kaizen` skill with argument `review $ARGUMENTS`.
1
+ ---
2
+ description: Review-only kaizen run — audit existing code/work, no plan, no implementation
3
+ argument-hint: "[target]"
4
+ ---
5
+
6
+ Invoke the `kaizen` skill with argument `review $ARGUMENTS`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: Resume the current kaizen run and execute the approved plan
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `run`.
1
+ ---
2
+ description: Resume the current kaizen run and execute the approved plan
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `run`.
@@ -1,5 +1,5 @@
1
- ---
2
- description: List every kaizen run grouped waiting-on-you / in flight / done / abandoned, plus open backlog count
3
- ---
4
-
5
- Invoke the `kaizen` skill with argument `status`.
1
+ ---
2
+ description: List every kaizen run grouped waiting-on-you / in flight / done / abandoned, plus open backlog count
3
+ ---
4
+
5
+ Invoke the `kaizen` skill with argument `status`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-agent",
3
- "version": "1.7.8",
3
+ "version": "1.8.0",
4
4
  "description": "Staged workflow for AI agents — code, writing, research, operations: plan, human approval, build, independent review, bounded fix loop.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,8 @@
21
21
  "scripts": {
22
22
  "install-links": "bun run cli/install.ts",
23
23
  "check": "bun run cli/install.ts --check",
24
+ "test": "bun test",
25
+ "prepublishOnly": "bun test",
24
26
  "postpublish": "sh cli/release.sh",
25
27
  "publish:gh": "sh cli/publish-gh.sh",
26
28
  "release": "bun publish && bun run publish:gh"