kaizen-agent 1.42.2 → 1.43.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/agents/kaizen-reviewer.md +1 -1
- package/cli/backlog.test.ts +133 -0
- package/cli/board.test.ts +10 -4
- package/cli/browser.test.ts +1 -1
- package/cli/browser.ts +12 -7
- package/cli/html.test.ts +19 -0
- package/cli/notes.ts +2 -6
- package/cli/pty.ts +2 -1
- package/cli/settings.test.ts +38 -0
- package/cli/settings.ts +37 -30
- package/cli/state.test.ts +86 -3
- package/cli/state.ts +245 -45
- package/cli/web.ts +67 -16
- package/package.json +1 -1
- package/skills/kaizen/README.md +0 -4
- package/skills/kaizen/SKILL.md +4 -1
- package/skills/kaizen/config.default.yml +1 -50
- package/skills/kaizen/docs.md +3 -2
- package/skills/kaizen/settings.md +10 -3
- package/skills/kaizen/spec-build.md +3 -2
- package/skills/kaizen/spec-main.md +40 -5
- package/skills/kaizen/spec-review.md +2 -1
- package/skills/kaizen-help/SKILL.md +0 -4
- package/web/board/agent-term.ts +1 -1
- package/web/board/api.ts +2 -0
- package/web/board/approvals.ts +3 -3
- package/web/board/cards.ts +284 -108
- package/web/board/chat.ts +4 -4
- package/web/board/dom.ts +17 -5
- package/web/board/fixes.ts +6 -8
- package/web/board/git.ts +3 -3
- package/web/board/icebox.ts +52 -70
- package/web/board/keys.ts +9 -6
- package/web/board/live.ts +3 -2
- package/web/board/main.ts +1 -0
- package/web/board/panel.ts +22 -16
- package/web/board/projects.ts +11 -16
- package/web/board/settings.ts +60 -29
- package/web/board/store.ts +2 -0
- package/web/board/terms.ts +279 -0
- package/web/board/update.ts +7 -7
- package/web/board/views.ts +19 -5
- package/web/board.html +40 -2
- package/web/board.js +25 -24
- package/web/notebook.js +1 -1
- package/web/notes.html +2 -0
- package/web/term.js +1 -1
package/cli/web.ts
CHANGED
|
@@ -4,10 +4,10 @@ import { existsSync, readdirSync, statSync, watch, appendFileSync, mkdirSync, cp
|
|
|
4
4
|
import { join, dirname, resolve, sep, basename } from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
home, type Item, type Card, boardCards, knownProjects, remember, locate, label, tilde, searchRoots, findProjects,
|
|
7
|
-
readInbox, writeInbox, replaceIdea, moveIdea, abandonRun, launchRun, parseItem, agentChoices, projectOf, KNOWN_AGENTS, plainModel, short, setArchived, writeNotes,
|
|
7
|
+
readInbox, writeInbox, replaceIdea, moveIdea, abandonRun, runToIdea, launchRun, parseItem, agentChoices, projectOf, KNOWN_AGENTS, plainModel, short, setArchived, writeNotes,
|
|
8
8
|
appendNote, saveAttachment, pidOnPort, readText, cmdExe, sysExe, powershellExe, runGit, cardsGit, readCommit, commitPush, notice, notifier, LOGO,
|
|
9
|
-
reviewFindings, pickFindings, fixPrompt, approvalPrompt, canApproveAwaiting, nativePath, closeSessions, sweepSessions, issueUrl,
|
|
10
|
-
launchMode, readSessions, ownerOf, resumePrompt, runAgent, firstSession, webHosts,
|
|
9
|
+
reviewFindings, pickFindings, backlogCards, backlogItem, startedBacklog, closeBacklog, markBacklogDone, fixPrompt, approvalPrompt, canApproveAwaiting, nativePath, closeSessions, sweepSessions, issueUrl,
|
|
10
|
+
launchMode, readSessions, ownerOf, runIndex, resumePrompt, runAgent, firstSession, webHosts,
|
|
11
11
|
} from "./state.ts";
|
|
12
12
|
import { attachPty, writePty, resizePty, ptyState, ptyKeys, adoptLoginEnv } from "./pty.ts";
|
|
13
13
|
import { readSources, addSource, removeSource, moveSource, setCategory, setWriteback, readIcebox, promote, dismiss, restore, addItem, githubRepo, markDone, markReplied, gatherSource, freshImages, gatherAll, gatherDirs, due, socialUrl, socialOf } from "./sources.ts";
|
|
@@ -190,17 +190,27 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
190
190
|
const inCard = (dir: string) => ({ card: launchMode(projectOf(dir)) === "card", onChange: changed });
|
|
191
191
|
// An idea's session, until its run exists and owns it (ownerOf).
|
|
192
192
|
const ideaPty = new Map<string, string>();
|
|
193
|
+
// Backlog items this board launched whose run has not written its state.json yet:
|
|
194
|
+
// shown as starting, like a started idea. Forgotten on restart; the card then offers Run again.
|
|
195
|
+
const backlogLaunched = new Set<string>();
|
|
193
196
|
// The card's newest in-card session, live or recently ended. Sessions are only read
|
|
194
197
|
// while this board has any, so /state costs nothing more for terminal launches.
|
|
195
|
-
|
|
198
|
+
// Owners are worked out once per /state: per card it re-read every session and every
|
|
199
|
+
// run's state.json, about a second on a board of 400 runs.
|
|
200
|
+
const ptyOf = (cards: Card[]) => {
|
|
196
201
|
const live = new Set(ptyKeys());
|
|
197
|
-
if (!live.size) return undefined;
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return
|
|
202
|
+
if (!live.size) return () => undefined;
|
|
203
|
+
const runs = new Map<string, string>();
|
|
204
|
+
for (const d of new Set(cards.filter((c) => c.kind === "run").map((c) => c.state))) {
|
|
205
|
+
const ids = runIndex(d);
|
|
206
|
+
for (const s of readSessions(d)) if (live.has(s.key)) runs.set(`${d}\0${ownerOf(d, s, ids)}`, s.key); // oldest first: the newest wins
|
|
207
|
+
}
|
|
208
|
+
return (c: Card) => {
|
|
209
|
+
const key = c.kind === "run" ? runs.get(`${c.state}\0${c.id}`) : ideaPty.get(`${c.state}\0${c.text}`);
|
|
210
|
+
if (!key || !live.has(key)) return undefined;
|
|
211
|
+
const st = ptyState(key)!;
|
|
212
|
+
return { key, alive: st.alive, asking: st.asking };
|
|
213
|
+
};
|
|
204
214
|
};
|
|
205
215
|
// Dev: the page reloads itself when board.html or its bundle is saved. Server code needs
|
|
206
216
|
// `bun --watch cli/install.ts web`; the page reconnects and reloads on hello.
|
|
@@ -271,7 +281,7 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
271
281
|
// Done requests are reported back to their sources here, on the board's own clock: a
|
|
272
282
|
// page load only reads, so opening the Icebox never closes an issue.
|
|
273
283
|
const loop = setInterval(async () => {
|
|
274
|
-
for (const dir of states(true)) { try { markDone(dir); await writeBacks(dir); } catch { /* the next tick tries again */ } }
|
|
284
|
+
for (const dir of states(true)) { try { markDone(dir); markBacklogDone(dir); await writeBacks(dir); } catch { /* the next tick tries again */ } }
|
|
275
285
|
}, 60_000);
|
|
276
286
|
|
|
277
287
|
const BOOT = Date.now().toString(36);
|
|
@@ -443,7 +453,7 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
443
453
|
return !root || d === state || (fold(p).startsWith(inside)
|
|
444
454
|
&& (!isGroup(p) || filled(d) || fold(dirname(p)) !== fold(root)));
|
|
445
455
|
});
|
|
446
|
-
const cards = boardCards(dirs, Date.now());
|
|
456
|
+
const cards = boardCards(dirs, Date.now()), pty = ptyOf(cards);
|
|
447
457
|
// The git mark, for the cards whose panel offers commit and push: one status per project.
|
|
448
458
|
const marks = new Map<string, Record<string, string>>();
|
|
449
459
|
const markable = (c: Card) => c.kind === "run" && !c.archived && (c.status === "done" || c.awaiting === "approvals.review") && c.state !== join(home, ".kaizen");
|
|
@@ -456,7 +466,8 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
456
466
|
project, all, dir: state,
|
|
457
467
|
projects: picker.map((d) => ({ dir: d, label: label(d), logo: !!projectLogo(d) })),
|
|
458
468
|
fresh: unset.map((d) => ({ dir: join(d, ".kaizen"), label: tilde(d) })),
|
|
459
|
-
cards: cards.map((c) => ({ ...c, notice: notice(c), git: markable(c) ? marks.get(c.state)?.[c.id!] : undefined, open: openFindings(c), pty:
|
|
469
|
+
cards: cards.map((c) => ({ ...c, notice: notice(c), git: markable(c) ? marks.get(c.state)?.[c.id!] : undefined, open: openFindings(c), pty: pty(c) })),
|
|
470
|
+
backlog: backlogCards(dirs).map((b) => ({ ...b, starting: backlogLaunched.has(`${b.state}\0${b.source}#${b.n}`) || undefined })),
|
|
460
471
|
now: Date.now(),
|
|
461
472
|
});
|
|
462
473
|
}
|
|
@@ -667,7 +678,8 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
667
678
|
if (!/^(runs\/[\w.-]+\/)?attachments\/[\w.-]+$/.test(p)) return bad("bad path");
|
|
668
679
|
const f = join(dir, p);
|
|
669
680
|
if (!existsSync(f)) return bad("no such file", 404);
|
|
670
|
-
|
|
681
|
+
// no-cache: a revise retakes a screenshot under the same name, and the browser must ask again.
|
|
682
|
+
return new Response(Bun.file(f), { headers: { "cache-control": "private, no-cache" } });
|
|
671
683
|
}
|
|
672
684
|
|
|
673
685
|
// An icebox image, sent on to a link read fresh from its source: the one kept
|
|
@@ -781,6 +793,18 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
781
793
|
return json({ ok: true, project: label(dir) });
|
|
782
794
|
}
|
|
783
795
|
|
|
796
|
+
// "Not doing this" on a backlog card: the line is closed in place with the reason,
|
|
797
|
+
// never removed, so a notebook link to it keeps its number.
|
|
798
|
+
if (path === "/backlog") {
|
|
799
|
+
if (body.op !== "reject") return bad("unknown op");
|
|
800
|
+
const why = String(body.reason ?? "").trim();
|
|
801
|
+
if (!why) return bad("a rejection needs a reason");
|
|
802
|
+
const err = closeBacklog(dir, String(body.source ?? ""), Number(body.n), "rejected", why);
|
|
803
|
+
if (err) return bad(err, err === "that item is already closed" ? 409 : 404);
|
|
804
|
+
changed();
|
|
805
|
+
return json({ ok: true });
|
|
806
|
+
}
|
|
807
|
+
|
|
784
808
|
if (path === "/inbox") {
|
|
785
809
|
const text = String(body.text ?? "").trim();
|
|
786
810
|
// Absent means untouched; an empty string clears. replaceIdea keeps the
|
|
@@ -922,11 +946,30 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
922
946
|
if (path === "/run") {
|
|
923
947
|
const text = String(body.text ?? "").trim();
|
|
924
948
|
const kind = body.kind === "full" || body.kind === "lite" ? body.kind : "";
|
|
925
|
-
if (!text) return bad("empty idea");
|
|
949
|
+
if (!text && !body.backlog) return bad("empty idea");
|
|
926
950
|
const agent = typeof body.agent === "string" && body.agent ? body.agent : undefined;
|
|
927
951
|
if (agent && !KNOWN_AGENTS.some((a) => a.cmd === agent)) return bad("unknown agent");
|
|
928
952
|
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
929
953
|
if (model && !plainModel(model)) return bad("A model name is letters, digits and . : / @ _ - only");
|
|
954
|
+
// A backlog item runs as an idea would, its text read here from the file, never
|
|
955
|
+
// from the page. The request names its source, which is how the board finds the
|
|
956
|
+
// line again: to hide the card while the run lives, and to close it when done.
|
|
957
|
+
if (body.backlog) {
|
|
958
|
+
const source = String(body.backlog.source ?? ""), n = Number(body.backlog.n);
|
|
959
|
+
const line = backlogItem(dir, source, n);
|
|
960
|
+
if (!line) return bad("no such backlog item", 404);
|
|
961
|
+
if (line.status !== "open") return bad("that item is already closed", 409);
|
|
962
|
+
if ((startedBacklog(dir).get(`${source}#${n}`) ?? []).some((c) => c.stage !== "abandoned")) return bad("a run was already started from that item", 409);
|
|
963
|
+
const p = parseItem(line.text);
|
|
964
|
+
const it: Item = { ...p, text: kind ? `${kind} ${p.text}` : p.text };
|
|
965
|
+
it.notes = [source !== "orphan" ? `Parent: ${source}` : "", `Backlog: ${source}#${n}`, p.text !== line.text ? `Backlog item: ${line.text}` : ""].filter(Boolean).join("\n");
|
|
966
|
+
const r = launchRun(it, dir, undefined, { agent, model: model || undefined, yolo: body.yolo === true, ...inCard(dir) });
|
|
967
|
+
if (r.ok) backlogLaunched.add(`${dir}\0${source}#${n}`);
|
|
968
|
+
changed();
|
|
969
|
+
return json(r.ok
|
|
970
|
+
? { ok: true, agent: r.agent, project: tilde(r.projectDir), pty: r.pty }
|
|
971
|
+
: { ok: false, why: r.why, manual: r.manual, agent: r.agent }, r.ok ? 200 : 500);
|
|
972
|
+
}
|
|
930
973
|
const it: Item = parseItem(`${kind} ${text}`.trim());
|
|
931
974
|
// A `started` idea may be run again: the terminal the first launch opened
|
|
932
975
|
// can come up empty, and nothing else tells the board a run never began.
|
|
@@ -1097,6 +1140,14 @@ export async function web(repoDir: string, opts: Opts = {}) {
|
|
|
1097
1140
|
: { ok: false, why: r.why, manual: r.manual, agent: r.agent }, r.ok ? 200 : 500);
|
|
1098
1141
|
}
|
|
1099
1142
|
|
|
1143
|
+
if (path === "/to-idea") {
|
|
1144
|
+
const id = String(body.id ?? "");
|
|
1145
|
+
if (!/^[\w.-]+$/.test(id)) return bad("bad run id");
|
|
1146
|
+
const failed = runToIdea(dir, id);
|
|
1147
|
+
changed();
|
|
1148
|
+
return failed ? bad(failed, 409) : json({ ok: true });
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1100
1151
|
if (path === "/abort") {
|
|
1101
1152
|
const id = String(body.id ?? "");
|
|
1102
1153
|
const why = String(body.why ?? "").trim() || "abandoned from the web board";
|
package/package.json
CHANGED
package/skills/kaizen/README.md
CHANGED
|
@@ -207,11 +207,7 @@ the defaults.
|
|
|
207
207
|
| `auto_fix.max_iterations` | `2` | Fix/recheck rounds before whatever is left is escalated to you |
|
|
208
208
|
| `review.memory_max_lines` | `100` | The cap on `memory.md`, which every stage of every run reads. Raise only if the lessons are genuinely all load-bearing |
|
|
209
209
|
| `git.auto_commit` | `false` | `true` commits the files a run changed when it is done, by path. Never pushes |
|
|
210
|
-
| `track.default` | `auto` (inferred) | A repo that is always one kind of work, e.g. a docs repo |
|
|
211
210
|
| `backlog.enabled` | `true` | Set `false` to skip both backlog capture points; the run still executes, just without a `06-backlog.md` |
|
|
212
|
-
| `backlog.file` | `06-backlog.md` | The per-run file name. Rarely. |
|
|
213
|
-
| `backlog.statuses` | `[open, done, rejected]` | Rarely. Only `open` items are printed |
|
|
214
|
-
| `backlog.orphan_file` | `backlog.md` | Where open items go when their run is pruned |
|
|
215
211
|
| `state.keep_runs` | `20` | You want a longer or shorter run history |
|
|
216
212
|
|
|
217
213
|
Mode never overrides a refusal: destructive or irreversible actions still get confirmed
|
package/skills/kaizen/SKILL.md
CHANGED
|
@@ -276,7 +276,10 @@ After a subagent returns, update `state.json` yourself in the main thread. Subag
|
|
|
276
276
|
report; the main thread owns the state machine.
|
|
277
277
|
Before moving any run from `build` to `review`, under either runner, apply the
|
|
278
278
|
*Preview gate* in [`spec-main.md`](spec-main.md): no `## Preview` with a screenshot
|
|
279
|
-
(or a one-line reason there is none) means the build is not done.
|
|
279
|
+
(or a one-line reason there is none) means the build is not done. Before the final
|
|
280
|
+
report and before `stage: "done"`, under either runner and in every mode, apply the
|
|
281
|
+
*Docs gate* there too: the project's docs vault is brought up to date with what the
|
|
282
|
+
run changed.
|
|
280
283
|
|
|
281
284
|
**Name files, do not narrate them.** The prompt says which paths to read, which
|
|
282
285
|
decisions bind the stage, and what to report back. It does not summarize those files:
|
|
@@ -58,45 +58,7 @@ auto_fix:
|
|
|
58
58
|
# knob in the file. A third round rarely closes what two could not.
|
|
59
59
|
max_iterations: 2
|
|
60
60
|
|
|
61
|
-
track:
|
|
62
|
-
# The kind of work a run is doing, which decides what "deliverable",
|
|
63
|
-
# "verification", and "finding" mean for every stage. It is INFERRED from the
|
|
64
|
-
# request by the planner and stated back in the plan's Framing for the user to
|
|
65
|
-
# correct at the approval — the user is never asked to categorize their own request.
|
|
66
|
-
# Set this only to pin a default for a repo that is always one kind of work
|
|
67
|
-
# (e.g. a docs repo). auto (or empty) infers per run.
|
|
68
|
-
default: auto
|
|
69
|
-
|
|
70
61
|
review:
|
|
71
|
-
# Checks that run in every track.
|
|
72
|
-
checks:
|
|
73
|
-
- correctness
|
|
74
|
-
- plan_conformance
|
|
75
|
-
# Additional checks per track, chosen by what the run actually is. The planner may
|
|
76
|
-
# name a track not listed here; the reviewer then derives its checks from the
|
|
77
|
-
# track's "what would make it wrong".
|
|
78
|
-
# Each check below is one question the reviewer asks; the line beside it says which.
|
|
79
|
-
track_checks:
|
|
80
|
-
# security - no new way in, no secret or user data exposed
|
|
81
|
-
# reuse_simplification - nothing rebuilt that the codebase already had
|
|
82
|
-
# test_coverage - the changed behaviour has a check that fails when it breaks
|
|
83
|
-
software: [security, reuse_simplification, test_coverage]
|
|
84
|
-
# blast_radius and regression are universal — see spec-review.md — not listed here.
|
|
85
|
-
# completeness - every part the piece promises is actually present
|
|
86
|
-
# claim_accuracy - every checkable claim traced to a source
|
|
87
|
-
# reader_usability - its intended reader can act on it without asking
|
|
88
|
-
writing: [completeness, claim_accuracy, reader_usability]
|
|
89
|
-
# audience_fit - written for the people who will actually read it
|
|
90
|
-
# voice_consistency - reads as the same voice as the surrounding material
|
|
91
|
-
communication: [claim_accuracy, audience_fit, voice_consistency]
|
|
92
|
-
# prerequisites - everything needed before step one is stated
|
|
93
|
-
# rollback - a way back exists for every step that changes something
|
|
94
|
-
# executability - someone else can run it start to finish unaided
|
|
95
|
-
operations: [prerequisites, rollback, executability]
|
|
96
|
-
# evidence_support - the conclusion follows from evidence shown, not asserted
|
|
97
|
-
# selection_bias - the evidence was not picked to fit the answer
|
|
98
|
-
# alternatives - the readings that were rejected are named and why
|
|
99
|
-
research: [evidence_support, selection_bias, alternatives]
|
|
100
62
|
# Let the reviewer append durable lessons to .kaizen/memory.md.
|
|
101
63
|
write_memory: true
|
|
102
64
|
# Every stage of every future run reads memory.md, so cap it. Over this, the reviewer
|
|
@@ -108,20 +70,12 @@ backlog:
|
|
|
108
70
|
# not take, findings below the fix threshold, and debts deferred at an approval.
|
|
109
71
|
# Written by the main thread at the plan approval and again at the final approval.
|
|
110
72
|
enabled: true
|
|
111
|
-
# Per run, beside its plan and review — provenance without a central file to sync.
|
|
112
|
-
file: 06-backlog.md
|
|
113
|
-
# Items are open | done | rejected. Only open ones are printed by /kaizen backlog;
|
|
114
|
-
# closed ones stay in their run's file as record.
|
|
115
|
-
statuses: [open, done, rejected]
|
|
116
|
-
# Before state.keep_runs prunes a run, its still-open items move here, relative to
|
|
117
|
-
# state.dir. Done and rejected items are pruned with the run.
|
|
118
|
-
orphan_file: backlog.md
|
|
119
73
|
|
|
120
74
|
state:
|
|
121
75
|
dir: .kaizen
|
|
122
76
|
# Keep this many completed runs before pruning the oldest. `kaizen prune` lists
|
|
123
77
|
# what would go and `kaizen prune --yes` deletes it, after rescuing open backlog
|
|
124
|
-
# items into backlog.
|
|
78
|
+
# items into backlog.md beside memory.md, so it never destroys open work.
|
|
125
79
|
keep_runs: 20
|
|
126
80
|
|
|
127
81
|
git:
|
|
@@ -130,9 +84,6 @@ git:
|
|
|
130
84
|
# pushes. false: never commit, push, or open a PR unless the user explicitly asks.
|
|
131
85
|
# Ignored when the project is not a git repository.
|
|
132
86
|
auto_commit: false
|
|
133
|
-
# Create a branch before the implement stage when on the default branch.
|
|
134
|
-
# Ignored when the project is not a git repository.
|
|
135
|
-
branch_before_implement: true
|
|
136
87
|
# Build each run on its own branch (kaizen/<run-id>) in its own git worktree under
|
|
137
88
|
# .kaizen/wt/, so runs and people working at the same time never share a folder. Done
|
|
138
89
|
# lands the branch by pull request (gh) or a rebase and push. Ignored when the
|
package/skills/kaizen/docs.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Kaizen — docs
|
|
2
2
|
|
|
3
3
|
Read for `/kaizen docs` and `/kaizen docs lint`. Nothing else needs this file, except
|
|
4
|
-
the
|
|
5
|
-
during a run.
|
|
4
|
+
the paragraphs in `spec-build.md` and `spec-review.md` that keep the vault current
|
|
5
|
+
during a run, and the *Docs gate* in `spec-main.md`, which reads *Update* step 4 and
|
|
6
|
+
*Lint* before a run is done.
|
|
6
7
|
|
|
7
8
|
`/kaizen docs` writes a project's documentation into the notebook as a small vault an
|
|
8
9
|
agent reads before it plans or builds: an **Index** listing every note with one line on
|
|
@@ -23,17 +23,24 @@ nearest first, so a change lands globally unless this project keeps its own conf
|
|
|
23
23
|
| `auto_fix.max_iterations` | `1`–`5` | Fix and recheck rounds before what is left is escalated |
|
|
24
24
|
| `review.write_memory` | `true`, `false` | Let the reviewer append lessons to `memory.md` |
|
|
25
25
|
| `git.auto_commit` | `true`, `false` | Commit the work when a run finishes |
|
|
26
|
-
| `git.branch_before_implement` | `true`, `false` | Branch before building when on the default branch |
|
|
27
26
|
| `git.worktree_per_run` | `true`, `false` | Build each run on its own branch in its own worktree and land it by pull request |
|
|
28
27
|
| `ui.mouse` | `false`, `true` | Click to select, click again to act; while on, the terminal cannot select text |
|
|
29
28
|
| `subagents.model` | `inherit`, `opus`, `sonnet`, `haiku` | Model the stage subagents run on (Claude Code only) |
|
|
30
29
|
| `subagents.effort` | `inherit`, `low`, `medium`, `high` | Reasoning effort for the stage subagents (Claude Code only) |
|
|
31
30
|
| `sources.every` | `off`, `1h`, `6h`, `24h` | How often the web board checks request sources for new requests, into the icebox, while it runs |
|
|
32
|
-
| `sources.agent` | `ask`, `chatgpt`, `agent` | Who
|
|
31
|
+
| `sources.agent` | `ask`, `chatgpt`, `agent` | Who picks the posts that fit from social sources when Gather is pressed: the ChatGPT plan signed in from the Icebox, or your agent on `/kaizen gather` |
|
|
33
32
|
| `sources.model` | text | Model the ChatGPT plan gathers with; empty takes the first it lists |
|
|
34
33
|
| `sources.effort` | `low`, `medium`, `high` | Reasoning effort of that model while gathering |
|
|
35
34
|
| `agent.default` | `auto`, `claude`, `codex`, `agy`, `opencode`, `gemini` | Coding agent to launch from dashboard backlog |
|
|
36
35
|
|
|
36
|
+
Some settings only have an effect while another stands a certain way: the approvals
|
|
37
|
+
while `mode` is `approve`, `build.executor` and `subagents.*` while `runner` is `full`,
|
|
38
|
+
`auto_fix.min_severity` and `auto_fix.max_iterations` while `auto_fix.enabled` is
|
|
39
|
+
`true`, `sources.model` and `sources.effort` while `sources.agent` is `chatgpt`. When
|
|
40
|
+
printing one of these that has no effect now, say so beside its value. Still offer it:
|
|
41
|
+
a value set now applies once the other setting changes, and `/kaizen full` overrides
|
|
42
|
+
the runner for one run.
|
|
43
|
+
|
|
37
44
|
Rules for writing the file:
|
|
38
45
|
|
|
39
46
|
- **Change the value in place.** `config.yml` ships with a comment above every key
|
|
@@ -46,7 +53,7 @@ Rules for writing the file:
|
|
|
46
53
|
- After writing, print the setting and its new value, and nothing else. No summary of
|
|
47
54
|
the whole file.
|
|
48
55
|
|
|
49
|
-
Settings not in the table —
|
|
56
|
+
Settings not in the table — such as `state.dir` and the per-tool
|
|
50
57
|
`agent.<cmd>.model` / `agent.<cmd>.effort` the board launches a session with — are edited by
|
|
51
58
|
hand in `config.yml`, which is commented throughout. Say so rather than offering a
|
|
52
59
|
picker with thirty options.
|
|
@@ -86,8 +86,9 @@ the note, correct what the change made untrue or left out — never the `## Note
|
|
|
86
86
|
section, which is the user's — and set its `updated` to today. Refresh the note's line
|
|
87
87
|
in `Index.md` if its `description` changed, and append one line to `Log.md`:
|
|
88
88
|
`- YYYY-MM-DD run <id> — <notes touched>`. Name the notes touched in `03-impl.md`. A
|
|
89
|
-
change no note covers is not written up here:
|
|
90
|
-
|
|
89
|
+
change no note covers is not written up here: list it in `03-impl.md` as "no note
|
|
90
|
+
covers `<path>`" — the main thread's *Docs gate* in `spec-main.md` documents it before
|
|
91
|
+
the run is done. No Index, nothing to do.
|
|
91
92
|
|
|
92
93
|
**Read the plan's File manifest instead of searching.** The planner already located
|
|
93
94
|
every path this run touches. Search the material only for what the manifest does not
|
|
@@ -97,13 +97,48 @@ tab from staying empty; the reviewer flagging it later is a second net, not the
|
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
|
+
## Docs gate
|
|
101
|
+
|
|
102
|
+
Before a run can be `done` — in every runner and every mode, `auto` included — bring
|
|
103
|
+
the project's docs vault up to date with what the run changed. Apply it once the review
|
|
104
|
+
is settled and before the final approval report, so the user approves the notes with
|
|
105
|
+
the work; where the user's answer sends findings back to be fixed, apply it again over
|
|
106
|
+
those fixes before setting `stage: "done"`. In a run with a `worktree`, edit the notes
|
|
107
|
+
in the worktree, where the vault is committed with the code. Where
|
|
108
|
+
`notes/Project/Index.md` does not exist in the state directory, there is no vault and
|
|
109
|
+
nothing to do. The builder already updates the notes it can see during the build; this
|
|
110
|
+
is the step that catches what it could not: fix iterations after it, and changes no
|
|
111
|
+
note covers.
|
|
112
|
+
|
|
113
|
+
1. **What changed:** every path named in `03-impl.md` and in each
|
|
114
|
+
`05-iterations/*-fix.md`, leaving out `.kaizen/` itself.
|
|
115
|
+
2. **Covered:** for each note whose `sources` include one of those paths (a folder in
|
|
116
|
+
`sources` covers everything under it), check the note against the changed files.
|
|
117
|
+
Where the change made it untrue or left something out, correct it as *Keep the
|
|
118
|
+
project's docs vault current* in [`spec-build.md`](spec-build.md) says — never the
|
|
119
|
+
`## Notes` section — set `updated` to today, and append the `Log.md` line, unless the
|
|
120
|
+
builder already wrote this run's line, in which case add the notes to it.
|
|
121
|
+
3. **Not covered:** a changed path no note's `sources` reach. Add it to the `sources`
|
|
122
|
+
of the note whose area it belongs to and describe it there; where it belongs to no
|
|
123
|
+
area, write a note for it as step 4 of *Update* in [`docs.md`](docs.md) says and
|
|
124
|
+
list it in `Index.md`. A path that is not part of the project's material — a lock
|
|
125
|
+
file, a generated bundle — needs no note.
|
|
126
|
+
4. **Lint** the notes touched, as *Lint* in [`docs.md`](docs.md) lists, and fix what
|
|
127
|
+
this run broke.
|
|
128
|
+
|
|
129
|
+
Name the notes touched in the final report; the commit at done includes them. A run
|
|
130
|
+
whose changes leave every note true touches nothing and says so in one line.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
100
134
|
## Final approval
|
|
101
135
|
|
|
102
136
|
Controlled by `approvals.review`, skipped in `auto`.
|
|
103
137
|
|
|
104
138
|
Report: what was built (for a code run, show its Preview proof), verification results, findings fixed, findings still open,
|
|
105
139
|
every criterion the reviewer answered `CANNOT VERIFY` with the check it says a human
|
|
106
|
-
must make,
|
|
140
|
+
must make, anything the builder noted as out of scope, and the notes the *Docs gate*
|
|
141
|
+
touched. Where the run used `runner: lite`, say
|
|
107
142
|
so in that report — the reviewer saw the work, and nobody should read a same-session
|
|
108
143
|
review as an independent one because the output looked the same.
|
|
109
144
|
Later build requests in the same session follow *Follow-ups are runs too* in [`spec.md`](spec.md): a revise of this run until it is `done`, then a new linked run.
|
|
@@ -133,10 +168,10 @@ Set `stage: "done"` in `state.json` once the user accepts.
|
|
|
133
168
|
|
|
134
169
|
**Commit at done, when `git.auto_commit` is `true`.** Setting `stage: "done"` — the
|
|
135
170
|
user approving at review, or an `auto` run finishing — is then the ask to commit, under
|
|
136
|
-
either runner. Commit only the files this run changed (the paths `03-impl.md` names
|
|
137
|
-
that `git status` shows changed), by path:
|
|
138
|
-
`git commit -m <message> -- <paths>`, never the whole
|
|
139
|
-
the same checkout. The message is `03-impl.md`'s `## Commit`. In a run with a
|
|
171
|
+
either runner. Commit only the files this run changed (the paths `03-impl.md` names
|
|
172
|
+
and the notes the *Docs gate* touched, that `git status` shows changed), by path:
|
|
173
|
+
`git add -A -- <paths>` then `git commit -m <message> -- <paths>`, never the whole
|
|
174
|
+
tree, since other sessions edit the same checkout. The message is `03-impl.md`'s `## Commit`. In a run with a
|
|
140
175
|
`worktree`, commit there. Then write `runs/<id>/07-commit.md` as `commit: <short sha>`
|
|
141
176
|
and `pushed: no`, so the board shows it committed and offers the push. Never push. A
|
|
142
177
|
failed commit is reported as printed; the run is still `done`. With the setting
|
|
@@ -125,7 +125,8 @@ and no criterion is `CANNOT VERIFY`; otherwise
|
|
|
125
125
|
`; <n> cannot verify` when any criterion was — even when every count is 0, so an open
|
|
126
126
|
human check never reads as a clean pass.
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Where `review.write_memory` is true (the default), the reviewer also appends any
|
|
129
|
+
durable lesson about this material to `memory.md` —
|
|
129
130
|
a recurring bug pattern, a non-obvious constraint, a convention worth keeping. Not
|
|
130
131
|
run-specific detail; only what the next run would want to know.
|
|
131
132
|
|
|
@@ -88,15 +88,11 @@ Each iteration is written to `runs/<id>/05-iterations/`.
|
|
|
88
88
|
| `auto_fix.enabled` | `true` | Auto-fix findings vs. escalate everything |
|
|
89
89
|
| `auto_fix.min_severity` | `high` | Threshold for auto-fix (`critical\|high\|medium\|low`) |
|
|
90
90
|
| `auto_fix.max_iterations` | `2` | Fix/recheck rounds before escalating remaining findings |
|
|
91
|
-
| `track.default` | `auto` (inferred per run) | Pin a track for a repo that is always one kind of work |
|
|
92
91
|
| `review.write_memory` | `true` | Let the reviewer append lessons to `.kaizen/memory.md` |
|
|
93
92
|
| `backlog.enabled` | `true` | Capture what a run deliberately did not do |
|
|
94
|
-
| `backlog.file` | `06-backlog.md` | Per-run backlog file |
|
|
95
|
-
| `backlog.orphan_file` | `backlog.md` | Where a pruned run's open items are rescued to |
|
|
96
93
|
| `state.dir` | `.kaizen` | State directory |
|
|
97
94
|
| `state.keep_runs` | `20` | Completed runs kept before pruning oldest (rescues open backlog first) |
|
|
98
95
|
| `git.auto_commit` | `false` | `true`: commit the run's changed files when it is done; never pushes |
|
|
99
|
-
| `git.branch_before_implement` | `true` | Branch before implement stage when on the default branch |
|
|
100
96
|
| `subagents.model` | `inherit` | Model the stage subagents run on (opus/sonnet/haiku, Claude Code only) |
|
|
101
97
|
| `subagents.effort` | `inherit` | Reasoning effort for the stage subagents (low/medium/high, Claude Code only) |
|
|
102
98
|
| `sources.every` | `24h` | How often the web board checks request sources for new ideas (`off\|1h\|6h\|24h`) |
|
package/web/board/agent-term.ts
CHANGED
|
@@ -11,7 +11,7 @@ let termKey = null,
|
|
|
11
11
|
termOff = null,
|
|
12
12
|
termLib = null;
|
|
13
13
|
export const where = (r) => (r.pty ? "running in its card" : "opened in your terminal");
|
|
14
|
-
const termLoad = () =>
|
|
14
|
+
export const termLoad = () =>
|
|
15
15
|
(termLib ||= new Promise((ok, no) => {
|
|
16
16
|
const l = document.createElement("link");
|
|
17
17
|
l.rel = "stylesheet";
|
package/web/board/api.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { key, render } from "./cards.ts";
|
|
|
3
3
|
import { $ } from "./dom.ts";
|
|
4
4
|
import { close, ideaBody, open, openRun } from "./panel.ts";
|
|
5
5
|
import { notifyUser, toast } from "./toast.ts";
|
|
6
|
+
import { openTerms, termsShowing } from "./terms.ts";
|
|
6
7
|
|
|
7
8
|
// The server writes notifier() from state.ts over a marker in board.html: one rule for both UIs.
|
|
8
9
|
declare const due: (key: string, say: string | null, done: boolean, now: number) => boolean;
|
|
@@ -36,6 +37,7 @@ export async function load(quiet) {
|
|
|
36
37
|
}
|
|
37
38
|
// A redraw under a form being typed in would reset its fields and its focus.
|
|
38
39
|
if (!(quiet && store.mode && $("board").querySelector(".inl input"))) render();
|
|
40
|
+
if (termsShowing()) openTerms();
|
|
39
41
|
// Run on an idea closed the panel: open the card on its agent once the board knows the session.
|
|
40
42
|
if (store.wantTerm && !store.openKey && !store.mode) {
|
|
41
43
|
const n = store.S.cards.find((x) => key(x) === store.wantTerm && x.pty);
|
package/web/board/approvals.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { store } from "./store.ts";
|
|
|
2
2
|
import { where } from "./agent-term.ts";
|
|
3
3
|
import { post } from "./api.ts";
|
|
4
4
|
import { key, via } from "./cards.ts";
|
|
5
|
-
import { $,
|
|
5
|
+
import { $, html } from "./dom.ts";
|
|
6
6
|
import { openRun } from "./panel.ts";
|
|
7
7
|
import { askNotify, toast } from "./toast.ts";
|
|
8
8
|
|
|
@@ -57,7 +57,7 @@ export async function decide(c, b, why, answers) {
|
|
|
57
57
|
export function confirmRevise(c) {
|
|
58
58
|
const foot = $("pfoot"),
|
|
59
59
|
plan = c.awaiting === "approvals.plan";
|
|
60
|
-
foot.innerHTML = `<div class="inl" style="flex:1"><div class="q">${plan ? "What should the plan do differently?" : "What should change in the work?"}</div><div class="q dim">Opens your agent on this run with your comment.${plan ? " It revises the plan and comes back here for approval." : " It changes the work, rechecks it and comes back here."}</div><textarea aria-label="Revise comment"></textarea><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Send back</button></div></div>`;
|
|
60
|
+
foot.innerHTML = html`<div class="inl" style="flex:1"><div class="q">${plan ? "What should the plan do differently?" : "What should change in the work?"}</div><div class="q dim">Opens your agent on this run with your comment.${plan ? " It revises the plan and comes back here for approval." : " It changes the work, rechecks it and comes back here."}</div><textarea aria-label="Revise comment"></textarea><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Send back</button></div></div>`;
|
|
61
61
|
const inp = foot.querySelector("textarea");
|
|
62
62
|
inp.focus();
|
|
63
63
|
foot.querySelector("[data-a=c]").onclick = () => {
|
|
@@ -78,7 +78,7 @@ export function confirmRevise(c) {
|
|
|
78
78
|
// a note for what the options miss. Each answer reaches the agent as one line.
|
|
79
79
|
export function confirmAnswers(c, qs) {
|
|
80
80
|
const foot = $("pfoot");
|
|
81
|
-
foot.innerHTML = `<div class="inl qa" style="flex:1"><div class="q">The plan asks ${qs.length === 1 ? "one question" : qs.length + " questions"}.</div><div class="q dim">Your answers go to your agent with the approval. It records them and builds the plan.</div>${qs.map((x, i) => `<fieldset><legend>${
|
|
81
|
+
foot.innerHTML = html`<div class="inl qa" style="flex:1"><div class="q">The plan asks ${qs.length === 1 ? "one question" : qs.length + " questions"}.</div><div class="q dim">Your answers go to your agent with the approval. It records them and builds the plan.</div>${qs.map((x, i) => html`<fieldset><legend>${x.q}</legend>${x.options.map((o, j) => html`<label><input type="${x.multi ? "checkbox" : "radio"}" name="q${i}" value="${j}"${o.rec || (!x.multi && !j && !x.options.some((p) => p.rec)) ? " checked" : ""}> ${o.text}</label>`)}<input type="text" data-n="${i}" placeholder="Note (optional)" aria-label="Note: ${x.q}"></fieldset>`)}<div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Approve with these answers</button></div></div>`;
|
|
82
82
|
foot.querySelector("[data-a=c]").onclick = () => {
|
|
83
83
|
foot.innerHTML = "";
|
|
84
84
|
openRun(c, true);
|