kaizen-agent 1.43.0 → 1.43.2

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.
@@ -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.orphan_file, so it never destroys open work.
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
@@ -163,7 +114,7 @@ agent:
163
114
  # auto picks Claude Code if installed, otherwise the first agent found on PATH.
164
115
  default: auto
165
116
  # on: a board decision ends the run's waiting session before continuing its
166
- # conversation, and abandoned or done runs (after 2 minutes) have theirs ended.
117
+ # conversation, and abandoned or done runs (after 1 minute) have theirs ended.
167
118
  # Only sessions the board or dashboard launched are touched. off: nothing is
168
119
  # ended, and a resumed conversation opens beside the window still waiting.
169
120
  close: on
@@ -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 two paragraphs in `spec-build.md` and `spec-review.md` that keep the vault current
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
@@ -107,7 +108,9 @@ updated: YYYY-MM-DD
107
108
  2. Split it into 5–12 areas a newcomer would name — by what the parts do, not by
108
109
  folder alone. Fewer for a small project.
109
110
  3. Write one note per area in the shape above, then `Index.md`, then `Log.md` with the
110
- first line.
111
+ first line. Every source file lands in some note's `sources` — the area's helpers,
112
+ small screens and scripts too, not only its main files — named directly or through a
113
+ folder that holds it (see *Coverage* below).
111
114
  4. Write the pointer (below).
112
115
  5. Lint, and report.
113
116
 
@@ -123,8 +126,9 @@ updated: YYYY-MM-DD
123
126
  rewrite the sections they affect — never `## Notes` — and set `updated`.
124
127
  3. Every `sources` path is gone: move the note to `Project/Archive/` and drop it from
125
128
  the Index. Never delete a note.
126
- 4. A part of the repository no note covers (a new top-level folder, a new entry point):
127
- write a note for it.
129
+ 4. A source file no note covers (see *Coverage*): add it to the `sources` of the note
130
+ whose area it belongs to, and mention it there; where it fits no area (a new top-level
131
+ folder, a new entry point), write a note for it.
128
132
  5. Refresh the Index lines whose description changed, append one Log line naming every
129
133
  note touched, refresh the pointer, lint, and report.
130
134
 
@@ -137,10 +141,20 @@ Checks, reported and not silently fixed:
137
141
  - every note has `description`, `tags`, `sources` and `updated` (the Index and Log need
138
142
  no `sources`), and is listed in the Index;
139
143
  - every `sources` path exists;
144
+ - **uncovered**: source files no note's `sources` names or holds, listed one per line;
140
145
  - **stale**: notes whose `sources` changed after their `updated` day, found as in
141
146
  *Update* step 1;
142
147
  - `Index.md` is under 60 lines.
143
148
 
149
+ ## Coverage
150
+
151
+ A **source file** is one the repository tracks (`git ls-files`; outside git, every file
152
+ not ignored), leaving out lockfiles, generated or built output, vendored code, binaries,
153
+ and bulk data folders. A note covers a file when its `sources` names the file or a folder
154
+ that holds it, so a data folder such as `public/quran` is covered by naming the folder.
155
+ Lint's *uncovered* list is what makes this checkable: a file no note covers is also a file
156
+ whose changes *stale* never sees, so the vault falls behind without saying so.
157
+
144
158
  ## The pointer
145
159
 
146
160
  What makes an agent open the Index first. One block, identical everywhere:
