kaizen-agent 1.2.2 → 1.3.1
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 +4 -3
- package/cli/dashboard.ts +1 -1
- package/cli/install.ts +47 -20
- package/cli/settings.ts +5 -2
- package/package.json +2 -2
- package/skills/kaizen/README.md +5 -3
- package/skills/kaizen/SKILL.md +9 -6
- package/skills/kaizen/adapters.md +3 -3
- package/skills/kaizen/config.default.yml +4 -2
package/README.md
CHANGED
|
@@ -463,7 +463,6 @@ Everything a run knows lives in files next to the work, not in a chat window:
|
|
|
463
463
|
|
|
464
464
|
```
|
|
465
465
|
.kaizen/
|
|
466
|
-
config.yml # how much it asks you, what it fixes on its own
|
|
467
466
|
memory.md # what past reviews learned about this project
|
|
468
467
|
runs/2026-09-07-onboarding-email/
|
|
469
468
|
00-request.md # what you asked, word for word
|
|
@@ -480,7 +479,7 @@ in one can be finished in another. See [adapters.md](https://github.com/hfadhlul
|
|
|
480
479
|
|
|
481
480
|
## Settings
|
|
482
481
|
|
|
483
|
-
|
|
482
|
+
`~/.kaizen/config.yml` — one file, every project. The three worth knowing:
|
|
484
483
|
|
|
485
484
|
| Setting | Default | Meaning |
|
|
486
485
|
|---|---|---|
|
|
@@ -499,7 +498,7 @@ opens a full-screen browser over the whole file — arrows to move and change, e
|
|
|
499
498
|
change written as you make it, esc to close:
|
|
500
499
|
|
|
501
500
|
```
|
|
502
|
-
kaizen settings
|
|
501
|
+
kaizen settings ~/.kaizen/config.yml
|
|
503
502
|
|
|
504
503
|
› preset medium
|
|
505
504
|
mode approve
|
|
@@ -517,6 +516,8 @@ change written as you make it, esc to close:
|
|
|
517
516
|
↑↓ move · ←→ change · esc close saved preset = medium
|
|
518
517
|
```
|
|
519
518
|
|
|
519
|
+
One project can differ: put a `.kaizen/config.yml` in it with just the keys to
|
|
520
|
+
override, and it wins there while everything else follows the global file.
|
|
520
521
|
It edits each line in place, so the comment above every key survives. `/kaizen-config`
|
|
521
522
|
does the same thing from inside your agent if you would rather not leave it. Everything is also
|
|
522
523
|
in [`config.default.yml`](https://github.com/hfadhlullah/kaizen/blob/main/skills/kaizen/config.default.yml),
|
package/cli/dashboard.ts
CHANGED
|
@@ -208,7 +208,7 @@ export async function dashboard(
|
|
|
208
208
|
{ key: "backlog", label: "Backlog", hint: "what runs noticed and did not do" },
|
|
209
209
|
] : []),
|
|
210
210
|
{ key: "settings", label: "Settings", hint: "modes, approvals, who builds" },
|
|
211
|
-
...(state && !existsSync(join(state, "
|
|
211
|
+
...(state && !existsSync(join(state, "spec.md"))
|
|
212
212
|
? [{ key: "init", label: "Set up this project", hint: "write .kaizen/ here" }] : []),
|
|
213
213
|
...(!state ? [{ key: "init", label: "Set up this project", hint: "write .kaizen/ here" }] : []),
|
|
214
214
|
{ key: "upgrade", label: "Upgrade", hint: "pull, relink, clear the installer cache" },
|
package/cli/install.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
symlinkSync, mkdirSync, readdirSync, lstatSync, readlinkSync, unlinkSync,
|
|
4
4
|
existsSync, copyFileSync, readFileSync, appendFileSync, rmSync, writeFileSync,
|
|
5
|
+
statSync, cpSync,
|
|
5
6
|
} from "node:fs";
|
|
6
7
|
import { join, dirname, basename } from "node:path";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { applyPreset,
|
|
8
|
+
import { homedir, tmpdir } from "node:os";
|
|
9
|
+
import { applyPreset, PRESETS, type PresetName } from "./settings.ts";
|
|
9
10
|
|
|
10
11
|
const home = homedir();
|
|
11
12
|
const REPO_URL = "https://github.com/hfadhlullah/kaizen.git";
|
|
@@ -319,8 +320,18 @@ async function resolveRepo() {
|
|
|
319
320
|
}
|
|
320
321
|
return dest;
|
|
321
322
|
}
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
if (Bun.which("git")) {
|
|
324
|
+
step(`cloning into ${tilde(dest)}`);
|
|
325
|
+
await Bun.$`git clone --quiet ${REPO_URL} ${dest}`;
|
|
326
|
+
return dest;
|
|
327
|
+
}
|
|
328
|
+
// No git: the bunx download already holds everything a clone would, so copy it
|
|
329
|
+
// in place. Upgrades then come from re-running bunx rather than a pull.
|
|
330
|
+
step(`copying into ${tilde(dest)} ${c.dim("(no git)")}`);
|
|
331
|
+
cpSync(here, dest, {
|
|
332
|
+
recursive: true, dereference: true,
|
|
333
|
+
filter: (src) => basename(src) !== "node_modules",
|
|
334
|
+
});
|
|
324
335
|
return dest;
|
|
325
336
|
}
|
|
326
337
|
|
|
@@ -356,10 +367,30 @@ const links = targets.flatMap((a) => [
|
|
|
356
367
|
] : []),
|
|
357
368
|
]);
|
|
358
369
|
|
|
370
|
+
const isWindows = process.platform === "win32";
|
|
371
|
+
|
|
372
|
+
// Windows only hands out file symlinks to an admin or a Developer Mode box, so
|
|
373
|
+
// there the link is a directory junction (allowed for everyone) or, for a plain
|
|
374
|
+
// file, a copy that is refreshed whenever the source moves ahead.
|
|
375
|
+
function makeLink(src: string, dest: string) {
|
|
376
|
+
if (!isWindows) { symlinkSync(src, dest); return; }
|
|
377
|
+
if (statSync(src).isDirectory()) { symlinkSync(src, dest, "junction"); return; }
|
|
378
|
+
copyFileSync(src, dest);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function sameFile(a: string, b: string) {
|
|
382
|
+
try { return readFileSync(a).equals(readFileSync(b)); } catch { return false; }
|
|
383
|
+
}
|
|
384
|
+
|
|
359
385
|
function linkState(src: string, dest: string) {
|
|
360
386
|
try {
|
|
361
387
|
const st = lstatSync(dest);
|
|
362
|
-
if (!st.isSymbolicLink())
|
|
388
|
+
if (!st.isSymbolicLink()) {
|
|
389
|
+
// A copied link on Windows is a real file; it is ours as long as it still
|
|
390
|
+
// matches the source, and stale once the source changes.
|
|
391
|
+
if (isWindows && st.isFile()) return sameFile(src, dest) ? "linked" : "stale";
|
|
392
|
+
return "occupied";
|
|
393
|
+
}
|
|
363
394
|
return readlinkSync(dest) === src ? "linked" : "stale";
|
|
364
395
|
} catch {
|
|
365
396
|
return "missing";
|
|
@@ -385,8 +416,8 @@ for (const [src, sub] of links) {
|
|
|
385
416
|
continue;
|
|
386
417
|
}
|
|
387
418
|
mkdirSync(dir, { recursive: true });
|
|
388
|
-
if (state !== "missing")
|
|
389
|
-
|
|
419
|
+
if (state !== "missing") rmSync(dest, { recursive: true, force: true });
|
|
420
|
+
makeLink(src, dest);
|
|
390
421
|
wrote++;
|
|
391
422
|
if (verbose) console.log(c.dim(` link ${name}`));
|
|
392
423
|
}
|
|
@@ -436,7 +467,9 @@ const kaizenDir = join(process.cwd(), ".kaizen");
|
|
|
436
467
|
const globalKaizen = join(home, ".kaizen");
|
|
437
468
|
const globalConfig = join(globalKaizen, "config.yml");
|
|
438
469
|
|
|
439
|
-
|
|
470
|
+
// Always global: a project keeps no config of its own, so ~/.kaizen/config.yml is
|
|
471
|
+
// what every project follows.
|
|
472
|
+
{
|
|
440
473
|
mkdirSync(globalKaizen, { recursive: true });
|
|
441
474
|
const defPath = join(repo, "skills/kaizen/config.default.yml");
|
|
442
475
|
const def = readFileSync(defPath, "utf8");
|
|
@@ -476,25 +509,19 @@ if (inProject && !kaizenAbove()) {
|
|
|
476
509
|
{ label: "Yes", hint: "writes .kaizen/ and a CLAUDE.md line so it runs by default", value: true },
|
|
477
510
|
])
|
|
478
511
|
: false;
|
|
479
|
-
if (now) initProject(
|
|
512
|
+
if (now) initProject();
|
|
480
513
|
} else if (existsSync(kaizenDir)) {
|
|
481
514
|
step(`${basename(process.cwd())}/.kaizen already set up`);
|
|
482
515
|
}
|
|
483
516
|
|
|
484
|
-
function initProject(
|
|
517
|
+
function initProject() {
|
|
485
518
|
mkdirSync(kaizenDir, { recursive: true });
|
|
486
519
|
for (const f of readdirSync(join(repo, "skills/kaizen")).filter((f) => f.startsWith("spec"))) {
|
|
487
520
|
copyFileSync(join(repo, "skills/kaizen", f), join(kaizenDir, f));
|
|
488
521
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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);
|
|
522
|
+
// No config.yml here on purpose: a project without one follows
|
|
523
|
+
// ~/.kaizen/config.yml, so a setting changed once applies everywhere. Write one
|
|
524
|
+
// only to make this project deliberately different.
|
|
498
525
|
|
|
499
526
|
// Run state is local; the workflow itself is shared, so those files stay tracked.
|
|
500
527
|
// Nothing to ignore where there is no git, so a plain folder gets no .gitignore.
|
|
@@ -603,7 +630,7 @@ function clean(out: string) {
|
|
|
603
630
|
// without re-resolving, so an upgrade that only pulls the clone still leaves the
|
|
604
631
|
// next `bunx kaizen-agent` running whatever was cached the first time.
|
|
605
632
|
async function clearBunxCache() {
|
|
606
|
-
const tmp = process.env.TMPDIR ??
|
|
633
|
+
const tmp = process.env.TMPDIR ?? tmpdir();
|
|
607
634
|
let removed = 0;
|
|
608
635
|
try {
|
|
609
636
|
for (const name of readdirSync(tmp)) {
|
package/cli/settings.ts
CHANGED
|
@@ -256,11 +256,14 @@ export async function settings(repoRoot: string, standalone = true) {
|
|
|
256
256
|
if (standalone) console.log(` ${c.green("+")} settings saved to ${tilde(file)}\n`);
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
// The nearest .kaizen wins, exactly as the workflow resolves it.
|
|
259
|
+
// The nearest .kaizen/config.yml wins, exactly as the workflow resolves it. A
|
|
260
|
+
// project that has not written one of its own is edited globally, so one setting
|
|
261
|
+
// changed in ~/.kaizen/config.yml reaches every project.
|
|
260
262
|
function locate() {
|
|
261
263
|
let dir = process.cwd();
|
|
262
264
|
for (;;) {
|
|
263
|
-
|
|
265
|
+
const conf = join(dir, ".kaizen", "config.yml");
|
|
266
|
+
if (existsSync(conf)) return conf;
|
|
264
267
|
if (existsSync(join(dir, ".git"))) break;
|
|
265
268
|
const up = dirname(dir);
|
|
266
269
|
if (up === dir) break;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kaizen-agent",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Staged workflow for AI agents
|
|
3
|
+
"version": "1.3.1",
|
|
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": {
|
|
7
7
|
"kaizen-agent": "./cli/install.ts",
|
package/skills/kaizen/README.md
CHANGED
|
@@ -189,9 +189,11 @@ software or writing?"
|
|
|
189
189
|
|
|
190
190
|
## Configuration
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
Your settings live in `~/.kaizen/config.yml` and apply to every project — `/kaizen
|
|
193
|
+
config` edits that file. To make one project different, copy
|
|
194
|
+
[`config.default.yml`](config.default.yml) to `.kaizen/config.yml` there and override
|
|
195
|
+
only the keys you care about; anything absent falls back to the global file, then to
|
|
196
|
+
the defaults.
|
|
195
197
|
|
|
196
198
|
| Key | Default | Change it when |
|
|
197
199
|
|---|---|---|
|
package/skills/kaizen/SKILL.md
CHANGED
|
@@ -60,10 +60,12 @@ If the user typed `/kaizen` with no argument and a run is in progress, treat it
|
|
|
60
60
|
1. Locate the state directory: `.kaizen/` in the project folder's root, else
|
|
61
61
|
`~/.kaizen/` keyed by working directory. If neither exists and the user is starting
|
|
62
62
|
work, run `/kaizen init` first: it creates `.kaizen/` with a copy of the `spec*.md`
|
|
63
|
-
files
|
|
63
|
+
files and — where the folder is a git repository — the gitignore entry
|
|
64
64
|
(see [`adapters.md`](adapters.md)).
|
|
65
|
-
2. Read
|
|
66
|
-
|
|
65
|
+
2. Read the config in this order, each file filling in the keys the one before it
|
|
66
|
+
does not set: `.kaizen/config.yml`, then `~/.kaizen/config.yml`, then
|
|
67
|
+
`config.default.yml` in this skill. A project usually has no `config.yml` of its
|
|
68
|
+
own, so the global one is what settings changes reach. `runner` decides whether this run dispatches subagents at
|
|
67
69
|
all; read it before the first stage, not when you reach one.
|
|
68
70
|
3. Read `.kaizen/runs/<current>/state.json` if a run is active. Never assume the
|
|
69
71
|
stage from conversation memory — the run may have been advanced by another tool
|
|
@@ -73,11 +75,12 @@ If the user typed `/kaizen` with no argument and a run is in progress, treat it
|
|
|
73
75
|
|
|
74
76
|
## Settings
|
|
75
77
|
|
|
76
|
-
`/kaizen config` reads `.kaizen/config.yml`,
|
|
77
|
-
|
|
78
|
+
`/kaizen config` reads the same chain — `.kaizen/config.yml`, then `~/.kaizen/config.yml`,
|
|
79
|
+
then `config.default.yml` — and prints the current value of each setting below with a
|
|
78
80
|
one-line meaning. Then it offers the settings as choices — the tool's structured
|
|
79
81
|
question mechanism, one option per legal value, never free text — and writes back
|
|
80
|
-
whichever the user picks.
|
|
82
|
+
whichever the user picks. Write to whichever file in the chain already exists,
|
|
83
|
+
nearest first, so a change lands globally unless this project keeps its own config.
|
|
81
84
|
|
|
82
85
|
| Setting | Values | What it decides |
|
|
83
86
|
|---|---|---|
|
|
@@ -24,7 +24,7 @@ Codex reads `AGENTS.md` from the repository root. Append:
|
|
|
24
24
|
|
|
25
25
|
Non-trivial work in this repo follows the staged workflow in
|
|
26
26
|
`.kaizen/spec.md`: plan, human approval, implement, independent review, bounded
|
|
27
|
-
fix loop. Read that file before starting, and read `.kaizen/config.yml` for the
|
|
27
|
+
fix loop. Read that file before starting, and read the config chain (`.kaizen/config.yml`, `~/.kaizen/config.yml`, `config.default.yml`) for the
|
|
28
28
|
mode and approval settings.
|
|
29
29
|
|
|
30
30
|
Run state lives in `.kaizen/runs/<id>/`. Always read `state.json` first and
|
|
@@ -38,7 +38,7 @@ from its stage file, and write its artifact before moving on. When reviewing, ju
|
|
|
38
38
|
diff on its own terms rather than defending the reasoning you used while writing it.
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Copy `spec*.md`
|
|
41
|
+
Copy `spec*.md` into `.kaizen/` so the reference resolves
|
|
42
42
|
without this skill installed.
|
|
43
43
|
|
|
44
44
|
## Antigravity
|
|
@@ -64,7 +64,7 @@ completes. A stale `state.json` is the one way this breaks.
|
|
|
64
64
|
|
|
65
65
|
## Installing the adapter files
|
|
66
66
|
|
|
67
|
-
`/kaizen init` writes `.kaizen/` with the `spec*.md` files
|
|
67
|
+
`/kaizen init` writes `.kaizen/` with the `spec*.md` files into the
|
|
68
68
|
project folder, plus the gitignore entry when that folder is a git repository.
|
|
69
69
|
`/kaizen install` additionally offers to append the `AGENTS.md` block above.
|
|
70
70
|
`.kaizen/spec*.md` and `.kaizen/config.yml` should be committed even though run state
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
# Kaizen defaults.
|
|
2
|
-
#
|
|
1
|
+
# Kaizen defaults. Settings live in ~/.kaizen/config.yml and apply to every project.
|
|
2
|
+
# To make one project different, copy this to .kaizen/config.yml there and override
|
|
3
|
+
# only the keys you care about; anything absent falls back to the global file, then
|
|
4
|
+
# to this one.
|
|
3
5
|
|
|
4
6
|
# Quick preset: low | medium | ultra | custom
|
|
5
7
|
# Selecting low, medium, or ultra applies its bundled defaults for runner, executor,
|