@dougharness/cli 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ablate.js +2 -2
- package/dist/ablate.js.map +1 -1
- package/dist/bin.js +13 -4
- package/dist/bin.js.map +1 -1
- package/dist/board-page.d.ts +5 -1
- package/dist/board-page.js +19 -5
- package/dist/board-page.js.map +1 -1
- package/dist/board-serve.js +34 -10
- package/dist/board-serve.js.map +1 -1
- package/dist/board.d.ts +1 -1
- package/dist/board.js +160 -13
- package/dist/board.js.map +1 -1
- package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/flow/agents/planner.md +1 -0
- package/node_modules/@dougharness/flow/hooks/register.tsx +18 -2
- package/node_modules/@dougharness/flow/lib/autopilot-core.mjs +94 -7
- package/node_modules/@dougharness/flow/lib/autopilot-view.mjs +31 -4
- package/node_modules/@dougharness/flow/lib/board.mjs +375 -30
- package/node_modules/@dougharness/flow/lib/card-loop.mjs +237 -24
- package/node_modules/@dougharness/flow/lib/cost.mjs +9 -0
- package/node_modules/@dougharness/flow/lib/decisions.mjs +2 -4
- package/node_modules/@dougharness/flow/lib/learn.mjs +95 -54
- package/node_modules/@dougharness/flow/lib/memory.mjs +1403 -44
- package/node_modules/@dougharness/flow/lib/plan.mjs +40 -0
- package/node_modules/@dougharness/flow/package.json +1 -1
- package/node_modules/@dougharness/flow/scripts/autopilot.mjs +355 -13
- package/node_modules/@dougharness/flow/scripts/board.mjs +160 -27
- package/node_modules/@dougharness/flow/scripts/cost.mjs +6 -3
- package/node_modules/@dougharness/flow/scripts/learn.mjs +1 -1
- package/node_modules/@dougharness/flow/scripts/memory.mjs +668 -39
- package/node_modules/@dougharness/flow/scripts/plan.mjs +3 -3
- package/node_modules/@dougharness/flow/scripts/session-recall.mjs +11 -4
- package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +7 -3
- package/node_modules/@dougharness/flow/skills/doug-learn/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +11 -2
- package/node_modules/@dougharness/flow/skills/doug-start/SKILL.md +9 -8
- package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/research/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +3 -3
- package/node_modules/@dougharness/flow/workflows/card-queen.js +238 -25
- package/node_modules/@dougharness/flow/workflows/doug-implement.js +55 -37
- package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/gates/lib/baseline.mjs +32 -0
- package/node_modules/@dougharness/gates/lib/block-ledger.mjs +45 -5
- package/node_modules/@dougharness/gates/lib/docs-gate.mjs +6 -2
- package/node_modules/@dougharness/gates/lib/verified-tree.mjs +33 -0
- package/node_modules/@dougharness/gates/package.json +1 -1
- package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +130 -42
- package/node_modules/@dougharness/gates/scripts/verify.mjs +20 -4
- package/package.json +3 -3
- package/templates/board-app.js +240 -30
|
@@ -179,18 +179,18 @@ function cachingProvider(provider) {
|
|
|
179
179
|
async function attachLessons(plan, dir, provider) {
|
|
180
180
|
if (!existsSync(join(dir, MEMORY_DB_RELPATH))) return plan;
|
|
181
181
|
try {
|
|
182
|
-
const { openMemory, recallLessons, degradedWarning, degradedReason,
|
|
182
|
+
const { openMemory, LIVE_LESSON_SQL, recallLessons, degradedWarning, degradedReason, renderFlowLessons } = await import("../lib/memory.mjs");
|
|
183
183
|
const staleDays = readMemoryStaleDays(dir);
|
|
184
184
|
const m = openMemory(dir);
|
|
185
185
|
try {
|
|
186
186
|
const detectedProvider = await detectOnce(provider);
|
|
187
187
|
// An empty lesson store has nothing to degrade (a code-index-only database, say): no warning for it.
|
|
188
|
-
const hasLessons = m.prepare(
|
|
188
|
+
const hasLessons = m.prepare(`SELECT 1 FROM lessons WHERE ${LIVE_LESSON_SQL} LIMIT 1`).get() !== undefined;
|
|
189
189
|
const tasks = [];
|
|
190
190
|
for (const task of plan.tasks) {
|
|
191
191
|
const taskRecall = await recallLessons(m, `${task.title}\n${task.spec}`, { files: task.files, k: 8, checkoutDir: dir, provider, staleDays, detectedProvider });
|
|
192
192
|
if (hasLessons) warnOnce(degradedWarning(taskRecall, { what: "memory recall", detectedProvider }));
|
|
193
|
-
const taskLessons =
|
|
193
|
+
const taskLessons = renderFlowLessons(taskRecall.lessons);
|
|
194
194
|
const lessonsText = taskLessons.text !== null && taskRecall.degraded ? `(recall was keyword-only: ${degradedReason(taskRecall, detectedProvider)})\n${taskLessons.text}` : taskLessons.text;
|
|
195
195
|
tasks.push(taskLessons.text === null ? task : { ...task, lessons: lessonsText, lessonIds: taskLessons.ids });
|
|
196
196
|
}
|
|
@@ -63,7 +63,7 @@ async function run() {
|
|
|
63
63
|
// Checked before anything opens it: a missing database is never created here.
|
|
64
64
|
if (!existsSync(join(dir, ".doug", ".state", "memory", "memory.db"))) return;
|
|
65
65
|
|
|
66
|
-
const { openMemory, recallLessons, topLessons, renderLessons, degradedReason } = await import("../lib/memory.mjs");
|
|
66
|
+
const { openMemory, recallLessons, topLessons, renderLessons, degradedReason, windowHidden, windowNote } = await import("../lib/memory.mjs");
|
|
67
67
|
const { readMemoryConfig, readMemoryStaleDays, createProvider } = await import("../lib/embeddings.mjs");
|
|
68
68
|
const { loadBoard } = await import("../lib/board.mjs");
|
|
69
69
|
|
|
@@ -78,6 +78,7 @@ async function run() {
|
|
|
78
78
|
let lessons;
|
|
79
79
|
let header;
|
|
80
80
|
let scope;
|
|
81
|
+
let hidden = null;
|
|
81
82
|
try {
|
|
82
83
|
if (cards.length) {
|
|
83
84
|
const query = cards.map((c) => `${c.title}\n${c.goal || ""}`).join("\n");
|
|
@@ -101,10 +102,12 @@ async function run() {
|
|
|
101
102
|
? `recall: hybrid via ${result.provider} ${provider.model}/${provider.dims}`
|
|
102
103
|
: `recall: keyword-only (${degradedReason(result)})`;
|
|
103
104
|
lessons = result.lessons;
|
|
105
|
+
hidden = result.hidden;
|
|
104
106
|
} else {
|
|
105
107
|
scope = "the repository: top by helpful-harmful";
|
|
106
108
|
header = null;
|
|
107
109
|
lessons = topLessons(m, { k: K, staleDays });
|
|
110
|
+
hidden = windowHidden(m, { staleDays });
|
|
108
111
|
}
|
|
109
112
|
} finally {
|
|
110
113
|
// A raced-out hybrid recall may still be running; closing the handle makes it fail into its catch.
|
|
@@ -115,10 +118,14 @@ async function run() {
|
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
const rendered = renderLessons(lessons);
|
|
118
|
-
|
|
121
|
+
const note = windowNote(hidden, staleDays);
|
|
122
|
+
if (rendered.text === null && !note) return;
|
|
119
123
|
const additionalContext =
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
rendered.text === null
|
|
125
|
+
? `[doug] ${note}`
|
|
126
|
+
: `[doug] Lessons from this repository's memory (${header ? `${header}; ` : ""}${scope}):\n${rendered.text}\n` +
|
|
127
|
+
(note ? `${note}\n` : "") +
|
|
128
|
+
"Each line is [lesson id] text (citation). When you brief an agent, paste the lessons that apply and list their ids as memoryUsed.";
|
|
122
129
|
await write({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext } });
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -40,6 +40,8 @@ Batch: one `AskUserQuestion` names every remaining card of the batch (id, title,
|
|
|
40
40
|
|
|
41
41
|
A size L card with no `docs/specs/<card-id>/spec.md` is offered feature mode first (ADR 0012): after Start and before 2b, ask with `AskUserQuestion` whether to run `/doug-start <id>` (specification, optional pseudocode, architecture, refinement) or build now; `/doug-start <id>` ends this loop and the card stays In flow. S and M cards go straight on, and any card can still run `/doug-start <id>` on request. Under autopilot the offer is not asked and the card goes straight on.
|
|
42
42
|
|
|
43
|
+
Batch: ask the offer for each such card before 2b; a card answered `/doug-start <id>` is dropped from the batch and stays In flow for `/doug-start <id>`, and the rest of the batch goes on (one card left is a plain single-card run; none left ends this loop).
|
|
44
|
+
|
|
43
45
|
## 2b. Shape: by hand, or a swarm
|
|
44
46
|
|
|
45
47
|
Size S, or a card with no size at all: by hand, no question.
|
|
@@ -50,7 +52,7 @@ Batch: a batch runs only as a swarm, so for two or more cards this step does not
|
|
|
50
52
|
|
|
51
53
|
Recommend a swarm only when the goal asks for two or more independent deliverables: separate pieces of work, each with its own test file, where none uses a function, type, or file another one adds (the 2026-09-19 split probe's four report commands, docs/live-runs.md); otherwise recommend by hand. Say in the question, in one clause, which side of this rule the card is on. The recommendation is the lead's reading of the goal, not a default.
|
|
52
54
|
|
|
53
|
-
Otherwise, for one card, ask with `AskUserQuestion`: run it as a swarm, or by hand. Swarm: spawn the planner the way `doug-plan` does, on the `plan` row: read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`), and pass that row's model (omit `model` when the row says `inherit`), but tell it this plan runs as a swarm the way `/doug-swarm` does: `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<title>\": <goal>. This plan runs as a swarm: set \"swarm\": true in the plan file, and size every task M or L unless it is truly one bounded change (a size-S task keeps a single implementer). Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })`. Then `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <id>` and `plan.mjs set swarm on` (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set swarm on`) guarantee the card and the opt-in even if the planner forgot, put `plan.mjs show` in the chat, and ask the approval gate with `AskUserQuestion`: approve as written, edit first, or stop. Approval is never automatic.
|
|
55
|
+
Otherwise, for one card, ask with `AskUserQuestion`: run it as a swarm, or by hand. Swarm: spawn the planner the way `doug-plan` does, on the `plan` row: read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`), and pass that row's model (omit `model` when the row says `inherit`), but tell it this plan runs as a swarm the way `/doug-swarm` does: `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<title>\": <goal>. This plan runs as a swarm: set \"swarm\": true in the plan file, and size every task M or L unless it is truly one bounded change (a size-S task keeps a single implementer). Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })`. Then `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <id>` and `plan.mjs set swarm on` (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set swarm on`) guarantee the card and the opt-in even if the planner forgot, put the complete output of `plan.mjs show` in the chat, verbatim, in a fenced code block, before the AskUserQuestion (never a summary instead), and ask the approval gate with `AskUserQuestion`: approve as written, edit first, or stop. Approval is never automatic.
|
|
54
56
|
|
|
55
57
|
On approve: `plan.mjs approve`, `plan.mjs anchor`, invoke the `swarm-launch` skill. Note the wall clock when it starts and ends. When it returns, save the `result` object of the Workflow tool's output to `.doug/.state/last-report.json` with the Write tool and note the run id the Workflow tool printed (`wf_...`), the way `/doug-next` step 4 does: `plan.mjs replan`, `plan.mjs done`, `board.mjs record`, and `cost.mjs <run-id>` all read those two. If the report carries `paused`, follow `/doug-next` step 4's paused paragraph: name the tasks in `next` and ask with `AskUserQuestion` whether to open the gate; yes reopens with `plan.mjs gate open <level>` and a relaunch with `resumeFromRunId`, no leaves the card In flow and stops — `plan.mjs done` refuses while the last report is paused. A paused or failed batch changes nothing in this mechanism: the paused paragraph and `plan.mjs replan` both work from the plan file, which keeps `cards` through replan, and a second pass is detected the way `/doug-next` section 2 is, by the plan's `cards` matching this batch. Otherwise read the report as `swarm-launch` says, then `plan.mjs done` and `plan.mjs land`; a report that is not ok goes back through `plan.mjs replan` and the approval gate above, or by hand if the user says so — for a batch, "by hand" dissolves it: the first card continues by hand at step 3, and every other card of the batch goes back to Ready with `board.mjs move <id> ready`.
|
|
56
58
|
|
|
@@ -64,11 +66,11 @@ The session model is the lead and only the lead, on both tracks; it never implem
|
|
|
64
66
|
|
|
65
67
|
First the research step when step 1 called for one: invoke the `research` skill. It names the agent (claude-code-guide for Claude Code questions, the plugin's read-only `researcher` agent otherwise), runs several researchers in parallel when there are several distinct questions, and writes one cited note to `.doug/.state/research/<id>.md`; read it before writing the brief, and treat a line marked unverified as a fact the code must not depend on.
|
|
66
68
|
|
|
67
|
-
Then recall memory for the card: `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" brief-lessons <id>`, and paste its output verbatim, header, lesson lines with their ids, and `memoryUsed` line, into every tester, coder, and reviewer brief. When it prints no lessons, or exits nonzero, say so in the chat and go on: recall never blocks a card. Save each brief to `.doug/.state/reports/<id>/briefs/<n>-<seat>.md` before spawning its agent, and each reviewer report to `.doug/.state/reports/<id>/reviews/<n>.md` when it returns.
|
|
69
|
+
Then recall memory for the card: `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" brief-lessons <id>`, and paste its output verbatim, header, lesson lines with their ids, and `memoryUsed` line, into every tester, coder, and reviewer brief. Then fetch the full text of each indexed lesson the card needs with `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" lesson show <id> --card <id>` and paste each output verbatim below that block in the same briefs; never cut or paraphrase a fetched lesson. When it prints no lessons, or exits nonzero, say so in the chat and go on: recall never blocks a card. Save each brief to `.doug/.state/reports/<id>/briefs/<n>-<seat>.md` before spawning its agent, and each reviewer report to `.doug/.state/reports/<id>/reviews/<n>.md` when it returns.
|
|
68
70
|
|
|
69
71
|
On the lean lane and on a tests-only card, every brief carries the same `brief-lessons` block.
|
|
70
72
|
|
|
71
|
-
Then find the test file that covers each module the goal touches: the project's test skill when it has one, otherwise grep for the module's name under the test directories. Follow any procedure CLAUDE.md names for changes of this kind (its test table, its rules for generated or copied files, its type declarations), and put those rules into the brief: read the goal as the acceptance list, name the test file you found — when the project has a tester agent, the tester writes or extends that file first, from the goal; the coder runs only that file while iterating and does not write tests — and the mutation list step 3 demands (one mutation per case the card's goal names, each with the test that must fail). A card whose acceptance is that a mechanism exists is proved by mutating it: the coder applies each listed mutation, confirms the named test fails, reverts it, and reports which assertion failed; a card that only changes prose, a message, or a docs line needs none. The reviewer, on every card, reruns the brief's mutation list and adds its own. A tests-only card's tester never edits a production file in the live checkout. The coder and the reviewer apply each mutation in a scratch copy or git worktree under `.doug/.state`, run the one test file there, and remove the copy. Keep the change to what the card asks: a defect found on the way becomes its own hand-track card. On facts: a fact the brief states about the workflow, a hook contract, or a model row is copied verbatim from CLAUDE.md, README, docs, or the code with its path, never paraphrased; a claim the lead cannot cite goes into the brief as unverified.
|
|
73
|
+
Then find the test file that covers each module the goal touches: the project's test skill when it has one, otherwise grep for the module's name under the test directories. Follow any procedure CLAUDE.md names for changes of this kind (its test table, its rules for generated or copied files, its type declarations), and put those rules into the brief: read the goal as the acceptance list, name the test file you found — when the project has a tester agent, the tester writes or extends that file first, from the goal; the coder runs only that file while iterating and does not write tests — and the mutation list step 3 demands (one mutation per case the card's goal names, each with the test that must fail). A mutation is a code change to a file in the card worktree that a test catches; a check that is not a code mutation (a git diff, a file-absence check) goes in reviewerChecks, never in mutations. A card whose acceptance is that a mechanism exists is proved by mutating it: the coder applies each listed mutation, confirms the named test fails, reverts it, and reports which assertion failed; a card that only changes prose, a message, or a docs line needs none. The reviewer, on every card, reruns the brief's mutation list and adds its own. A tests-only card's tester never edits a production file in the live checkout. The coder and the reviewer apply each mutation in a scratch copy or git worktree under `.doug/.state`, run the one test file there, and remove the copy. Keep the change to what the card asks: a defect found on the way becomes its own hand-track card. On facts: a fact the brief states about the workflow, a hook contract, or a model row is copied verbatim from CLAUDE.md, README, docs, or the code with its path, never paraphrased; a claim the lead cannot cite goes into the brief as unverified. Every tester, coder, and reviewer brief carries the no-CPU-burners rule verbatim: A burner is any process started only to load the machine (yes > /dev/null, stress, a busy loop), bounded or not; concurrent runs of the project's test command (the commands in `stopGate.commands`) are not burners. Never start a burner without the user's yes: a seat that wants one stops and returns a designQuestion naming the load it would start (the process, how many, the timeout, and why), and starts it only after an answer from the user says yes. Before hand-back, confirm with ps that no load process you started survives, orphans reparented to PID 1 included, and kill any that does. When a seat returns a question asking to start a burner, the lead asks the user and resumes the seat with the answer; no burner starts before a yes.
|
|
72
74
|
|
|
73
75
|
Next: before the coder, spawn the project's `tester` agent (`.claude/agents/tester.md`, on the `implement` row) with the goal as the acceptance list and the test file step 3 found, so the failing tests are written from the card, not from the code — the Agent tool naming the project's `tester` agent, `model` set to the `implement` row's model, the same way the coder runs on that row. Tell the tester to end its report with the `tests_red_by_design` claim naming the test files it left red, so the SubagentStop gate lets it stop; the lead's own Stop stays red until the coder's change lands. The claim may sit in the tester's final plain-text message or in its hand-back call; the gate reads the plain text first, then the last hand-back. The tester is spawned in the foreground, never in the background: the coder starts from its red tests.
|
|
74
76
|
|
|
@@ -84,6 +86,8 @@ Before the commit the lead itself runs `node .doug/hooks/scripts/verify.mjs` (th
|
|
|
84
86
|
|
|
85
87
|
Stay within the card's goal. A defect found on the way that the goal does not cover becomes a new hand-track card, not part of this commit.
|
|
86
88
|
|
|
89
|
+
When a by-hand run stops a card after Start (a hook block it reports, a design question answered stop, a gate it cannot get green, an unresolved reviewer blocker, or the user saying stop), the lead runs the recording step once: `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" record <id> --hand --stop <reason> --detail "<one line>"`. `<reason>` is one of the card loop's stop reasons (`lib/card-loop.mjs` `STOP_REASONS`) or `user-stopped` when the user stopped it.
|
|
90
|
+
|
|
87
91
|
## 4. Record and land
|
|
88
92
|
|
|
89
93
|
With the commit sha (the merge sha for a swarmed card) and the wall clock since step 1 (or since 2b's launch, for a swarmed card):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: doug-learn
|
|
3
|
-
description: Log-then-propose self-improvement (card learn-signals). Counts signals from the outcomes/lessons store and the run trace, renders each candidate change - demote a violated prose rule to a hook, delete a rule nothing references, promote a
|
|
3
|
+
description: Log-then-propose self-improvement (card learn-signals). Counts signals from the outcomes/lessons store and the run trace, renders each candidate change - demote a violated prose rule to a hook, delete a rule nothing references, promote a pitfall lesson recorded for 3 cards over 2 sessions (as a path-scoped `.claude/rules` file, or a CLAUDE.md Gotchas bullet when no files are known), tighten a skill description - as a diff, and asks the user to approve each one before applying it. Only the user invokes this; it never applies a change on its own.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
allowed-tools: AskUserQuestion, Read, Bash(node *learn.mjs *), Bash(git *)
|
|
6
6
|
---
|
|
@@ -33,6 +33,8 @@ If `.doug/plan.json` is already a draft whose `card` is this id (or whose `cards
|
|
|
33
33
|
|
|
34
34
|
A size L card with no `docs/specs/<card-id>/spec.md` is offered feature mode first (ADR 0012): before the planner, ask with `AskUserQuestion` whether to run `/doug-start <id>` (specification, optional pseudocode, architecture, refinement) or plan now; `/doug-start <id>` ends this loop and the card stays In flow. S and M cards go straight to the plan, and any card can still run `/doug-start <id>` on request. Under autopilot the offer is not asked and the card goes straight to the plan.
|
|
35
35
|
|
|
36
|
+
Batch: ask the offer for each such card before any planner is spawned; a card answered `/doug-start <id>` is dropped from the batch and stays In flow for `/doug-start <id>`, and the rest of the batch goes on (one card left is a plain single-card run; none left ends this loop).
|
|
37
|
+
|
|
36
38
|
If the card's goal depends on a fact outside the repository (a Claude Code hook contract, a CLI's output format, a third-party API), invoke the `research` skill first and add `Research note: <path>` to the planner's prompt; the planner writes the facts it relied on into the plan's goal with their sources.
|
|
37
39
|
|
|
38
40
|
The planner runs on the `plan` row (see "Who does the work" above): read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`), and pass that row's model on every planner spawn below (omit `model` when the row says `inherit`).
|
|
@@ -98,16 +100,19 @@ Ask with `AskUserQuestion` whether to take the next Ready card. Yes: go to step
|
|
|
98
100
|
|
|
99
101
|
`/doug-next --auto [n]` takes the first n runnable Ready cards of either track in board order (`board.mjs next --batch <n> --track any`). With n, the queue is fixed at the approval. Without n, autopilot is unlimited: whenever a slot is free it re-reads Ready with `autopilot.mjs pick` and takes the next runnable Ready cards in board order. It never takes a Backlog card: follow-ups still go to Backlog, and a card the user promotes to Ready during the run is picked up. It shows the queue once, each card's id, size, track, and class, with the caps: cards, wall clock, and USD. The caps default to the queue's length in cards, the wall cap that board.mjs next --batch prints (the sum of each card's budget from the size x class wall budget table in board.mjs, at the p80 of measured landings: a size/class cell with fewer than 5 landings falls back to its size's all-class figure, a card with no class uses its size's all-class figure, and a card with no size counts as M), and the usd cap it prints beside it (the sum of each card's budget from the size x class usd budget table in board.mjs, at the p80 of measured cost over flow and hand landings, with the same fallbacks), and the user may change them at the approval. The USD cap has no fixed default and no floor: it is the sum of the per-card budgets, and the user may raise it at the approval. Unlimited autopilot shows the runnable Ready cards at the approval, and the approval covers every card it later takes from Ready. Unlimited autopilot has no card cap: its wall cap and its USD cap grow as each card is taken, by that card's budget from the same size x class tables (`board.mjs budgets` and `board.mjs budgets --usd` print them); only the totals are checked on every queen notification. When the measured walls or costs drift, the lead reruns `board.mjs budgets --from docs/live-runs.md` and `board.mjs budgets --usd --from docs/live-runs.md`, which recompute the tables and print each cell's n, and replaces each table in the `board.mjs` source with the printed one. Autopilot asks one approval, for the whole queue, and no other gate: no start gate, no shape question, and no continue gate.
|
|
100
102
|
|
|
103
|
+
`/doug-next --milestone <id>` passes the id to `board.mjs next --milestone <id>`, and `--auto [n] --milestone <id>` adds `--milestone <id>` to that `board.mjs next --batch <n> --track any` command and to `autopilot.mjs pick`, so only that milestone's Ready cards launch. The closing summary also prints `board.mjs rollup --milestone <id>`. Autopilot never changes a milestone's status.
|
|
104
|
+
|
|
101
105
|
A card the user asks for during the run is not a gate: the lead scopes and files it as "Cards the user asks for" says, and the queue resumes.
|
|
102
106
|
|
|
103
107
|
Each card still runs its track unchanged (research when needed, tester, coder, reviewer, mutations, gate, commit, record, Done; a lean-lane card has no tester and no mutation list). A hand card runs card-queen, whose queen follows doug-hand from step 3, with step 2's start gate question, 2b's question, and step 6's continue gate skipped; a hand card that asks for a swarm takes 2b's swarm branch instead of step 3, as below. A flow card runs doug-implement per card, following sections 2 to 6 above, with section 3's approval replaced as below and section 7 skipped. Only the questions are skipped, not the actions behind them. Under autopilot a card still gets everything the skipped gates' Start or Approve branches do: a hand card gets doug-hand step 2's class, move to flow, and condition open, and the wall clock is noted when the card starts; a flow card gets section 1's move and section 3's Approve steps.
|
|
104
108
|
|
|
105
109
|
The queue approval stands for each flow-track card's plan approval: the lead puts plan.mjs show in the chat and runs plan.mjs approve itself. A card runs by hand unless its goal asks for a swarm. A hand card whose goal asks for a swarm follows doug-hand's 2b swarm branch, with the queue approval standing for its plan approval. Follow-ups are filed to Backlog as section 6 says, without a question. On every queen notification the lead measures the session's cost with cost.mjs --session <id> and checks every cap.
|
|
106
110
|
|
|
107
|
-
Parallel cards: up to `workers.max` cards (default 3) run at once, each under its own queen, a background run in the card's own worktree, and main only coordinates. Main picks with `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" pick` up to workers.max cards that are disjoint: for hand cards, goals that name no common path and components that differ; for flow cards, plans whose owned files do not overlap. Flow cards in `needsPlan` get one `doug-flow:planner` each, all in one message, on the plan row, each writing `.doug/plans/<id>.json` and validating it with `plan.mjs validate --card <id>`. For each planned card main runs `plan.mjs set card <id> --card <id>`, `plan.mjs set integrationBranch doug/<id> --card <id>`, and `plan.mjs set integrationWorktree .claude/worktrees/doug-integration-<id> --card <id>`, puts `plan.mjs show --card <id>` in the chat, and runs `plan.mjs approve --card <id>`, because the queue approval stands for it; then main picks again. A flow card gets section 1's move and a hand card gets doug-hand step 2's class, move, and condition open, before its launch. A flow queen is `Workflow({ scriptPath: <the queen.js path>, args: <plan.mjs json --card <id>> })`, the queen.js being the per-card copy of `workflows/doug-implement.js` that `autopilot.mjs queen-args` writes to `.doug/.state/reports/<id>/queen.js`; a hand queen is `Workflow({ scriptPath: <the queen.js path>, args })`, the per-card copy of `workflows/card-queen.js` at the same path, with args `{ card, options, models, repoRoot, worktreePath }`, the worktreePath being `<repoRoot>/.claude/worktrees/card-<id>`. Before any queen launch, main runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" agent-types --kind <flow|hand>`, which prints the agent types that queen spawns, one per line, and compares each name with the agent types its own session lists; when one is missing it launches no queen and tells the user to run /reload-plugins or restart. To launch a queen, main runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" queen-args <id> --kind <flow|hand> --with <the other ids launched in the same message>`, which writes `.doug/.state/reports/<id>/queen-args.json` and the per-card `.doug/.state/reports/<id>/queen.js`, whose run name is `<short id> card-queen` or `<short id> flow-queen`, and prints both paths; then `Workflow({ scriptPath: <the queen.js path>, args })` with that file's JSON as args; then `autopilot.mjs launched <id> --run <wf_id> --kind <flow|hand> --worktree <abs> --args-file <path>`, the flow worktree being `<repoRoot>/<the plan's integrationWorktree>`, because that value is relative. Main starts all the Workflow calls in one message, and it never briefs, builds, or reviews a queen card. Each queen's report arrives in that run's task-notification `<result>`, not in the Workflow tool's return, and main pipes it through `autopilot.mjs report --run <wf_id> --file .doug/.state/briefs/<id>-result.json`, the queen's result saved there, which prints land, resume, or ask. Land means the landing steps below; resume relaunches the same `.doug/.state/reports/<id>/queen.js` with `resumeFromRunId` and the saved `queen-args.json`, then runs `autopilot.mjs resumed <id> --run <new wf_id>`, once, and a second resume is ask; ask stops that card and asks the user. Main lands one card at a time: a flow card with `plan.mjs done --card <id>` then `plan.mjs land --card <id>`, a hand card with `plan.mjs land-card <id> --branch card/<id> --worktree <abs>`. After each landing main runs `board.mjs move`, the run-report skill with `cost.mjs <wf_id> --report <per-card report path>`, which is `.doug/.state/reports/<id>/last-report.json` for a flow card and `.doug/.state/reports/<id>/card-report.json` for a hand card, and `autopilot.mjs lock-wait <id> --ms <n>`, where n is the `lockWaitMs` line that `plan.mjs land` or `land-card` printed, and the landing commit carries the card's plan `.doug/plans/<id>.json`. A resume never replans. When a queen's run needs the user, main asks while the other queens keep running. A cap reached launches no new queen and lets the in-flight ones finish. In manual or accept-edits mode every queen launch prompts, and the Workflow allow rule or "Yes, and don't ask again" runs n queens unattended (docs/research/parallel-cards-research.md section 4). A card tagged swarm (or `swarm: true`, or whose goal says to run it as a swarm) and a decision card run alone, as today, after the in-flight queens finish; `/doug-next <id>` by hand is unchanged.
|
|
111
|
+
Parallel cards: up to `workers.max` cards (default 3) run at once, each under its own queen, a background run in the card's own worktree, and main only coordinates. Main picks with `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" pick` up to workers.max cards that are disjoint: for hand cards, goals that name no common path and components that differ; for flow cards, plans whose owned files do not overlap. Flow cards in `needsPlan` get one `doug-flow:planner` each, all in one message, on the plan row, each writing `.doug/plans/<id>.json` and validating it with `plan.mjs validate --card <id>`. For each planned card main runs `plan.mjs set card <id> --card <id>`, `plan.mjs set integrationBranch doug/<id> --card <id>`, and `plan.mjs set integrationWorktree .claude/worktrees/doug-integration-<id> --card <id>`, puts `plan.mjs show --card <id>` in the chat, and runs `plan.mjs approve --card <id>`, because the queue approval stands for it; then main picks again. A flow card gets section 1's move and a hand card gets doug-hand step 2's class, move, and condition open, before its launch. A flow queen is `Workflow({ scriptPath: <the queen.js path>, args: <plan.mjs json --card <id>> })`, the queen.js being the per-card copy of `workflows/doug-implement.js` that `autopilot.mjs queen-args` writes to `.doug/.state/reports/<id>/queen.js`; a hand queen is `Workflow({ scriptPath: <the queen.js path>, args })`, the per-card copy of `workflows/card-queen.js` at the same path, with args `{ card, options, models, repoRoot, worktreePath }`, the worktreePath being `<repoRoot>/.claude/worktrees/card-<id>`. Before any queen launch, main runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" agent-types --kind <flow|hand>`, which prints the agent types that queen spawns, one per line, and compares each name with the agent types its own session lists; when one is missing it launches no queen and tells the user to run /reload-plugins or restart. To launch a queen, main runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" queen-args <id> --kind <flow|hand> --with <the other ids launched in the same message>`, which writes `.doug/.state/reports/<id>/queen-args.json` and the per-card `.doug/.state/reports/<id>/queen.js`, whose run name is `<short id> card-queen` or `<short id> flow-queen`, and prints both paths; then `Workflow({ scriptPath: <the queen.js path>, args })` with that file's JSON as args; then `autopilot.mjs launched <id> --run <wf_id> --kind <flow|hand> --worktree <abs> --args-file <path>`, the flow worktree being `<repoRoot>/<the plan's integrationWorktree>`, because that value is relative. When a hand card's goal depends on a fact outside the repository (a Claude Code hook contract, a CLI's output format, a third-party API), main adds `--research on` to its queen-args line, so its queen runs the research seat before the lead seat; queen-args takes the queen's base branch from the current branch, or main when there is none. Main starts all the Workflow calls in one message, and it never briefs, builds, or reviews a queen card. Each queen's report arrives in that run's task-notification `<result>`, not in the Workflow tool's return, and main pipes it through `autopilot.mjs report --run <wf_id> --file .doug/.state/briefs/<id>-result.json`, the queen's result saved there, which prints land, resume, rerun alone, or ask. Land means the landing steps below; resume relaunches the same `.doug/.state/reports/<id>/queen.js` with `resumeFromRunId` and the saved `queen-args.json`, then runs `autopilot.mjs resumed <id> --run <new wf_id>`, once, and a second resume is ask; ask stops that card and asks the user. For any asking stop with a stoppedAt seat (a design-decision, seat-blocked, or reviewer-major-open stop among them), main asks the user, records the answer with `autopilot.mjs answer <id> --answer "<the user's answer>"`, which adds it to `options.answers` in the saved `queen-args.json`, and `autopilot.mjs report` then prints resume with the answer: main relaunches the same queen.js with `resumeFromRunId` and that `queen-args.json` and runs `autopilot.mjs resumed`; the seats before the asking seat replay from the cache, the asking seat re-runs with the answer in its brief, and a second stop from that seat stops the card again. A gate-timeout stop is load, not a red, when another queen is in flight: `autopilot.mjs report` then prints rerun alone, and main launches no new queen, waits until no other queen is running, runs `autopilot.mjs rerun-alone <id>`, which adds a rerun answer for the gate seat to the saved `queen-args.json`, relaunches the same queen.js with `resumeFromRunId` and that `queen-args.json`, and runs `autopilot.mjs resumed`; that is the card's one resume, so a second gate timeout is ask. Main lands one card at a time: a flow card with `plan.mjs done --card <id>` then `plan.mjs land --card <id>`, a hand card with `plan.mjs land-card <id> --branch card/<id> --worktree <abs>`. After each landing main runs `board.mjs move`, the run-report skill with `cost.mjs <wf_id> --report <per-card report path>`, which is `.doug/.state/reports/<id>/last-report.json` for a flow card and `.doug/.state/reports/<id>/card-report.json` for a hand card, and `autopilot.mjs lock-wait <id> --ms <n>`, where n is the `lockWaitMs` line that `plan.mjs land` or `land-card` printed, and the landing commit carries the card's plan `.doug/plans/<id>.json`. A resume never replans. When a queen's run needs the user, main asks while the other queens keep running. A cap reached launches no new queen and lets the in-flight ones finish. In manual or accept-edits mode every queen launch prompts, and the Workflow allow rule or "Yes, and don't ask again" runs n queens unattended (docs/research/parallel-cards-research.md section 4). A card tagged swarm (or `swarm: true`, or whose goal says to run it as a swarm) and a decision card run alone, as today, after the in-flight queens finish; `/doug-next <id>` by hand is unchanged.
|
|
108
112
|
|
|
109
113
|
The lead keeps the run's state file, `.doug/.state/autopilot.json` (uncommitted), current through `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs"`, which the live pane and the board's Autopilot panel (`doug board serve`) read:
|
|
110
114
|
|
|
115
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" clear` first when `--auto` is invoked, before the queue approval question, so an ended or inactive run drops from the pane and the board's panel; a running run is kept.
|
|
111
116
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" start --cards <id,id,...> --usd-cap <n> --wall-cap <min>` at the queue approval.
|
|
112
117
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" add <id> [--usd-cap <n>] [--wall-cap <min>]` for each card unlimited autopilot takes from Ready later, with the grown caps.
|
|
113
118
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" card <id>` when a card that main runs itself starts.
|
|
@@ -115,7 +120,9 @@ The lead keeps the run's state file, `.doug/.state/autopilot.json` (uncommitted)
|
|
|
115
120
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" land <id> --commit <sha>` when the card lands, or `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" stop <id> --reason "<why>"` when it stops.
|
|
116
121
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" filed <id> --kind <follow-up|user> [--from <card>] --position "<text>"` for each follow-up or user card filed to Backlog.
|
|
117
122
|
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" caps --usd <n> --wall <min> --context <pct>` after each cap check on a queen notification.
|
|
118
|
-
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" end` at the closing summary.
|
|
123
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" end` at the closing summary; it refuses while a card of the run is In flow and was neither resumed nor answered, naming each such card.
|
|
124
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" rerun-alone <id>` for a gate-timeout stop that report printed as rerun alone.
|
|
125
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" leave <id> --reason "<the user's answer>"` when the user chooses to leave a card In flow.
|
|
119
126
|
|
|
120
127
|
A write that fails never stops the run. The live pane (a Claude Code pane, drawn by the doug-flow plugin's hooks module) opens on its own when the run starts and `/doug-autopilot` reopens it; the same run shows on the board page; with no run the pane shows a "No autopilot run." line and the board shows no panel; a finished run shows its last summary.
|
|
121
128
|
|
|
@@ -123,6 +130,7 @@ Stop conditions, one per line:
|
|
|
123
130
|
|
|
124
131
|
- It stops and asks when a card needs a design decision its goal does not answer.
|
|
125
132
|
- It stops and asks when the gate is red twice on one card.
|
|
133
|
+
- It stops and asks when a gate seat times out with no other queen in flight, or times out again on its rerun alone.
|
|
126
134
|
- It stops and asks when a hook blocks a step.
|
|
127
135
|
- It stops and asks when a reviewer blocker stays open after one fix pass.
|
|
128
136
|
- It stops and asks when a cap is reached.
|
|
@@ -136,6 +144,7 @@ It ends with the summary below, one condition per line:
|
|
|
136
144
|
- It ends when Ready has no runnable card, under unlimited autopilot.
|
|
137
145
|
- It ends when the user says stop: the in-flight cards are finished or left In flow.
|
|
138
146
|
- It ends when the lead's context is at or above autopilot.contextThreshold in .doug/config.json (default 60): on every queen notification the lead runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/context.mjs"`, which prints the context percentage of the lead's own session transcript, and treats a run that prints no reading as at the threshold.
|
|
147
|
+
- Before it ends, every card of the run still In flow is resumed once, alone, from its saved `queen-args.json` (after `autopilot.mjs rerun-alone <id>` unless it stopped agent-null); only when that resume also stops does it ask, and the user's choice to leave a card In flow is recorded with `autopilot.mjs leave <id> --reason "<the user's answer>"`.
|
|
139
148
|
- Before that summary it refreshes the anchor handoff: a `## Autopilot handoff` section in .doug/anchor.md naming the landed cards, any card left In flow, and that a new session resumes with /doug-next --auto.
|
|
140
149
|
|
|
141
150
|
An edge case or input the goal does not name is not a design decision: the lead takes the conservative behaviour, rejecting the input or, where rejecting is not possible, preserving it, never guessing, has whoever writes the test pin it, and names it in the card's landing note and the closing summary. A design decision is a choice, for inputs the goal names, between behaviours the goal's acceptance would both satisfy and a user would notice, or one that changes another card's or module's contract.
|
|
@@ -19,7 +19,8 @@ The Walking skeleton writes no phase file; it is done when the `walking-skeleton
|
|
|
19
19
|
|
|
20
20
|
- Only a committed phase file counts as done: a phase file that exists but is uncommitted means that phase is still waiting at its gate, so the next run re-asks that gate, showing the file, and does not move on.
|
|
21
21
|
- A phase file is written by the lead (this session). The spec-writer and researcher agents return text and write nothing.
|
|
22
|
-
- Before writing a phase, check whether its file exists. If it does, write the new text to `.doug/.state/scratch/<name>.new`, show `git diff --no-index` of the two, and ask (AskUserQuestion: replace / keep / revise). Never overwrite a phase file without that answer.
|
|
22
|
+
- Before writing a phase, check whether its file exists. If it does, write the new text to `.doug/.state/scratch/<name>.new`, show `git diff --no-index` of the two in the chat, in a fenced code block, before the AskUserQuestion, and ask (AskUserQuestion: replace / keep / revise). Never overwrite a phase file without that answer.
|
|
23
|
+
- Show the file first: before every gate that asks about a phase file, put the file's complete text in the chat, verbatim, in a fenced code block, before the AskUserQuestion. Never replace it with a summary or a path, and never truncate it (a very long file may go over more than one message). After the block the lead may add a few lines (assumptions, open questions); the questions themselves go in the AskUserQuestion.
|
|
23
24
|
- The gate after each phase asks AskUserQuestion with the options approve / revise / stop. Approve: commit the phase file (a plain message, no trailers) and go to the next phase. Revise: take the user's note, redraft, show the diff, ask again. Stop: leave the file uncommitted and end; a later `/doug-start` resumes at the first missing phase file.
|
|
24
25
|
- At every gate the lead calls AskUserQuestion, even when the session looks non-interactive; a denied or unanswered call ends the phase there, and the lead does nothing past it (it names the phase that is waiting).
|
|
25
26
|
|
|
@@ -31,7 +32,7 @@ Start here: if `.doug/config.json` is missing, run `doug init --yes` first. It w
|
|
|
31
32
|
|
|
32
33
|
Writes `docs/product/idea.md` (problem, users, success, non-goals). Spawn the spec-writer with the user's idea and ask it for those four sections:
|
|
33
34
|
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<the user's idea verbatim>. Draft docs/product/idea.md with sections: problem, users, success, non-goals. Return the file text." })`
|
|
34
|
-
Write the file, then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
35
|
+
Write the file, show the file first (Phase rules), then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
35
36
|
|
|
36
37
|
### 2. Architecture
|
|
37
38
|
|
|
@@ -39,7 +40,7 @@ Writes `docs/product/architecture.md` plus the ADR `docs/decisions/0001-stack.md
|
|
|
39
40
|
`Agent({ subagent_type: "doug-flow:researcher", prompt: "<one stack question>. Cite each fact with its source." })`
|
|
40
41
|
Then spawn the architect on the `plan` row's model (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models`; omit `model` when it says `inherit`):
|
|
41
42
|
`Agent({ subagent_type: "architect", model: "<the plan row's model>", prompt: "<the approved idea and the research>. Return the text of docs/product/architecture.md and, separately, the body of the stack ADR without its title line." })`
|
|
42
|
-
Write `docs/product/architecture.md` from its returned text. `docs/decisions/` is proposal-only, so the lead never Writes it directly. Save the ADR body under `.doug/.state/scratch/` and run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" decision propose --title "Stack" --file <body>`, which renders the diff for `docs/decisions/0001-stack.md` under `.doug/.state/learn/` and writes nothing else.
|
|
43
|
+
Write `docs/product/architecture.md` from its returned text. `docs/decisions/` is proposal-only, so the lead never Writes it directly. Save the ADR body under `.doug/.state/scratch/` and run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" decision propose --title "Stack" --file <body>`, which renders the diff for `docs/decisions/0001-stack.md` under `.doug/.state/learn/` and writes nothing else. Show the file first (Phase rules) for `architecture.md` and for the proposal's ADR diff, then ask at the AskUserQuestion gate (approve / revise / stop). On approve, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" apply <proposal-file>`, which writes the ADR, and commit. Unapproved, the proposal stays pending and the ADR does not exist.
|
|
43
44
|
|
|
44
45
|
### 3. Walking skeleton
|
|
45
46
|
|
|
@@ -49,7 +50,7 @@ Writes the first card with `doug board add` (id `walking-skeleton`, size S, clas
|
|
|
49
50
|
|
|
50
51
|
Writes `docs/product/scope.md` (v1 in one paragraph, then one line per card) and the board cards. Spawn the spec-writer with the approved idea and architecture; it returns the text of `scope.md` and the card list as one line per card (id, title, goal, size, class, column):
|
|
51
52
|
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<idea and architecture paths>. Draft docs/product/scope.md and its card list: id, title, goal, size, class, column. Return the file text." })`
|
|
52
|
-
The lead never drafts the scope; it writes the returned `scope.md` and runs `doug board add` only for each card the user approved. A card counts as approved at the gate, or in advance in the conversation. Cards are written with `doug board add`, never by a script editing `.doug/board.json`. Put at most 7 to Ready for v1 and the rest to Backlog, each with a size; a large card then goes through feature mode (`/doug-start <card-id>`). Show the board (`doug board list`) and ask at the AskUserQuestion gate (approve / revise / stop).
|
|
53
|
+
The lead never drafts the scope; it writes the returned `scope.md` and runs `doug board add` only for each card the user approved. A card counts as approved at the gate, or in advance in the conversation. Cards are written with `doug board add`, never by a script editing `.doug/board.json`. Put at most 7 to Ready for v1 and the rest to Backlog, each with a size; a large card then goes through feature mode (`/doug-start <card-id>`). Show the file first (Phase rules) for `scope.md`, show the board (`doug board list`) and ask at the AskUserQuestion gate (approve / revise / stop).
|
|
53
54
|
|
|
54
55
|
## Feature mode
|
|
55
56
|
|
|
@@ -59,21 +60,21 @@ Files live under `docs/specs/<card-id>/`. A goal with no card id gets a short ke
|
|
|
59
60
|
|
|
60
61
|
Writes `spec.md`: what must be true when the work is done, as outcomes a command can check, plus non-goals and open questions.
|
|
61
62
|
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<card goal or the user's goal verbatim>. Draft docs/specs/<card-id>/spec.md: outcomes, non-goals, open questions. Return the file text." })`
|
|
62
|
-
Write the file, then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
63
|
+
Write the file, show the file first (Phase rules), then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
63
64
|
|
|
64
65
|
### 2. Pseudocode (optional)
|
|
65
66
|
|
|
66
|
-
Writes `pseudocode.md`, only when the user asks for it at this gate (AskUserQuestion: write it / skip). Skip leaves no file and goes to Architecture; a missing `pseudocode.md` counts as skipped once `architecture.md` exists, or when the user said they skipped it; resuming with only `spec.md` asks write / skip once. When written, spawn the spec-writer again for the algorithm in plain steps, and gate it as above.
|
|
67
|
+
Writes `pseudocode.md`, only when the user asks for it at this gate (AskUserQuestion: write it / skip). Skip leaves no file and goes to Architecture; a missing `pseudocode.md` counts as skipped once `architecture.md` exists, or when the user said they skipped it; resuming with only `spec.md` asks write / skip once. When written, spawn the spec-writer again for the algorithm in plain steps, and gate it as above: show the file first (Phase rules).
|
|
67
68
|
|
|
68
69
|
### 3. Architecture
|
|
69
70
|
|
|
70
|
-
Writes `architecture.md` in the shape of the architect agent: modules, files, interfaces, the test for each. Every file it names will be owned by a plan task, so name each exactly. Spawn the project's `architect` agent on the `plan` row's model with `spec.md` (and `pseudocode.md` if present) and the code it touches: `Agent({ subagent_type: "architect", model: "<the plan row's model>", prompt: "<the spec path>. Return the text of docs/specs/<card-id>/architecture.md." })`. The lead writes its returned text to the file and never drafts it.
|
|
71
|
+
Writes `architecture.md` in the shape of the architect agent: modules, files, interfaces, the test for each. Every file it names will be owned by a plan task, so name each exactly. Spawn the project's `architect` agent on the `plan` row's model with `spec.md` (and `pseudocode.md` if present) and the code it touches: `Agent({ subagent_type: "architect", model: "<the plan row's model>", prompt: "<the spec path>. Return the text of docs/specs/<card-id>/architecture.md." })`. The lead writes its returned text to the file and never drafts it. Show the file first (Phase rules), then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
71
72
|
|
|
72
73
|
### 4. Refinement
|
|
73
74
|
|
|
74
75
|
The planner writes an approvable plan whose tasks own the files `architecture.md` names. Read the `plan` row with `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` and pass its model (omit `model` when it says `inherit`):
|
|
75
76
|
`Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "<card goal>. Spec: docs/specs/<card-id>/spec.md. Architecture: docs/specs/<card-id>/architecture.md. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json as a draft and return the rendered plan." })`
|
|
76
|
-
Set the card when the argument was a board card id (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <card-id>`), render it (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show`) and ask at the AskUserQuestion gate (approve / revise / stop). The plan is approved with `/doug-approve`, by the user; this skill never approves it.
|
|
77
|
+
Set the card when the argument was a board card id (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <card-id>`), render it (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show`), show the file first (Phase rules), meaning the plan's complete `show` output in the chat, and ask at the AskUserQuestion gate (approve / revise / stop). The plan is approved with `/doug-approve`, by the user; this skill never approves it.
|
|
77
78
|
|
|
78
79
|
### 5. Completion
|
|
79
80
|
|
|
@@ -22,7 +22,7 @@ Put the card's title and goal (or the goal text) in the chat verbatim, in a fenc
|
|
|
22
22
|
|
|
23
23
|
## 2. Plan on the plan row
|
|
24
24
|
|
|
25
|
-
The planner runs on the `plan` row, the same row `doug-plan` uses, not the `lead` row (the `lead` row is the swarm's own lead agent inside the workflow, a different job). Read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`). Spawn the planner on it: `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<title>\": <goal>. This plan runs as a swarm: set \"swarm\": true in the plan file, and size every task M or L unless it is truly one bounded change (a size-S task keeps a single implementer). Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })` (for a goal, the prompt is the goal text with the same swarm sentence); omit `model` when the row says `inherit`. The planner's rules apply unchanged: disjoint file ownership per task, a verify command per task, acceptance criteria with commands, no tier set on its own.
|
|
25
|
+
The planner runs on the `plan` row, the same row `doug-plan` uses, not the `lead` row (the `lead` row is the swarm's own lead agent inside the workflow, a different job). Read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`). Spawn the planner on it: `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<title>\": <goal>. This plan runs as a swarm: set \"swarm\": true in the plan file, and size every task M or L unless it is truly one bounded change (a size-S task keeps a single implementer). The shape gate keeps a task off the lead unless it owns two or more source files (not counting its tests), and a size-S task never reaches the lead, so give each M or L task two or more source files. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })` (for a goal, the prompt is the goal text with the same swarm sentence); omit `model` when the row says `inherit`. The planner's rules apply unchanged: disjoint file ownership per task, a verify command per task, acceptance criteria with commands, no tier set on its own.
|
|
26
26
|
|
|
27
27
|
Then guarantee the opt-in and the card (`plan.mjs set swarm on`): `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set swarm on`, and `plan.mjs set card <id>` for a card. `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show` and put its complete output in the chat verbatim, in a fenced code block, before anything else; it prints `Swarm: on`. Name any task the planner sized S (it will not swarm) and any assumption it stated.
|
|
28
28
|
|
|
@@ -33,7 +33,7 @@ Write the findings to `.doug/.state/research/<card-id>.md` (session state, ignor
|
|
|
33
33
|
|
|
34
34
|
A fact the design depends on is marked verified only when it is quoted verbatim from the raw source with its line number, fetched and searched (for example `curl -sL <url>` then `grep -n`) by the plugin's researcher agent or by a claude-code-guide asked for exact quotes; a guide answer that paraphrases, or cannot give the line, stays `unverified` in the note. The confirming fetch counts against the same research.maxFetches budget. On 2026-10-01 a guide answer quoted a sentence the raw sub-agents page does not contain, and only the grep caught it.
|
|
35
35
|
|
|
36
|
-
The note is written under the card's id, not the question's, because the id is also the promotion key: a note for a card that lands is promoted to `docs/research/<card-id>.md`, `board.mjs record` copying it there when it records the landing so a commit that cites the note has something to read afterward. A note for a card that never lands (dropped, replanned under another id) stays in state and is eventually swept with the rest of `.doug/.state/`.
|
|
36
|
+
The note is written under the card's id, not the question's, because the id is also the promotion key: a note for a card that lands is promoted to `docs/research/<card-id>.md`, `board.mjs record` copying it there when it records the landing so a commit that cites the note has something to read afterward. A queen run's inline note rides the queen report as `researchNote`; `autopilot.mjs report` writes it to `.doug/.state/research/<card-id>.md` so record promotes it as for a by-hand card. A note for a card that never lands (dropped, replanned under another id) stays in state and is eventually swept with the rest of `.doug/.state/`.
|
|
37
37
|
|
|
38
38
|
Then hand it on:
|
|
39
39
|
|
|
@@ -14,7 +14,7 @@ Every `board.mjs record` and `board.mjs summary` takes a required `--summary "<o
|
|
|
14
14
|
|
|
15
15
|
With the card id, the report (`.doug/.state/last-report.json` unless another path is given), the run id the Workflow tool printed (`wf_...`), the wall clock, and the landing commit:
|
|
16
16
|
|
|
17
|
-
1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" <run-id>`: the run's real cost, summed from the local Claude Code transcripts of every agent in the run and priced from the dated table in the script (no network). Its last lines are the run total and any agent it could not price. The number after `Run total:` is the cost; when the run cannot be found or every agent is unpriced, there is no cost. The workflow itself does not report cost; never estimate one. The Codex line after it, when present, is the Codex adversary's cost: pass its USD as --codex-cost <usd> to record and summary; it is never added to --cost.
|
|
17
|
+
1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" <run-id> --report <report.json>`: the run's real cost, summed from the local Claude Code transcripts of every agent in the run and priced from the dated table in the script (no network). Its last lines are the run total and any agent it could not price. The number after `Run total:` is the cost; when the run cannot be found or every agent is unpriced, there is no cost. The workflow itself does not report cost; never estimate one. The Codex line after it, when present, is the Codex adversary's cost: pass its USD as --codex-cost <usd> to record and summary; it is never added to --cost.
|
|
18
18
|
2. Classify every adversary block in the report. A block is a finding the adversary raised as a blocker (its ledger id, `F1`; for a report from before the ledger, `pass-<n>`); `board.mjs record` refuses an id the report does not have and lists the ones it has. This is judgment work and runs on the `review` row: read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.review.model`, `roles.review.effort`), and spawn `Agent({ subagent_type: "doug-flow:reviewer", model: "<review row model>", prompt: "There is no worktree and no diff for this job: read the report at <report path> (repository root, read-only) and classify every adversary block in it as real (a defect a user would hit), marginal (true to the spec, no user impact), or false (wrong), each with a one-line reason drawn from the fix passes and the checks in the report, not from the adversary's summary. Return exactly one line per block, nothing else: F1=<real|marginal|false>: <one-line reason> (pass-<n>=... for a pre-ledger report). Edit nothing." })`. The lead passes each returned line into `--adversary` verbatim and never reclassifies.
|
|
19
19
|
3. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json> --run <run-id> --wall "<m> min" --commit <sha> --cost <usd> --summary "<one sentence>" [--codex-cost <usd>] --adversary "F1=<class>: <reason>"` with `--adversary` repeated once per block, leaving `--cost` out when step 1 gave no number and `--codex-cost` out when step 1 showed no Codex line. This appends the entry to `docs/live-runs.md`: the measures table with the precision line (n real / n marginal / n false), the per-level, per-task table, and one line per classified block. It also promotes `.doug/.state/research/<id>.md` to `docs/research/<id>.md` when that note exists, printing one line saying so; add that path to the landing commit alongside `docs/live-runs.md`.
|
|
20
20
|
4. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" import`: rolls any auto-memory file written since the last import into the lessons store — it is idempotent, a rewritten file supersedes its old row, and it never writes under the auto-memory directory. Its one-line output goes in the chat; an exit 1 naming Node is said in the chat and the landing goes on, the same as record below.
|
|
@@ -28,7 +28,7 @@ A run that stopped is recorded the same way, without `--commit`; the summary's t
|
|
|
28
28
|
|
|
29
29
|
When `.doug/plan.json` carries `cards` (made by `plan.mjs merge`), the run is recorded once per card:
|
|
30
30
|
|
|
31
|
-
1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" <run-id> --by-card`: after the usual tables, a per-card table with each card's tasks' agents summed for that card and one row for the shared integration agents (the integrate and level-adversary agents, task `level-<n>`). The card rows are each card's `--cost`; the shared row is `--shared-cost`, the same number for every card. Pass the Codex line's USD as --codex-cost to the first card's record only.
|
|
31
|
+
1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" <run-id> --report <report.json> --by-card`: after the usual tables, a per-card table with each card's tasks' agents summed for that card and one row for the shared integration agents (the integrate and level-adversary agents, task `level-<n>`). The card rows are each card's `--cost`; the shared row is `--shared-cost`, the same number for every card. Pass the Codex line's USD as --codex-cost to the first card's record only.
|
|
32
32
|
2. Classify the adversary blocks per card: `board.mjs record` lists only that card's tasks' blocks and refuses the others' ids.
|
|
33
33
|
3. For each card, in the plan's `cards` order: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json> --run <run-id> --wall "<m> min" --commit <sha> --summary "<one sentence>" --cost <that card's usd> --shared-cost <shared usd> [--codex-cost <usd>, first card only] --adversary ...`. Record reads the batch from the plan file: the entry names the other cards, shows only that card's tasks and the integration rows of their levels, and counts the shared cost once, under the first card.
|
|
34
34
|
4. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" import` once for the whole report, the same as the workflow-run form's step 4: its one-line output goes in the chat, an exit 1 naming Node is said in the chat and the landing goes on, and it is idempotent, a rewritten file supersedes its old row, and it never writes under the auto-memory directory.
|
|
@@ -43,7 +43,7 @@ With the card id, the landing commit, the wall clock, and the gate line — one
|
|
|
43
43
|
1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>" --summary "<one sentence>" --cost <usd>`, with `--note "<one line>"` when something about the build is worth keeping. `<usd>` is the card's measured cost, never an estimate: `node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --session <session> --since <openedAt>` prices the lead session and its subagents from the card's `openedAt` (in `.doug/.state/reports/<id>/condition.json`) to now, where `<session>` is `CLAUDE_CODE_SESSION_ID` (not the card id); take the session total's dollar figure. When nothing was measured (no condition.json, no session id, or cost.mjs exits non-zero), leave `--cost` out: the entry then has no Cost row, never a `$0`, and you never estimate one. This appends the hand-track entry to `docs/live-runs.md`; its table is the summary. It also promotes `.doug/.state/research/<id>.md` to `docs/research/<id>.md` when that note exists, printing one line saying so; add that path to the landing commit alongside `docs/live-runs.md`.
|
|
44
44
|
2. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" import`, the same as above: its one-line output goes in the chat, an exit 1 naming Node is said in the chat and the landing goes on, and it is idempotent, supersedes a rewritten file, and never writes under the auto-memory directory.
|
|
45
45
|
3. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>" [--cost <usd>] [--note "<one line>"]`, the same values step 1 used, leaving `--cost` out when step 1 did. It records one row in the local outcome log `.doug/.state/memory/memory.db` (uncommitted), keyed `hand:<sha>` so re-recording the same commit updates that row and track `hand`; a card that lands twice, in two commits, gets two rows. Its workflow-only columns (verified, review counts, adversary verdict, blocks, fix passes, budget, tokens, the report itself) are null because they do not exist on this track, never because they measured zero: no independent verifier ran, and the gate's own text is the `gate` column instead. The USD column is the measured `--cost`, null when none was given. It needs Node >=22.16 (node:sqlite). When it exits 1 naming Node, say so in the chat and go on, since the record is a log, not a gate.
|
|
46
|
-
4. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" reflect --hand <id> --commit <sha> --rounds <n> [--review "<key>=<class>: <reason>"]... (the key is "`R<n>` or `<card>/R<n>`") [--harmful <id>]... [--note "<one line>"]`. It reads the briefs, reviewer reports and lessons.json doug-hand saved, applies helpful counters to the card's memoryUsed. A real block no longer bumps harmful; pass `--harmful <id>` once per recalled lesson the lead judges actually misled the work (it must be in a task's `memoryUsed`, else exit 1). Give `--harmful` on the report's first reflect: a rerun is "Already reflected" and ignores it. It proposes at most three lessons on the cheap row, filtered in code. Its one-line summary (with the measured cost) goes in the chat. An exit 1 naming the LLM pass is said in the chat and the landing goes on regardless — reflect is a log, not a gate, the same as record.
|
|
46
|
+
4. `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" reflect --hand <id> --commit <sha> --rounds <n> [--review "<key>=<class>: <reason>"]... (the key is "`R<n>` or `<card>/R<n>`") [--harmful <id>]... [--note "<one line>"]`. It reads the briefs, reviewer reports and lessons.json doug-hand saved, applies helpful counters to the card's memoryUsed (only lessons fetched with `lesson show <id> --card <id>`). A real block no longer bumps harmful; pass `--harmful <id>` once per recalled lesson the lead judges actually misled the work (it must be in a task's `memoryUsed`, else exit 1). Give `--harmful` on the report's first reflect: a rerun is "Already reflected" and ignores it. It proposes at most three lessons on the cheap row, filtered in code. Its one-line summary (with the measured cost) goes in the chat. An exit 1 naming the LLM pass is said in the chat and the landing goes on regardless — reflect is a log, not a gate, the same as record.
|
|
47
47
|
|
|
48
48
|
Say the one-sentence summary record printed first, then the commit, the wall clock, and the gate line in the chat. In the hand form the landing block is printed by `board.mjs record` (both forms) and by `board.mjs summary --card` as their last three lines (Changed, Outcome, Learned; at most 100 words, `--note` feeds Learned); plain `summary` (no `--card`) prints no block. Your final message carries it verbatim, and the Stop gate blocks until it does.
|
|
49
49
|
|