@@ -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 reads social sources when Gather is pressed: the ChatGPT plan signed in from the Icebox, or your agent on `/kaizen gather` |
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 — the review checks, backlog and state paths, and the per-tool
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: say "suggest `/kaizen docs`" in
90
- `03-impl.md` and leave the vault alone. No Index, nothing to do.
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, and anything the builder noted as out of scope. Where the run used `runner: lite`, say
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: `git add -A -- <paths>` then
138
- `git commit -m <message> -- <paths>`, never the whole tree, since other sessions edit
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
@@ -156,3 +191,31 @@ When a child run reaches `done`, the board appends `Child <child id> done: <titl
156
191
  the parent's `notes.md` by itself. The parent documents it from there: if it is still in
157
192
  flight its next stage reads `notes.md`; if it is already `done`, the entry is the record.
158
193
  Never reopen a finished parent.
194
+
195
+ **A roadmap's increments are filed at its approval.** When the approved plan has a
196
+ `## Roadmap` (see *A whole application is a roadmap* in `spec-plan.md`), the same write
197
+ that records the approval files increments 2 to N, in order, from the project folder:
198
+ `kaizen card add "<title> (roadmap #N · after #a, #b)" --parent <run id>`, with
199
+ `any time` in place of `after …` where it has no prerequisite and its `blocked: <reason>`
200
+ kept in the text where it has one. Increment 1 is this run's own build. Nothing starts
201
+ by itself: the user starts an increment from its card when they choose, and the parent's
202
+ card lists its children with their status. An increment cut at the approval is not
203
+ filed; it goes to the backlog like anything else cut there.
204
+
205
+ ## Discovery rounds
206
+
207
+ A roadmap plan's Discovery and any run's REA offer reach the user through the main
208
+ thread, under every runner: each round's questions are asked with the tool's structured
209
+ question mechanism (AskUserQuestion in Claude Code), all of a round's questions in one
210
+ call, each with the planner's recommended option first. Under `runner: full`, the
211
+ planner stops after writing a round; dispatch it again with the answers, recorded in
212
+ `02-approval.md` as that round's decisions, until it writes a plan with no round left
213
+ to ask, then present that plan at the plan approval as usual. Answers are not an
214
+ approval: the run stays at `stage: "plan"` with `awaiting: null` throughout, and
215
+ `awaiting: "approvals.plan"` is set only once `01-plan.md` has a `## Roadmap` — a round
216
+ written to that file is not a plan to approve, and a tool resuming the run re-asks its
217
+ questions. Where the user says yes to REA before the plan exists, run the study here,
218
+ write what it found to the run's `notes.md`, and dispatch the planner with it. Where
219
+ REA has to be installed first, the install in *Offer REA* (`spec-plan.md`) is the main
220
+ thread's too: doctor, the dry-run shown to the user, setup only on their approval,
221
+ then the restart, after which the run resumes at `stage: "plan"` from `state.json`.
@@ -27,6 +27,39 @@ different work behind each, nothing available to break the tie — and even then
27
27
  its recommendation with the question. Questions the existing material can answer are
28
28
  never asked; that is the planner's own work.
29
29
 
30
+ **Offer REA for what exists but is not here.** In every run and every track: when the
31
+ request or what came with it names something that already exists but whose source is
32
+ not at hand — an app or site to copy, replace or integrate with, an APK or IPA, a
33
+ binary, a package, a file format, a device or its firmware, a protocol or a capture —
34
+ and REA's tools are connected in the session, the plan asks once:
35
+ `Q: Study <the thing> with REA first?`, with `yes` (recommended where how it works
36
+ inside decides the design) and `no`. Under `runner: lite` or an inline planner, a yes
37
+ given before the plan is written lets the planner run REA itself and plan from what it
38
+ found. Under `full`, where the planner has no REA tools, the main thread runs the study
39
+ on a yes, writes what it found to the run's `notes.md`, and dispatches the planner
40
+ again; a yes given at the plan approval instead makes the study the build's first
41
+ step. What REA
42
+ returns is cited with its limitations, as *the track* in `spec.md` already requires.
43
+ REA not connected: ask instead
44
+ `Q: REA isn't installed — set it up first?`, with `help me install it` (recommended
45
+ where how it works inside decides the design), `I'll install it myself` and `skip
46
+ REA`. On *help me install it* the main thread follows REA's own setup: it runs
47
+ `npx -y rea-agents@<version> doctor --client <client> --json` — the version pinned,
48
+ as REA's own guide pins it, taken from `npm view rea-agents version` and shown to the
49
+ user; the client from the run's `agent` (`claude` → `claude_code`, `codex` → `codex`,
50
+ `agy` → `antigravity`, `opencode` → `opencode`, `gemini` → `gemini_cli`, `cursor` →
51
+ `cursor`) — then `setup --client <client> --dry-run --json` with the same version, shows the user the exact files it
52
+ would change, and runs `setup --client <client> --yes` only after they approve it —
53
+ setup writes the agent's own configuration, so it is never run unseen, in any mode.
54
+ On *I'll install it myself* it gives that same command and waits. Either way the new
55
+ tools appear only after the agent restarts: say so, and the run resumes from its
56
+ `state.json`, the yes to REA recorded beforehand in `02-approval.md` so the resumed
57
+ session studies the thing rather than asking again. REA's prerequisites (Node, and
58
+ Java, Ghidra or JADX for some targets) are named where doctor reports them missing,
59
+ never installed by the run. On *skip REA*, or when setup fails, the plan says so
60
+ once and claims about that thing's internals are marked unverified. Nothing of the
61
+ kind named: REA is not mentioned.
62
+
30
63
  **Building from nothing is the exception: settle the foundation first.** When the
31
64
  track is a new product or app, there is no material to answer the questions that
32
65
  decide everything after them, and "wrong" for this track is a wrong foundation — the
@@ -39,6 +72,67 @@ materially different builds — put to the user as an *Open question* with optio
39
72
  all in one round, before anything is built. Three quick questions and a default for
40
73
  the rest is how a run ends up writing the second app instead of the first.
41
74
 
75
+ **A whole application is a roadmap, not a run.** When the track is a new product or
76
+ app — web, mobile, PWA, desktop, anything — and it covers more than one independent
77
+ module, one run cannot hold it: the plan, the diff and the review all outgrow what a
78
+ reader can check. A single screen or a single feature stays one run. The Framing says
79
+ in one line that the plan is a roadmap, so the user can refuse it. A roadmap plan adds
80
+ three sections right after the Goal, each written for a person, and the
81
+ foundation round above becomes up to three rounds:
82
+
83
+ - **`## Discovery`** — the planner goes deep before it designs. It reads everything the
84
+ user brought first (decks, forms, sheets, screenshots, an existing repo, REA's
85
+ findings), then asks only what is still open, in up to three rounds of `Q:` pick
86
+ lists, each question with a recommended option: (1) purpose, who uses it and in what
87
+ roles, what it replaces; (2) the flows of each module and their domain rules;
88
+ (3) the foundation choices, among them how it ships, as a multi-select — full CI/CD
89
+ (tests and deploy on every merge), Docker (an image and a compose file that run
90
+ anywhere), a managed platform (Vercel, Fly, Railway or similar), mobile app ready
91
+ (store builds with signing, for example Capacitor or Expo/EAS), or a simple manual
92
+ deploy — and, where the app has a screen and no design system exists, its look and
93
+ feel, asked as item 7 *Design direction* below says: the feel (for example rich and
94
+ animated, calm and simple, enterprise and dense, gen-z and playful) and how much
95
+ motion. A round with nothing left to ask is skipped. Under `runner: full` the
96
+ planner writes `01-plan.md` with the Framing, `## Discovery` so far and the round's
97
+ questions as *Open questions*, and no `## Roadmap`, then stops; that file is a
98
+ round, not a plan. The main thread asks them and dispatches it again with the
99
+ answers. Under
100
+ `lite` or inline, the session asks them directly. The section records the roles, the
101
+ main flows, the things the app keeps track of and how they relate, a glossary of
102
+ the domain's words, and every answer given.
103
+ - **`## Foundation`** — a decision with its reason, derived from Discovery, for each
104
+ of: platform; frontend (framework, UI kit, state, the design direction chosen);
105
+ backend (runtime, framework, API style); the contract between them (REST, RPC or
106
+ realtime, and where its types live); hosting and deploy, as chosen in discovery;
107
+ database (engine, where it runs, the main tables or collections and their
108
+ relations, how migrations are done); auth and access; repo layout and conventions.
109
+ Every increment builds on it.
110
+ - **`## Roadmap`** — the increments, numbered, one line each:
111
+ `N. <title> — <what it delivers> — after: #a, #b` (or `any time`), with
112
+ `— blocked: <reason>` when something outside the work holds it. Increment 1 is always
113
+ the skeleton: the scaffold, the stack wired, the database schema and migration setup,
114
+ an auth stub, and a deploy of the empty app the way the Foundation ships (in its
115
+ Docker image, through its pipeline, as a store test build). Every feature increment
116
+ after it is one feature end to end — its screens, its API, its tables and migration,
117
+ and its tests — so each can be tried on its own; splitting by layer ("all the APIs", then "all the screens")
118
+ is only for an app with no UI or no backend. Each increment is sized to one ordinary
119
+ run. Where real people will use the app, the last increment is going live: what
120
+ the deploy choice still needs (the full pipeline, the production image, store
121
+ listings and release builds), backups, monitoring and error tracking, and the
122
+ production deploy. A last line says which must go in order and which can start any
123
+ time.
124
+
125
+ The rest of a roadmap plan — Work list, Steps, File manifest, gate — covers increment 1
126
+ only; it is this run's build. The others become child cards at the approval (see
127
+ *Child cards* in `spec-main.md`).
128
+
129
+ **An increment run builds inside its parent's decisions.** A request with a `Parent:`
130
+ line whose parent run's `01-plan.md` has a `## Roadmap` is an increment. Its planner
131
+ reads that plan's Discovery, Foundation and its own Roadmap line before anything else.
132
+ They bind it: a deviation from the Foundation is an *Open question* to the user, and a
133
+ change to the roadmap is an *Out of scope suggestion*, never a silent edit of the
134
+ parent's plan.
135
+
42
136
  The goals stay the user's. The planner may propose a better solution to what was
43
137
  asked, or argue that the request as stated will not achieve what the user wants, but
44
138
  it may not widen the goal on its own. Anything beyond the request goes in *Out of
@@ -132,7 +226,7 @@ questions that are open. An alternative rejected gets a line saying what and why
132
226
  a case against it.
133
227
 
134
228
  **Write the parts a person reads for a person.** Framing, the Work list, Goal, Scope,
135
- Risks and Out of scope suggestions are read by someone deciding whether to approve,
229
+ Discovery, Foundation, Roadmap, Risks and Out of scope suggestions are read by someone deciding whether to approve,
136
230
  often on a narrow panel. Write them in everyday words: short sentences, one idea per
137
231
  bullet, things named by what the user sees ("the chat bubble", "the Plan tab") rather
138
232
  than by function, file, CSS value or gate id. Code names, `path:line` and gate ids
@@ -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
- The reviewer also appends any durable lesson about this material to `memory.md` —
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
 
@@ -31,7 +31,7 @@ anything.
31
31
  | `/kaizen backlog` | Print open backlog items across all runs, grouped by source run, rescued items last under `Orphaned` |
32
32
  | `/kaizen gather` | Read request sources kaizen cannot read by itself; their requests land in the icebox, nothing starts |
33
33
  | `/kaizen discover` | Group what is in the icebox and promote the ideas you pick; nothing starts |
34
- | `/kaizen docs [lint]` | Write or refresh the project's docs vault in the notebook, which agents read first; `lint` only reports what is stale or broken |
34
+ | `/kaizen docs [lint]` | Write or refresh the project's docs vault in the notebook, which agents read first; `lint` only reports what is stale, broken or uncovered |
35
35
  | `/kaizen approve` | Approve whatever the current run is waiting on |
36
36
  | `/kaizen reject <reason>` | Reject it; the reason decides revise (back to planner) vs kill (abandon) |
37
37
  | `/kaizen abort` | Mark the current run abandoned |
@@ -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`) |
@@ -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);
@@ -237,6 +237,7 @@ function card(c) {
237
237
  } else {
238
238
  items.push(["open", actionOf(c), ico("open")]);
239
239
  items.push(["archive", "Archive", ico("archive")]);
240
+ if (c.column === 1 && c.status !== "abandoned") items.push(["toidea", "Back to idea", ico("restore")]);
240
241
  if (c.status === "waiting" || c.status === "running" || c.status === "stalled") {
241
242
  items.push(null, ["reject", "Abandon", ico("x"), "danger"]);
242
243
  }
@@ -267,6 +268,12 @@ function card(c) {
267
268
  toggleArchive(c, a === "archive");
268
269
  return;
269
270
  }
271
+ if (a === "toidea") {
272
+ const r = await post("/to-idea", { dir: c.state, id: c.id });
273
+ if (r.ok) toast("Back in Ideas");
274
+ load();
275
+ return;
276
+ }
270
277
  store.mode = a;
271
278
  store.modeKey = k;
272
279
  render();
@@ -302,7 +309,7 @@ function inlineForm(c) {
302
309
  li.className = "inl";
303
310
  const dir = c.state;
304
311
  if (store.mode === "edit") {
305
- li.innerHTML = html`<input value="${c.text}"><textarea placeholder="Notes: links, paths, background">${c.notes || ""}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Save</button></div>`;
312
+ li.innerHTML = html`<input value="${c.text}"><textarea placeholder="Anything the planner should know">${c.notes || ""}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Save</button></div>`;
306
313
  li.querySelector("[data-a=s]").onclick = async () => {
307
314
  const next = li.querySelector("input").value.trim(),
308
315
  notes = li.querySelector("textarea").value.trim();
@@ -314,7 +321,7 @@ function inlineForm(c) {
314
321
  }
315
322
  if (store.mode === "reject") {
316
323
  const idea = c.kind !== "run";
317
- li.innerHTML = html`<div class="q">${c.kind === "backlog" ? "Not doing this backlog item?" : idea ? "Reject this idea?" : "Abandon this run?"} The reason is recorded.</div><input placeholder="Reason"><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn warn" data-a="s">${idea ? "Reject" : "Abandon"}</button></div>`;
324
+ li.innerHTML = html`<div class="q">${c.kind === "backlog" ? "Not doing this backlog item?" : idea ? "Reject this idea?" : "Abandon this run?"} The reason is recorded.</div><input placeholder="Why not?"><div class="row"><button class="btn ghost" data-a="c">Keep it</button><button class="btn warn" data-a="s">${idea ? "Reject" : "Abandon"}</button></div>`;
318
325
  li.querySelector("[data-a=s]").onclick = async () => {
319
326
  const why = li.querySelector("input").value.trim();
320
327
  if (!why) {
@@ -450,7 +457,7 @@ async function startRun(c, kind) {
450
457
  const backlog = c.kind === "backlog" ? { source: c.source, n: c.n } : undefined;
451
458
  const r = await post("/run", { dir: c.state, text: c.text, backlog, kind, agent: p ? p.agent : undefined, yolo });
452
459
  if (r.ok) {
453
- toast((kind === "full" ? "Full" : "Lite") + (yolo ? " yolo" : "") + " run " + where(r) + ": " + via(r.agent));
460
+ toast((kind === "full" ? "Full" : kind === "lite" ? "Lite" : "Started") + (yolo ? " yolo" : "") + " run " + where(r) + ": " + via(r.agent));
454
461
  // A backlog card has no terminal of its own; the run's card gets it once the run exists.
455
462
  if (r.pty && !backlog) store.wantTerm = key(c);
456
463
  }
@@ -461,12 +468,13 @@ const HINT = { 1: "Ideas you run are planned here", 2: "Approved plans are built
461
468
 
462
469
  // The new-idea form, wired into f: inline at the top of Ideas, or in a modal.
463
470
  function ideaForm(f, close) {
464
- f.innerHTML = html`${store.all ? html`<div class="combo" id="ndc"><button type="button" class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<input id="newidea" placeholder="What would you like done?"><textarea id="newnotes" placeholder="Notes: links, paths, background (optional)"></textarea><div class="att" id="newatt"></div><input type="file" id="newfile" multiple hidden><div class="row"><button type="button" class="ib" id="newpick" aria-label="Attach files" title="Attach files">${ico("clip")}</button><button type="button" class="btn ghost" id="nc">Cancel</button><button type="button" class="btn pri" id="ns">Add</button></div>`;
471
+ f.innerHTML = html`${store.all ? html`<div class="combo" id="ndc"><button type="button" class="sel" id="newdir" aria-label="Project for this idea" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<input id="newidea" placeholder="What do you want to build?"><textarea id="newnotes" placeholder="Anything the planner should know (optional)"></textarea><div class="att" id="newatt"></div><input type="file" id="newfile" multiple hidden><div class="row"><button type="button" class="ib" id="newpick" aria-label="Attach files" title="Attach files">${ico("clip")}</button><button type="button" class="btn ghost" id="nc">Cancel</button><div class="split"><button type="button" class="btn pri" id="ns">Add</button><button type="button" class="btn pri" id="nm" aria-label="More ways to add" aria-haspopup="menu" aria-expanded="false">${ico("chev")}</button><div class="menu-pop" role="menu" hidden><button type="button" role="menuitem" id="nr" title="Add the idea and run it now">${ico("play")}Start run</button></div></div></div>`;
465
472
  const inp = f.querySelector("#newidea"),
466
473
  files = [];
467
474
  draft(inp, "f:new:0");
468
475
  draft(f.querySelector("textarea"), "f:new:1");
469
- const save = async () => {
476
+ // run: add the idea, then launch it at once with the project's runner and agent.
477
+ const save = async (run) => {
470
478
  const text = inp.value.trim();
471
479
  if (!text) return;
472
480
  const notes = f.querySelector("textarea").value.trim();
@@ -481,6 +489,11 @@ function ideaForm(f, close) {
481
489
  const n = await post("/note", { dir, for: text, text: "", files: enc });
482
490
  if (!n.ok) toast("Idea added, files not attached");
483
491
  }
492
+ if (run) {
493
+ await load();
494
+ const c = store.S.cards.find((x) => x.kind === "idea" && x.text === text && (!dir || x.state === dir));
495
+ if (c) return startRun(c);
496
+ }
484
497
  }
485
498
  load();
486
499
  };
@@ -503,7 +516,37 @@ function ideaForm(f, close) {
503
516
  }
504
517
  };
505
518
  if (store.all) ideaCombo(f);
506
- f.querySelector("#ns").onclick = save;
519
+ f.querySelector("#ns").onclick = () => save(false);
520
+ f.querySelector("#nr").onclick = () => save(true);
521
+ {
522
+ const mb = f.querySelector("#nm"),
523
+ menu = f.querySelector(".split .menu-pop");
524
+ // Focus moves into the menu only when it was opened from the keyboard.
525
+ const show = (on, kbd) => {
526
+ menu.hidden = !on;
527
+ mb.setAttribute("aria-expanded", String(on));
528
+ if (on && kbd) setTimeout(() => menu.querySelector("button").focus());
529
+ };
530
+ mb.onclick = (e) => {
531
+ e.stopPropagation();
532
+ show(menu.hidden, e.detail === 0);
533
+ };
534
+ menu.onkeydown = (e) => {
535
+ if (e.key === "Escape") {
536
+ e.preventDefault();
537
+ e.stopPropagation();
538
+ show(false);
539
+ mb.focus();
540
+ }
541
+ };
542
+ f.addEventListener("click", (e) => {
543
+ if (!menu.hidden && !e.target.closest(".split")) show(false);
544
+ });
545
+ const sp = f.querySelector(".split");
546
+ sp.addEventListener("focusout", (e) => {
547
+ if (!sp.contains(e.relatedTarget)) show(false);
548
+ });
549
+ }
507
550
  f.querySelector("#nc").onclick = close;
508
551
  {
509
552
  const m = mic([inp, f.querySelector("textarea")]);
@@ -512,7 +555,7 @@ function ideaForm(f, close) {
512
555
  inp.onkeydown = (e) => {
513
556
  if (e.key === "Enter") {
514
557
  e.preventDefault();
515
- save();
558
+ save(false);
516
559
  }
517
560
  if (e.key === "Escape") {
518
561
  e.preventDefault();
package/web/board/git.ts CHANGED
@@ -31,7 +31,7 @@ export function gitBtn(c) {
31
31
  export function confirmCommit(c, msg) {
32
32
  const g = store.runDoc.git,
33
33
  foot = $("pfoot");
34
- foot.innerHTML = html`<div class="inl" style="flex:1"><div class="q">Commit ${g.files.length} changed file${g.files.length === 1 ? "" : "s"} on ${g.branch || "this branch"} and push to ${g.remote}?</div><div class="q dim">Only the files this run's report names.${g.others ? ` ${g.others} other changed file${g.others === 1 ? " is" : "s are"} left alone.` : ""}</div><pre class="files">${g.files.join("\n")}</pre>${g.shared && g.shared.length ? html`<div class="q">These files also hold other runs' changes, and a file is committed whole:</div><pre class="files">${g.shared.join("\n")}</pre>` : ""}<textarea aria-label="Commit message" placeholder="feat(scope): what changed">${msg || commitMsg(store.runDoc.impl) || c.title || c.text}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Commit &amp; push</button></div></div>`;
34
+ foot.innerHTML = html`<div class="inl" style="flex:1"><div class="q">Commit ${g.files.length} changed file${g.files.length === 1 ? "" : "s"} on ${g.branch || "this branch"} and push to ${g.remote}?</div><div class="q dim">Only the files this run's report names.${g.others ? ` ${g.others} other changed file${g.others === 1 ? " is" : "s are"} left alone.` : ""}</div><pre class="files">${g.files.join("\n")}</pre>${g.shared && g.shared.length ? html`<div class="q">These files also hold other runs' changes, and a file is committed whole:</div><pre class="files">${g.shared.join("\n")}</pre>` : ""}<textarea aria-label="Commit message" placeholder="Describe what changed">${msg || commitMsg(store.runDoc.impl) || c.title || c.text}</textarea><div class="q dim" id="clong" hidden>The first line is over 72 characters; logs will cut it short.</div><div class="row"><button class="btn ghost" data-a="c">Cancel</button><button class="btn pri" data-a="s">Commit &amp; push</button></div></div>`;
35
35
  const inp = foot.querySelector("textarea"),
36
36
  go = foot.querySelector("[data-a=s]");
37
37
  inp.focus();
@@ -975,7 +975,7 @@ export async function openSources() {
975
975
  if (!dir && !showable().length) return toast("Pick a project first");
976
976
  // The new-idea form, cut down: the request, files, dictation and, in All projects, the project.
977
977
  const dl = modal(
978
- `${all ? `<div class="combo" id="ndc"><button type="button" class="sel" id="newdir" aria-label="Project for this request" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<textarea id="newidea" placeholder="New request for the icebox" aria-label="Request"></textarea><div class="att"></div><input type="file" multiple hidden><div class="row"><button type="button" class="ib" id="newpick" aria-label="Attach files" title="Attach files">${ico("clip")}</button><button type="button" class="btn ghost" value="">Cancel</button><button class="btn pri">Add</button></div>`,
978
+ `${all ? `<div class="combo" id="ndc"><button type="button" class="sel" id="newdir" aria-label="Project for this request" aria-haspopup="listbox" aria-expanded="false"></button><div class="menu-pop" hidden><input class="sel" aria-label="Search projects" placeholder="Search projects…" spellcheck="false" autocomplete="off"><div id="nds" role="listbox"></div></div></div>` : ""}<textarea id="newidea" placeholder="What did someone ask for?" aria-label="Request"></textarea><div class="att"></div><input type="file" multiple hidden><div class="row"><button type="button" class="ib" id="newpick" aria-label="Attach files" title="Attach files">${ico("clip")}</button><button type="button" class="btn ghost" value="">Cancel</button><button class="btn pri">Add</button></div>`,
979
979
  );
980
980
  const f = dl.querySelector("form"),
981
981
  inp = f.querySelector("textarea"),