@dougharness/cli 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/bin.js +18 -3
  2. package/dist/bin.js.map +1 -1
  3. package/dist/board-serve.js +14 -7
  4. package/dist/board-serve.js.map +1 -1
  5. package/dist/board.d.ts +1 -1
  6. package/dist/board.js +42 -9
  7. package/dist/board.js.map +1 -1
  8. package/dist/detect/repo.js +14 -1
  9. package/dist/detect/repo.js.map +1 -1
  10. package/dist/detect/types.d.ts +2 -0
  11. package/dist/doctor.d.ts +46 -0
  12. package/dist/doctor.js +491 -0
  13. package/dist/doctor.js.map +1 -0
  14. package/dist/generate/agents.d.ts +7 -0
  15. package/dist/generate/agents.js +62 -12
  16. package/dist/generate/agents.js.map +1 -1
  17. package/dist/generate/claude-md.js +4 -0
  18. package/dist/generate/claude-md.js.map +1 -1
  19. package/dist/generate/config.d.ts +8 -0
  20. package/dist/generate/config.js +4 -1
  21. package/dist/generate/config.js.map +1 -1
  22. package/dist/generate/proposal.js +15 -6
  23. package/dist/generate/proposal.js.map +1 -1
  24. package/dist/generate/settings.d.ts +2 -0
  25. package/dist/generate/settings.js +17 -2
  26. package/dist/generate/settings.js.map +1 -1
  27. package/dist/init.d.ts +14 -0
  28. package/dist/init.js +94 -8
  29. package/dist/init.js.map +1 -1
  30. package/dist/memory-setup.d.ts +28 -0
  31. package/dist/memory-setup.js +326 -0
  32. package/dist/memory-setup.js.map +1 -0
  33. package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +16 -4
  34. package/node_modules/@dougharness/flow/agents/lead.md +1 -1
  35. package/node_modules/@dougharness/flow/agents/planner.md +3 -3
  36. package/node_modules/@dougharness/flow/agents/researcher.md +2 -2
  37. package/node_modules/@dougharness/flow/hooks/hooks.json +16 -0
  38. package/node_modules/@dougharness/flow/lib/board.mjs +171 -13
  39. package/node_modules/@dougharness/flow/lib/code-index.mjs +18 -3
  40. package/node_modules/@dougharness/flow/lib/context.mjs +57 -0
  41. package/node_modules/@dougharness/flow/lib/cost.mjs +155 -7
  42. package/node_modules/@dougharness/flow/lib/embeddings.mjs +188 -24
  43. package/node_modules/@dougharness/flow/lib/land.mjs +74 -10
  44. package/node_modules/@dougharness/flow/lib/learn.mjs +4 -4
  45. package/node_modules/@dougharness/flow/lib/memory.mjs +368 -50
  46. package/node_modules/@dougharness/flow/lib/plan.mjs +11 -1
  47. package/node_modules/@dougharness/flow/package.json +2 -1
  48. package/node_modules/@dougharness/flow/scripts/board.mjs +86 -25
  49. package/node_modules/@dougharness/flow/scripts/context.mjs +50 -0
  50. package/node_modules/@dougharness/flow/scripts/cost.mjs +15 -3
  51. package/node_modules/@dougharness/flow/scripts/memory.mjs +197 -37
  52. package/node_modules/@dougharness/flow/scripts/plan.mjs +54 -64
  53. package/node_modules/@dougharness/flow/scripts/session-recall.mjs +132 -0
  54. package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +105 -0
  55. package/node_modules/@dougharness/flow/skills/doug-learn/SKILL.md +1 -1
  56. package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +50 -14
  57. package/node_modules/@dougharness/flow/skills/doug-plan/SKILL.md +1 -1
  58. package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +7 -7
  59. package/node_modules/@dougharness/flow/skills/research/SKILL.md +4 -2
  60. package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +14 -13
  61. package/node_modules/@dougharness/flow/skills/swarm-launch/SKILL.md +1 -1
  62. package/node_modules/@dougharness/flow/workflows/doug-implement.js +145 -86
  63. package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
  64. package/node_modules/@dougharness/gates/hooks/hooks.json +11 -0
  65. package/node_modules/@dougharness/gates/lib/baseline.mjs +166 -1
  66. package/node_modules/@dougharness/gates/lib/bash-rules.mjs +21 -14
  67. package/node_modules/@dougharness/gates/lib/block-ledger.mjs +58 -1
  68. package/node_modules/@dougharness/gates/lib/checkpoint.mjs +1 -1
  69. package/node_modules/@dougharness/gates/lib/config.mjs +20 -0
  70. package/node_modules/@dougharness/gates/lib/docs-gate.mjs +95 -0
  71. package/node_modules/@dougharness/gates/lib/evidence.mjs +2 -0
  72. package/node_modules/@dougharness/gates/lib/gate-lock.mjs +94 -0
  73. package/node_modules/@dougharness/gates/lib/run.mjs +4 -0
  74. package/node_modules/@dougharness/gates/lib/test-totals.mjs +40 -7
  75. package/node_modules/@dougharness/gates/lib/trace.mjs +24 -7
  76. package/node_modules/@dougharness/gates/lib/verified-tree.mjs +225 -0
  77. package/node_modules/@dougharness/gates/package.json +1 -1
  78. package/node_modules/@dougharness/gates/scripts/research-cap.mjs +133 -5
  79. package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +558 -197
  80. package/node_modules/@dougharness/gates/scripts/trace.mjs +5 -1
  81. package/node_modules/@dougharness/gates/scripts/turn-start.mjs +17 -0
  82. package/node_modules/@dougharness/gates/scripts/verify.mjs +201 -0
  83. package/package.json +4 -4
  84. package/templates/board-app.js +4 -3
  85. package/node_modules/@dougharness/flow/lib/rehearse.mjs +0 -825
  86. package/node_modules/@dougharness/flow/scripts/rehearse.mjs +0 -129
  87. package/node_modules/@dougharness/flow/skills/core-next/SKILL.md +0 -92
  88. package/node_modules/@dougharness/flow/skills/harness-fix/SKILL.md +0 -85
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook (card memory-every-session, brief A): puts the repository's top lessons into a fresh
3
+ // session's context. Runs on source startup|clear only. Query = title and goal of the card(s) in the board's
4
+ // flow column, else the query-free top lessons by helpful minus harmful. Keyword recall first; when an
5
+ // embeddings provider is configured a hybrid recall races a 3000 ms deadline and wins if it lands. Never
6
+ // creates the memory database, never exits non-zero: any exception is one stderr line plus a systemMessage.
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+
10
+ const DEADLINE_MS = 3000;
11
+ const PROVIDER_TIMEOUT_MS = 1500;
12
+ const K = 5;
13
+
14
+ function readStdin(ms) {
15
+ return new Promise((resolve) => {
16
+ let data = "";
17
+ let done = false;
18
+ const finish = () => {
19
+ if (done) return;
20
+ done = true;
21
+ resolve(data);
22
+ };
23
+ const timer = setTimeout(finish, ms);
24
+ process.stdin.setEncoding("utf8");
25
+ process.stdin.on("data", (d) => (data += d));
26
+ process.stdin.on("end", () => {
27
+ clearTimeout(timer);
28
+ finish();
29
+ });
30
+ process.stdin.on("error", finish);
31
+ });
32
+ }
33
+
34
+ function parseInput(text) {
35
+ try {
36
+ const v = JSON.parse(text);
37
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
38
+ } catch {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ function optedOut(dir) {
44
+ if (process.env.DOUG_SESSION_RECALL === "0") return true;
45
+ try {
46
+ const raw = JSON.parse(readFileSync(join(dir, ".doug", "config.json"), "utf8"));
47
+ return !!(raw && raw.memory && raw.memory.sessionRecall === false);
48
+ } catch {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function write(obj) {
54
+ return new Promise((resolve) => process.stdout.write(JSON.stringify(obj) + "\n", resolve));
55
+ }
56
+
57
+ async function run() {
58
+ const input = parseInput(await readStdin(1000));
59
+ const dir = process.env.CLAUDE_PROJECT_DIR || (typeof input.cwd === "string" && input.cwd) || process.cwd();
60
+ const source = input.source;
61
+ if (source !== "startup" && source !== "clear") return;
62
+ if (optedOut(dir)) return;
63
+ // Checked before anything opens it: a missing database is never created here.
64
+ if (!existsSync(join(dir, ".doug", ".state", "memory", "memory.db"))) return;
65
+
66
+ const { openMemory, recallLessons, topLessons, renderLessons, degradedReason } = await import("../lib/memory.mjs");
67
+ const { readMemoryConfig, readMemoryStaleDays, createProvider } = await import("../lib/embeddings.mjs");
68
+ const { loadBoard } = await import("../lib/board.mjs");
69
+
70
+ let cards = [];
71
+ try {
72
+ cards = loadBoard(dir).cards.filter((c) => c.column === "flow");
73
+ } catch {
74
+ cards = [];
75
+ }
76
+ const staleDays = readMemoryStaleDays(dir);
77
+ const m = openMemory(dir);
78
+ let lessons;
79
+ let header;
80
+ let scope;
81
+ try {
82
+ if (cards.length) {
83
+ const query = cards.map((c) => `${c.title}\n${c.goal || ""}`).join("\n");
84
+ scope = `for ${cards.map((c) => c.id).join(", ")}`;
85
+ const provider = createProvider(readMemoryConfig(dir), { timeoutMs: PROVIDER_TIMEOUT_MS });
86
+ let result = await recallLessons(m, query, { k: K, checkoutDir: dir, staleDays, provider: null });
87
+ if (provider) {
88
+ let timer;
89
+ const deadline = new Promise((resolve) => {
90
+ timer = setTimeout(() => resolve(null), DEADLINE_MS);
91
+ });
92
+ const hybrid = recallLessons(m, query, { k: K, checkoutDir: dir, staleDays, provider });
93
+ hybrid.catch(() => {});
94
+ const won = await Promise.race([hybrid, deadline]);
95
+ clearTimeout(timer);
96
+ if (won) result = won;
97
+ else result = { ...result, mode: "keyword-only", reason: "embeddings timed out" };
98
+ }
99
+ header =
100
+ result.mode === "hybrid"
101
+ ? `recall: hybrid via ${result.provider} ${provider.model}/${provider.dims}`
102
+ : `recall: keyword-only (${degradedReason(result)})`;
103
+ lessons = result.lessons;
104
+ } else {
105
+ scope = "the repository: top by helpful-harmful";
106
+ header = null;
107
+ lessons = topLessons(m, { k: K, staleDays });
108
+ }
109
+ } finally {
110
+ // A raced-out hybrid recall may still be running; closing the handle makes it fail into its catch.
111
+ try {
112
+ m.close();
113
+ } catch {
114
+ // ignore
115
+ }
116
+ }
117
+ const rendered = renderLessons(lessons);
118
+ if (rendered.text === null) return;
119
+ const additionalContext =
120
+ `[doug] Lessons from this repository's memory (${header ? `${header}; ` : ""}${scope}):\n${rendered.text}\n` +
121
+ "Each line is [lesson id] text (citation). When you brief an agent, paste the lessons that apply and list their ids as memoryUsed.";
122
+ await write({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext } });
123
+ }
124
+
125
+ try {
126
+ await run();
127
+ } catch (err) {
128
+ const first = String((err && err.message) || err).split("\n")[0];
129
+ process.stderr.write(`[doug:session-recall] hook error, failing open: ${first}\n`);
130
+ await write({ systemMessage: `[doug:session-recall] hook error, failing open: ${first}` });
131
+ }
132
+ process.exit(0);
@@ -0,0 +1,105 @@
1
+ ---
2
+ name: doug-hand
3
+ description: Take the next hand-track board card (a gated by-hand change) start to finish, or several together as one swarmed batch (`/doug-hand <id> <id>...` or `--batch <n>`): show its goal, ask before starting, build it by hand or, for a size M or L card, through the doug-implement workflow as a swarm, with tests and the full gate, commit without trailers, record the landing in docs/live-runs.md and on the board, and ask before the next card. Either way the session model leads only; implement and review work run on the Models table rows, a swarm's workers on the worker row. Invoked by `/doug-next` for a hand-track card, or directly with `/doug-hand`; its own start gate still blocks any work.
4
+ allowed-tools: AskUserQuestion, Agent, Workflow, Read, Edit, Write, Grep, Glob, Bash
5
+ ---
6
+
7
+ # doug-hand
8
+
9
+ One hand-track card, start to finish, with two human gates: start and continue. A hand-track card is a gated by-hand change, picked by size and risk: built directly in the checkout with tests and the project's own gate, or, for a size M or L card the user chooses to swarm, through the doug-implement workflow under an approved plan (section 2b). Nothing starts before the user says so, and the loop never continues to another card without being asked (except under autopilot, /doug-next --auto).
10
+
11
+ `/doug-next <card-id>` hands a hand-track card to this loop; `/doug-hand` takes the first Ready hand-track card whose dependencies are Done, and `/doug-hand <card-id>` takes that card.
12
+
13
+ **Several cards in one swarmed batch.** `/doug-hand <id> <id>...` takes those hand-track cards together, and `/doug-hand --batch <n>` the first n runnable Ready hand-track cards (`board.mjs next --batch <n> --track hand`, board order). A batch runs only as a swarm: one planner per card in parallel writes `.doug/.state/drafts/<id>.json`, `plan.mjs merge` joins the drafts into one plan whose tasks carry their card, then one approval, one run, and one land, with each card keeping its own Done move and its own run entry. By-hand stays one card at a time: for the by-hand path a batch would only be step 6's loop with fewer gates, and the single gate run it would save is not worth collapsing n approvals for. Where the text below says "the card", read "each card of the batch" and follow the batch paragraphs marked as such.
14
+
15
+ Under autopilot (/doug-next --auto) the start gate, the shape question, and the continue gate are not asked; the queue approval stands for them, and the autopilot's stop conditions apply.
16
+
17
+ ## 1. Pick the card
18
+
19
+ Start from a clean working tree on the base branch (`git status --porcelain` empty apart from `.doug/` and `.claude/worktrees/`).
20
+
21
+ - With an id: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" card <id>`. If the card does not carry `"track": "hand"`, stop and say it is a flow-track card for `/doug-next <id>`.
22
+ - Without: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next --track hand`. Show the `waiting on` skipping lines it prints (those hand-track cards wait on a dependency); the `flow track` lines need no comment.
23
+ - Batch: with several ids, `board.mjs card <id>` for each. With `--batch <n>`, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next --batch <n> --track hand` prints the first n runnable Ready hand-track cards as a JSON array in board order (fewer than n when fewer are runnable; a batch left with one card, whether by a set-aside or by a drop, is a plain single-card run from here on). Show the `waiting on` skipping lines. Keep the ids in board order from here on.
24
+
25
+ Batch eligibility is checked here, on the card records alone, before the start gate and before any planner is spawned: a batch runs only as a swarm, so a card that step 2b would send by hand without asking (size S, or no size at all; or a goal naming a file the running workflow or hooks execute, as 2b lists them) cannot be in a batch. Such a card is set aside, not the whole batch: it stays Ready, untouched, and the start-gate question below names it and the reason and says `/doug-hand <id>` takes it alone by hand. If eligibility sets every card of the batch aside, say which and why, and stop: nothing moves.
26
+
27
+ Put the card's title and goal in the chat verbatim, in a fenced code block: the goal is the acceptance list for the work. Note the wall clock. Batch: put each remaining card's title and goal in the chat the same way.
28
+
29
+ If the goal depends on a fact outside the repository (a Claude Code hook contract, a CLI's output format, a third-party API), say so here: the research step runs after the start gate, before any test is written.
30
+
31
+ ## 2. Human gate: start
32
+
33
+ Ask with `AskUserQuestion`: start this card, skip to the next hand-track card, or stop. Starting is never automatic.
34
+
35
+ Batch: one `AskUserQuestion` names every remaining card of the batch (id, title, size) — and any card step 1 set aside, with its reason — with options Start the batch / Stop. One start gate for the whole batch: the batch's point is fewer gates, and naming every card in the one question keeps the drop-before-run ability the batch needs. The automatic "Other" answer is how the user drops cards: they type the ids to leave out, and the gate is asked again with the rest. A batch reduced to one card from there is a plain single-card run, through 2b's usual question.
36
+
37
+ - **Start**: give the card a `class` if it has none (`doug board edit <id> --class <tests-only|prose|gate-script|code|docs|eval|decision>`; `condition open` below refuses a card without one), then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> flow`. Leave the move uncommitted; the served page (`doug board serve`) shows it live. Then stamp the landing's condition before the brief: `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" condition open <id>` writes `.doug/.state/reports/<id>/condition.json`, which `memory.mjs record --hand` (run by the `run-report` skill at landing) reads to fill the outcome row's condition columns. Batch: one move per remaining card, and one `condition open` per card.
38
+ - **Skip**: go back to step 1 and take the next hand-track card after this one.
39
+ - **Stop**: end.
40
+
41
+ ## 2b. Shape: by hand, or a swarm
42
+
43
+ Size S, or a card with no size at all: by hand, no question.
44
+
45
+ Size M or L: by hand without asking when the goal names a file the running workflow or hooks execute: the workflow file (`workflows/doug-implement.js`), anything under `.doug/hooks/scripts/`, the plugin's agents, plus any list the project's CLAUDE.md, or a procedure it names, gives. The workflow cannot rewrite itself, its gate hooks, or its agents while it runs them, so those stay by hand.
46
+
47
+ Batch: a batch runs only as a swarm, so for two or more cards this step does not ask swarm-or-by-hand; it says so and goes straight to spawning one planner per card in the same message so they run in parallel, on the `plan` row — the same `plan.mjs models` row and prompt shape below (including its `Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts.` sentence), each told to write its draft to `.doug/.state/drafts/<id>.json` (not `.doug/plan.json`) and validate it with `plan.mjs validate --file .doug/.state/drafts/<id>.json`, each also told this plan runs as a swarm: set `"swarm": true` and size every task M or L unless it is truly one bounded change. When every planner has returned, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" merge <id> <id>...` joins the drafts into `.doug/plan.json` (its output in the chat verbatim), then `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set swarm on`. Never `plan.mjs set card` for a batch: merge's `cards` replaces it. Then `plan.mjs show` (it prints `Cards:` and each task's card) and the approval gate below; Stop on a batch moves every card back to Ready with `board.mjs move <id> ready` (mirroring `/doug-next` section 3's Stop).
48
+
49
+ 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.
50
+
51
+ 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`.
52
+
53
+ Then continue at step 4.
54
+
55
+ ## 3. Build it by hand
56
+
57
+ A dissolved batch (2b's approval gate went by hand after a failed run) builds only its first card here; the other cards of the batch already went back to Ready.
58
+
59
+ The session model is the lead and only the lead, on both tracks; it never implements, verifies, or reviews the change itself. The order is: research (when called for), the test file and the project's procedure, the brief, the tester, the coder, the reviewer, then the lead's own gate and commit; a lean-lane card has no tester.
60
+
61
+ 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.
62
+
63
+ 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.
64
+
65
+ On the lean lane and on a tests-only card, every brief carries the same `brief-lessons` block.
66
+
67
+ 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.
68
+
69
+ 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.
70
+
71
+ A tests-only card (its goal says tests only, no production change) runs as tester then reviewer, with no coder: the tester writes the test from the goal, runs the brief's mutation list itself in a scratch copy or git worktree under `.doug/.state`, and reports each result; the reviewer reruns the list and adds its own.
72
+
73
+ The lean lane: a size S card whose class is `prose` or `docs` runs as coder then reviewer, with no tester seat and no mutation list. One coder on the `implement` row writes the test and the change together, from the goal, and the reviewer reviews it as usual. The session model still never implements or reviews. On the lean lane the coder, not a tester, writes or extends the test file. The reviewer has no list to rerun, so it adds its own mutations only where the change turns out to be a mechanism.
74
+
75
+ A project with no `tester` agent (no `.claude/agents/tester.md`) has the coder write the failing test first, from the goal, before the change. The lead says so in the brief in place of the tester line, and a tests-only card runs as coder then reviewer.
76
+
77
+ Then spawn the implementation with the Agent tool on the `implement` row of the CLAUDE.md Models table (the project's `coder` agent with `model` set to that row's model, which executes the brief); the coder then makes them pass and may not edit a test except with a stated reason in its report; the reviewer checks that rule. Then a review on the `review` row (the project's `reviewer` agent); a swarm's workers run on the `worker` row. Verification on the hand track is the gate itself (the commands in `.doug/config.json` `stopGate.commands`), which the reviewer runs and the lead re-runs before the commit, says what ran, and records. Both run it through `node .doug/hooks/scripts/verify.mjs`, which credits a verified-tree record of the same tree instead of re-running the commands. A row that says `inherit` means the session model, and that is the user's choice in the table, not the lead's.
78
+
79
+ Before the commit the lead itself runs `node .doug/hooks/scripts/verify.mjs` (the commands in `.doug/config.json` `stopGate.commands`), records its last line as the gate line, and says what ran and the counts. The pre-commit hook always runs the commands itself, whatever a record says. Commit without attribution trailers (one commit per coherent change, with a message that says what changed and why), and leaves `.doug/plan.json` and `.doug/anchor.md` out unless the card is about them. If a hook blocks a step, report it; do not work around it.
80
+
81
+ 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.
82
+
83
+ ## 4. Record and land
84
+
85
+ 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):
86
+
87
+ 1. Invoke the `run-report` skill. By hand: its hand-track-landing form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> --hand --commit <sha> --wall "<m> min" --gate "<gate line>" --summary "<one sentence>"` (the gate line is run-report's own rule: one `<command> <exit>` clause per entry of `.doug/config.json` `stopGate.commands`, joined by `; `; for example `typecheck 0; test:unit 0 (<n> passed)`), and `--note "<one line>"` when something about the build is worth keeping (what was harder than the goal said, what was left out and why), Hand the form's step 4, `memory.mjs reflect --hand`, three more inputs; they go to that command only, never to `board.mjs record` or `memory.mjs record`, which reject them: `--rounds <n>` (coder rounds until the reviewer passed) and one `--review "R<n>=<real|marginal|false>: <reason>"` per reviewer blocker finding a later round fixed, classed by the lead, and, third, `--harmful <id>` once per recalled lesson the lead judges actually misled the work (a real block no longer bumps harmful). Swarmed: its workflow-run form, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json>` (the file 2b saved, `.doug/.state/last-report.json`) `--commit <merge sha> --wall "<m> min" --cost <usd> --codex-cost <usd> --summary "<one sentence>"` (add --codex-cost <usd> when cost.mjs printed a Codex line, next to the run total) with one `--adversary "<id>=<real|marginal|false>: <reason>"` per adversary block the report carries (run-report requires every block classified), then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --wall "<m> min" --commit <merge sha> --summary "<one sentence>"` and put its sentence, then its three lines, in the chat verbatim. Batch: the `run-report` skill's batch form — `cost.mjs <run-id> --by-card` for each card's cost with the shared integration agents counted once under the first, then one `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" record <id> <report.json> --commit <merge sha> --wall "<m> min" --cost <that card's usd> --shared-cost <shared usd> --summary "<one sentence>" --adversary ...` per card in the plan's `cards` order, then `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" summary <report.json> --card <id> --wall "<m> min" --commit <merge sha> --summary "<one sentence>"` per card with each card's sentence and three lines in the chat verbatim. A batch ends with one overall sentence, in plain words, of what the batch completed, after each card's own summary line.
88
+ 2. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> done --source "commit <sha>"`. Batch: once per card of the batch, same merge sha.
89
+ 3. `git add <record> docs/live-runs.md && git commit -m "Board: <id> done as <sha>"` (swarmed: add `.doug/plan.json` too); when step 1's `run-report` invocation printed that it promoted a research note, add `docs/research/<id>.md` too. No trailers. Batch: one commit `"Board: <id>, <id> done as <sha>"` including `.doug/plan.json`.
90
+
91
+ ## 5. Follow-up cards
92
+
93
+ Before the continue gate, gather the follow-up candidates this landing produced: the coder's and reviewer's "left out", "not closed", and "found on the way" items, the landing note, and any defect the reviewer marked out of scope. Suggestions come only from what the run actually reported, never invented; each carries the one-line reason and cites the pass that found it. A reviewer's out-of-scope finding is filed only when it is realistic drift (a form a model or a person would plausibly write) or when one card would close the whole class; otherwise it goes in the landing note and is not filed. The card's goal text carries both: the goal names the pass that found it (the reviewer's finding, the coder's report, the adversary block id) and the landed commit.
94
+
95
+ Follow-ups are filed without a question. A landing with no candidates files nothing. Each realistic candidate goes straight to Backlog with `doug board add` (never a script that edits the record): `doug board add <id> --title "<title>" --goal "<goal>" --component <c> --size <S|M|L> --class <tests-only|prose|gate-script|code|docs|eval|decision> --track <hand|flow> --deps <landed-id> --column backlog`. Then the lead places it in Backlog by priority with `doug board reorder`: `--top` for a defect that blocks a commit, a gate, or a user; `--after <id>` of the last such card for drift a user would hit; `--bottom` otherwise. Only the user promotes a card to Ready; the lead never files a follow-up to Ready or Decide.
96
+
97
+ The lead says in the chat what it filed and where: each new card's id, title, and its Backlog position with the reason. Then one commit `"Board: <id> follow-ups added: <new-id>, <new-id>"`, whatever the count; the served page (`doug board serve`) shows the new cards without a republish. Batch: each card's deps name the card of the batch it came from; still one commit for the whole batch.
98
+
99
+ ## Cards the user asks for
100
+
101
+ A card the user asks for is a mini ADR, not a one-line goal. Before filing it, the lead asks the scoping questions in one `AskUserQuestion` of up to 4 questions, each with a Recommended option, and writes the answers into the goal. The goal carries the fixed headings Context / Decision / Options / Questions; `docs/decisions` stays for real ADRs. Only a user-requested card takes this form: a follow-up the lead files keeps step 5's lighter form, so autopilot follow-ups stay question-free. A card the user asks for while autopilot runs is scoped at once with the same questions, filed to Backlog, and the queue resumes at the card it was on: the request is input, not a gate, and the running queue is unchanged.
102
+
103
+ ## 6. Human gate: continue
104
+
105
+ Ask with `AskUserQuestion` whether to take the next hand-track card. Yes: go to step 1 with no id. No: end.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: doug-learn
3
- description: Log-then-propose harness 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 repeated lesson, 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.
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 repeated lesson, 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
  ---
@@ -1,13 +1,13 @@
1
1
  ---
2
2
  name: doug-next
3
- description: Take the next board card (.doug/board.json), or several cards in one plan, through the whole flow in one command: plan it, stop for the user's approval, implement with the doug-implement workflow, replan a blocked run keeping the tasks that passed, land the integration branch, move the card to Done, log the run, and offer the next card. Only the user invokes this.
3
+ description: Take the next board card (.doug/board.json), or several cards in one plan, through the whole flow in one command (a hand-track card goes to the by-hand loop, the `doug-hand` skill; `--auto [n]` runs the queue of Ready cards on one approval): plan it, stop for the user's approval, implement with the doug-implement workflow, replan a blocked run keeping the tasks that passed, land the integration branch, move the card to Done, log the run, and offer the next card. Only the user invokes this.
4
4
  disable-model-invocation: true
5
5
  allowed-tools: Agent, AskUserQuestion, Workflow, Read, Bash(node *plan.mjs *), Bash(node *board.mjs *), Bash(node *cost.mjs *), Bash(git *), Bash(doug board *), Bash(node *bin.js board *)
6
6
  ---
7
7
 
8
8
  # doug-next
9
9
 
10
- One card, start to finish, with two human gates. Nothing is implemented before the user approves the plan, and the loop never continues to another card without being asked.
10
+ One card, start to finish, with two human gates. Nothing is implemented before the user approves the plan, and the loop never continues to another card without being asked (except under autopilot, below).
11
11
 
12
12
  `/doug-next` takes the first Ready card whose dependencies are Done. `/doug-next <card-id>` takes that card.
13
13
 
@@ -15,15 +15,15 @@ One card, start to finish, with two human gates. Nothing is implemented before t
15
15
 
16
16
  ## Who does the work
17
17
 
18
- The session model is the lead only, on both tracks (decision 0005 as amended 2026-09-07). In this loop it picks the card, asks the human gates, launches the workflow, reads the report, replans, lands, and records; it never plans, researches, implements, verifies, reviews, or classifies itself. Planning and research run on the `plan` row (the `doug-plan` and `research` skills), the workflow's stages on the `implement`, `verify`, `review`, `adversary`, and `integrate` rows and a swarm's on the `lead` and `worker` rows, and adversary-block classification on the `review` row (`run-report`). A row that says `inherit` is the session model by the user's choice in the table.
18
+ The session model is the lead only, on both tracks (a hand-track card runs the by-hand loop, `doug-hand`, with the same rule). In this loop it picks the card, asks the human gates, launches the workflow, reads the report, replans, lands, and records; it never plans, researches, implements, verifies, reviews, or classifies itself. Planning and research run on the `plan` row (the `doug-plan` and `research` skills), the workflow's stages on the `implement`, `verify`, `review`, `adversary`, and `integrate` rows and a swarm's on the `lead` and `worker` rows, and adversary-block classification on the `review` row (`run-report`). A row that says `inherit` is the session model by the user's choice in the table.
19
19
 
20
20
  ## 1. Pick the card
21
21
 
22
22
  Start from a clean working tree on the base branch (`git status --porcelain` empty apart from `.doug/` and `.claude/worktrees/`); land refuses otherwise, and the scope gate blocks uncommitted files the plan does not own.
23
23
 
24
- - With an id: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" card <id>`. If the card carries `"track": "hand"`, stop: it is harness work built directly in the checkout (decision 0005), not a flow run; say so and end.
25
- - Without: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next`. Show any "skipping" lines it prints; those Ready cards are waiting on a dependency.
26
- - Batch: with several ids, `board.mjs card <id>` for each (a hand-track card among them stops the run the same way); with `--batch <n>`, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next --batch <n>` prints the cards as a JSON array in board order (fewer than n when fewer are runnable; one card is a plain single-card run). Show the "skipping" lines. Keep the ids in board order from here on: the merge sequences overlapping files by it.
24
+ - With an id: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" card <id>`. If the card carries `"track": "hand"`, it is a gated by-hand change, not a flow run: invoke the `doug-hand` skill with the card id and end this loop; doug-hand asks its own start gate before anything moves.
25
+ - Without: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next --track any`. A plain /doug-next takes the next runnable Ready card of either track; a hand-track card goes to the doug-hand skill. Show any "skipping" lines it prints; those Ready cards are waiting on a dependency.
26
+ - Batch: with several ids, `board.mjs card <id>` for each (a hand-track card among them stops the run, saying `/doug-next <id>` takes it alone); with `--batch <n>`, `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" next --batch <n>` prints the cards as a JSON array in board order (fewer than n when fewer are runnable; one card is a plain single-card run). Show the "skipping" lines. Keep the ids in board order from here on: the merge sequences overlapping files by it.
27
27
 
28
28
  Move it: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> flow` prints `Moved <id> to flow in <record>.` (normally `<record>` is `.doug/board.json`; a repository that has not migrated prints its fallback). Batch: one move per card. Leave the move uncommitted: it is working state, the scope gate allows an uncommitted `.doug/board.json`, and land ignores `.doug/` changes; the served page (`doug board serve`, `--detach` to keep it running) shows the card In flow live. The record change lands in section 5's landing commit together with `docs/live-runs.md`.
29
29
 
@@ -36,19 +36,19 @@ If the card's goal depends on a fact outside the repository (a Claude Code hook
36
36
  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`).
37
37
 
38
38
  Spawn the planner with the card's goal text verbatim as the request:
39
- `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<card.title>\": <card.goal>. Write .doug/plan.json and return the rendered plan." })`
39
+ `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<card.title>\": <card.goal>. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })`
40
40
 
41
41
  Then record the card on the plan: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <id>` (the planner also writes it; setting it here is what guarantees the plan file carries the id even if the planner forgot).
42
42
 
43
43
  Batch: spawn one planner per card in the same message so they run in parallel, each told where its draft goes, each carrying the same plan row's model:
44
- `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<card.title>\": <card.goal>. Write the plan to .doug/.state/drafts/<id>.json (a draft for a batch; not .doug/plan.json), validate it with plan.mjs validate --file .doug/.state/drafts/<id>.json, and return the rendered plan." })`
44
+ `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<card.title>\": <card.goal>. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write the plan to .doug/.state/drafts/<id>.json (a draft for a batch; not .doug/plan.json), validate it with plan.mjs validate --file .doug/.state/drafts/<id>.json, and return the rendered plan." })`
45
45
  When all have returned, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" merge <id> <id>...` joins the drafts into `.doug/plan.json`: one draft plan, `cards` listing the batch in board order, every task carrying its `card`, level k of the batch being the union of each card's level k. A file two cards own is not refused: the later card's task (board order) gets a dependency edge to the earlier card's, so the file is edited in sequence and each card keeps its own spec, verify, and acceptance; merge prints each such edge (`<file>: <card>/<task> runs after <card>/<task>`), any task id it had to rename, and any plan-level setting the drafts disagreed on. Only a dependency cycle across cards is refused, by card name; then edit the drafts (or drop a card from the batch) and merge again. Put merge's output in the chat verbatim. Do not run `plan.mjs set card` for a batch; `plan.mjs show` prints `Cards:` and each task's card.
46
46
 
47
47
  Then `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: the title and goal, every acceptance line, the verify commands, every level with each task's owned files and verify command, and the models table. The approval prompt that follows is a small dialog and hides the plan, so the user must be able to read the whole plan in the conversation above it. After the block, add at most a few lines of your own: any assumption the planner stated, any task that owns many files, and any task whose implementer is not on the implement row (a `tier`, `model`, or `effort` on the task): the planner sets none on its own, so one that is there came from the plan file and the user decides whether it stays. Do not summarize the plan instead of showing it.
48
48
 
49
49
  ## 3. Human gate: approval
50
50
 
51
- Ask with `AskUserQuestion`, only after the full plan is in the chat: approve as written, edit first, or stop. Keep the question short; the plan is above it. Approval is never automatic and never inferred.
51
+ Ask with `AskUserQuestion`, only after the full plan is in the chat: approve as written, edit first, or stop. Keep the question short; the plan is above it. Approval is never automatic and never inferred, except that under autopilot the queue approval stands for it.
52
52
 
53
53
  - **Approve**: first make the plan file complete, because the workflow and `plan.mjs land` both read the file, never the launch arguments: `install` and `adversary.command` are filled from `.doug/config.json` (`commands.install` and `commands.adversary`, where `{root}` stands for the project directory) when the plan has none; check with `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" json | grep -o '"adversary":{[^}]*}'` and, only if the config lacks one, set it with `plan.mjs set adversary.command "<command>"` (or `plan.mjs set install "<command>"`). `plan.mjs show` prints `Fix attempts per blocked task`; change it only if the user asks, with `plan.mjs set fixAttempts <n>` (0 disables the in-run fix loop). When the user asked for a swarm, `plan.mjs set swarm on` (a lead splits each full-shape task into worker briefs; `plan.mjs show` prints `Swarm: on`); `/doug-swarm` is the command that does this from the start. Then `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" approve` and `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" anchor`.
54
54
  - **Edit**: stop here. Tell the user to edit `.doug/plan.json` and run `/doug-next <id>` again; the planner will not overwrite an edited draft unless asked.
@@ -69,19 +69,55 @@ If `ok` is not true: the workflow has already relaunched the implementer for eve
69
69
  Then, with the merge commit it printed:
70
70
 
71
71
  1. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> done --source "commit <sha>"`
72
- 2. Invoke the `run-report` skill for the card with `.doug/.state/last-report.json`, the run id the Workflow tool printed (`wf_...`), the wall clock, and the merge commit: it measures the run's cost with `cost.mjs` from the local transcripts (the workflow does not report cost; never estimate one), passes `cost.mjs`'s Codex line, when present, as `--codex-cost` to `board.mjs record` and `board.mjs summary` (never folded into `--cost`), appends the run entry to `docs/live-runs.md` with `board.mjs record`, and prints the three-line summary with `board.mjs summary`; put those three lines in the chat verbatim.
72
+ 2. Invoke the `run-report` skill for the card with `.doug/.state/last-report.json`, the run id the Workflow tool printed (`wf_...`), the wall clock, and the merge commit: it measures the run's cost with `cost.mjs` from the local transcripts (the workflow does not report cost; never estimate one), passes `cost.mjs`'s Codex line, when present, as `--codex-cost` to `board.mjs record` and `board.mjs summary` (never folded into `--cost`), appends the run entry to `docs/live-runs.md` with `board.mjs record`, and prints the three-line summary with `board.mjs summary`, each given the lead's `--summary "<one sentence>"` (run-report); put the sentence the commands print first in the chat, then those three lines verbatim.
73
73
  3. `git add <record> docs/live-runs.md .doug/plan.json && git commit -m "Board: <id> landed as <sha>"`, with `<record>` the path `board.mjs move` printed in item 1; when item 2's `run-report` invocation printed that it promoted a research note, add `docs/research/<id>.md` too. No trailers.
74
74
 
75
- Batch: every card of the batch moves to Done with the same `--source "commit <sha>"` (item 1, once per card), the `run-report` skill's batch section records one entry per card (that card's tasks only, its own measured cost, the shared integration agents counted once) and prints one summary per card, and item 3 is one commit: `"Board: <id>, <id> landed as <sha>"`.
75
+ Batch: every card of the batch moves to Done with the same `--source "commit <sha>"` (item 1, once per card), the `run-report` skill's batch section records one entry per card (that card's tasks only, its own measured cost, the shared integration agents counted once) and prints one summary per card, and item 3 is one commit: `"Board: <id>, <id> landed as <sha>"`. A batch ends with one overall sentence, in plain words, of what the batch completed, after each card's own summary line.
76
76
 
77
77
  ## 6. Follow-up cards
78
78
 
79
- Before the continue gate, gather the follow-up candidates this landing produced: the report's review issues, the adversary blocks classified marginal or false whose reason names a hole, the integrate stage's conflicts and failed verify tails, and the landing summary. Suggestions come only from what the run actually reported, never invented; each carries the one-line reason and cites the pass that found it. A reviewer's out-of-scope finding is offered only when it is realistic drift (a form a model or a person would plausibly write) or when one card would close the whole class; otherwise it goes in the landing note and the lead recommends none. The suggestion's goal text carries both: the goal names the pass that found it (the reviewer's finding, the implementer's blockedReason or handoff, the verifier's findings, the adversary block id) and the landed commit.
79
+ Before the continue gate, gather the follow-up candidates this landing produced: the report's review issues, the adversary blocks classified marginal or false whose reason names a hole, the integrate stage's conflicts and failed verify tails, and the landing summary. Suggestions come only from what the run actually reported, never invented; each carries the one-line reason and cites the pass that found it. A reviewer's out-of-scope finding is filed only when it is realistic drift (a form a model or a person would plausibly write) or when one card would close the whole class; otherwise it goes in the landing note and is not filed. The card's goal text carries both: the goal names the pass that found it (the reviewer's finding, the implementer's blockedReason or handoff, the verifier's findings, the adversary block id) and the landed commit.
80
80
 
81
- A landing with no candidates skips the question. Otherwise ask once with `AskUserQuestion`, `multiSelect: true`, one option per suggestion with a proposed id, title, column, size, class, track, and deps on the landed card, plus a "none" option.
81
+ Follow-ups are filed without a question. A landing with no candidates files nothing. Each realistic candidate goes straight to Backlog with `doug board add` (never a script that edits the record): `doug board add <id> --title "<title>" --goal "<goal>" --component <c> --size <S|M|L> --class <tests-only|prose|gate-script|code|docs|eval|decision> --track <hand|flow> --deps <landed-id> --column backlog`. Then the lead places it in Backlog by priority with `doug board reorder`: `--top` for a defect that blocks a commit, a gate, or a user; `--after <id>` of the last such card for drift a user would hit; `--bottom` otherwise. Only the user promotes a card to Ready; the lead never files a follow-up to Ready or Decide.
82
82
 
83
- Each accepted suggestion is added with `doug board add` (never a script that edits the record): `doug board add <id> --title "<title>" --goal "<goal>" --component <c> --size <S|M|L> --class <tests-only|prose|gate-script|code|docs|eval|decision> --track <hand|flow> --deps <landed-id> --column <ready|decide>` (or `node packages/doug-cli/dist/bin.js board add ...` when `doug` is not on PATH; build the CLI first if `dist/` is missing); an S hand card goes to the top of Ready (`doug board reorder <id> --top`) when it is mechanical and to Decide when it needs a decision. Then one commit `"Board: <id> follow-ups added: <new-id>, <new-id>"`, whatever the count; the served page (`doug board serve`) shows the new cards without a republish. Batch: each suggestion's deps name the card of the batch it came from; still one question and one commit for the whole batch.
83
+ The lead says in the chat what it filed and where: each new card's id, title, and its Backlog position with the reason. Then one commit `"Board: <id> follow-ups added: <new-id>, <new-id>"`, whatever the count; the served page (`doug board serve`) shows the new cards without a republish. Batch: each card's deps name the card of the batch it came from; still one commit for the whole batch.
84
+
85
+ ## Cards the user asks for
86
+
87
+ A card the user asks for is a mini ADR, not a one-line goal. Before filing it, the lead asks the scoping questions in one `AskUserQuestion` of up to 4 questions, each with a Recommended option, and writes the answers into the goal. The goal carries the fixed headings Context / Decision / Options / Questions; `docs/decisions` stays for real ADRs. Only a user-requested card takes this form: a follow-up the lead files keeps section 6's lighter form, so autopilot follow-ups stay question-free. A card the user asks for while autopilot runs is scoped at once with the same questions, filed to Backlog, and the queue resumes at the card it was on: the request is input, not a gate, and the running queue is unchanged.
84
88
 
85
89
  ## 7. Human gate: continue
86
90
 
87
91
  Ask with `AskUserQuestion` whether to take the next Ready card. Yes: go to step 1 with no id. No: end. Each card is a fresh plan and a fresh set of worktrees; nothing carries over between cards except the board and the log. Between passes of the same card, `plan.mjs replan` carries the task branches that passed.
92
+
93
+ ## Autopilot (--auto)
94
+
95
+ `/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: before each card it re-reads Ready with `board.mjs next --track any` and takes the next runnable Ready card 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 $40, and the user may change them 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 USD cap defaults to $40, and its wall cap grows as each card is taken, by that card's budget from the same size x class table (`board.mjs budgets` prints it); only the totals are checked between cards. When the measured walls drift, the lead reruns `board.mjs budgets --from docs/live-runs.md`, which recomputes the table and prints each cell's n, and replaces the 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.
96
+
97
+ 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.
98
+
99
+ 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 follows doug-hand from step 3 when it runs by hand, 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 follows 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.
100
+
101
+ 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. Between cards the lead measures the session's cost with cost.mjs --session latest and checks every cap.
102
+
103
+ Stop conditions, one per line:
104
+
105
+ - It stops and asks when a card needs a design decision its goal does not answer.
106
+ - It stops and asks when the gate is red twice on one card.
107
+ - It stops and asks when a hook blocks a step.
108
+ - It stops and asks when a reviewer blocker stays open after one fix pass.
109
+ - It stops and asks when a cap is reached.
110
+ - It stops and asks when a workflow run's report is not ok, on either track.
111
+ - It stops and asks when a workflow run pauses at a human gate, on either track.
112
+ - It stops and asks when cost.mjs reports an unpriced transcript, because the USD cap cannot be checked.
113
+
114
+ It ends with the summary below, one condition per line:
115
+
116
+ - It ends when Ready has no runnable card, under unlimited autopilot.
117
+ - It ends when the user says stop: the current card is finished or left In flow.
118
+ - It ends when the lead's context is at or above autopilot.contextThreshold in .doug/config.json (default 60): between cards 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.
119
+ - 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.
120
+
121
+ 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.
122
+
123
+ It waits for the answer, then resumes the queue at the card it stopped on. Its closing summary starts with one overall sentence, in plain words, of what the run completed; each landed card's own summary line was printed at its landing. It ends with one summary: each landed card's commit, wall clock, and gate line; each stopped card and why; each edge case taken conservatively; and the follow-ups filed.
@@ -12,7 +12,7 @@ Produce a plan file the user can approve. No code is written by this skill.
12
12
  1. If the request 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: it writes one cited note to `.doug/.state/research/<id>.md` (one researcher, or several in parallel with distinct questions).
13
13
 
14
14
  The planner runs on the `plan` row: read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models`, which prints the parsed table as JSON (`roles.plan.model`, `roles.plan.effort`). Spawn the planner subagent with the user's request verbatim plus any constraints they gave, and the note's path when there is one, passing that row's model (omit `model` when the row says `inherit`):
15
- `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "<request and constraints>. Research note: <path>. Write .doug/plan.json and return the rendered plan." })`
15
+ `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "<request and constraints>. Research note: <path>. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })`
16
16
  The Agent tool carries no effort parameter; the row's effort applies through the planner agent's own frontmatter (`effort: high`). The session model never plans itself: it hands the request over and shows the plan.
17
17
  2. When it returns, validate and render the plan:
18
18
  `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show`
@@ -15,14 +15,14 @@ The swarm pays on a task with independent deliverables that share no new symbol;
15
15
 
16
16
  Start from a clean working tree on the base branch (`git status --porcelain` empty apart from `.doug/` and `.claude/worktrees/`).
17
17
 
18
- - A card id: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" card <id>`. A card with `"track": "hand"` is harness work built in the checkout (decision 0005), not a swarm; say so and end. Move the card to In flow exactly as `/doug-next` step 1 does (`board.mjs move <id> flow`), leaving the move uncommitted working state that the served page (`doug board serve`) shows live.
18
+ - A card id: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" card <id>`. A card with `"track": "hand"` is a by-hand card, not a swarm; say so, name `/doug-next <id>` as the command that takes it, and end. Move the card to In flow exactly as `/doug-next` step 1 does (`board.mjs move <id> flow`), leaving the move uncommitted working state that the served page (`doug board serve`) shows live.
19
19
  - A goal: no board record changes; the plan carries no `card`.
20
20
 
21
21
  Put the card's title and goal (or the goal text) in the chat verbatim, in a fenced code block. If it depends on a fact outside the repository, invoke the `research` skill first.
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). 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). 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
 
@@ -36,17 +36,17 @@ Invoke the `swarm-launch` skill: it checks the plan is approved with swarm on an
36
36
 
37
37
  ## 5. Land and record
38
38
 
39
- `/doug-next` step 5 verbatim: `plan.mjs done`, `plan.mjs land`, the card's move to Done with the merge commit, the `run-report` skill (its cost table lists every worker by label), and one commit for the record and the log. A goal without a card lands the same way and skips the board steps; its run entry is not written, since the log is per card.
39
+ `/doug-next` step 5 verbatim: `plan.mjs done`, `plan.mjs land`, the card's move to Done with the merge commit, the `run-report` skill (its cost table lists every worker by label), and one commit for the record and the log. The landing's first chat line is the one-sentence summary `run-report` prints. A goal without a card lands the same way and skips the board steps; its run entry is not written, since the log is per card.
40
40
 
41
41
  ## 6. Follow-up cards
42
42
 
43
- `/doug-next` step 6 verbatim: gather the run's follow-up candidates, ask once with `AskUserQuestion` when there are any, and add accepted ones with `doug board add`, then one commit.
43
+ `/doug-next` step 6 verbatim: gather the run's follow-up candidates and file each straight to Backlog with `doug board add`, placed by priority, then one commit.
44
44
 
45
- Before the continue gate, gather the follow-up candidates this landing produced: the report's review issues, the adversary blocks classified marginal or false whose reason names a hole, the integrate stage's conflicts and failed verify tails, and the landing summary. Suggestions come only from what the run actually reported, never invented; each carries the one-line reason and cites the pass that found it. A reviewer's out-of-scope finding is offered only when it is realistic drift (a form a model or a person would plausibly write) or when one card would close the whole class; otherwise it goes in the landing note and the lead recommends none. The suggestion's goal text carries both: the goal names the pass that found it (the reviewer's finding, the implementer's blockedReason or handoff, the verifier's findings, the adversary block id) and the landed commit.
45
+ Before the continue gate, gather the follow-up candidates this landing produced: the report's review issues, the adversary blocks classified marginal or false whose reason names a hole, the integrate stage's conflicts and failed verify tails, and the landing summary. Suggestions come only from what the run actually reported, never invented; each carries the one-line reason and cites the pass that found it. A reviewer's out-of-scope finding is filed only when it is realistic drift (a form a model or a person would plausibly write) or when one card would close the whole class; otherwise it goes in the landing note and is not filed. The card's goal text carries both: the goal names the pass that found it (the reviewer's finding, the implementer's blockedReason or handoff, the verifier's findings, the adversary block id) and the landed commit.
46
46
 
47
- A landing with no candidates skips the question. Otherwise ask once with `AskUserQuestion`, `multiSelect: true`, one option per suggestion with a proposed id, title, column, size, class, track, and deps on the landed card, plus a "none" option.
47
+ Follow-ups are filed without a question. A landing with no candidates files nothing. Each realistic candidate goes straight to Backlog with `doug board add` (never a script that edits the record): `doug board add <id> --title "<title>" --goal "<goal>" --component <c> --size <S|M|L> --class <tests-only|prose|gate-script|code|docs|eval|decision> --track <hand|flow> --deps <landed-id> --column backlog`. Then the lead places it in Backlog by priority with `doug board reorder`: `--top` for a defect that blocks a commit, a gate, or a user; `--after <id>` of the last such card for drift a user would hit; `--bottom` otherwise. Only the user promotes a card to Ready; the lead never files a follow-up to Ready or Decide.
48
48
 
49
- Each accepted suggestion is added with `doug board add` (never a script that edits the record): `doug board add <id> --title "<title>" --goal "<goal>" --component <c> --size <S|M|L> --class <tests-only|prose|gate-script|code|docs|eval|decision> --track <hand|flow> --deps <landed-id> --column <ready|decide>` (or `node packages/doug-cli/dist/bin.js board add ...` when `doug` is not on PATH; build the CLI first if `dist/` is missing). An S hand card goes to the top of Ready (`doug board reorder <id> --top`) when it is mechanical and to Decide when it needs a decision. Then one commit `"Board: <id> follow-ups added: <new-id>, <new-id>"`, whatever the count; the served page (`doug board serve`) shows the new cards without a republish. Batch: each suggestion's deps name the card of the batch it came from; still one question and one commit for the whole batch.
49
+ The lead says in the chat what it filed and where: each new card's id, title, and its Backlog position with the reason. Then one commit `"Board: <id> follow-ups added: <new-id>, <new-id>"`, whatever the count; the served page (`doug board serve`) shows the new cards without a republish. Batch: each card's deps name the card of the batch it came from; still one commit for the whole batch.
50
50
 
51
51
  A goal without a card (section 1's second bullet) skips this step, the way section 5 says such a run skips the board steps.
52
52
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: research
3
- description: The research step before a plan, for a card whose goal depends on facts outside the repository (a Claude Code hook contract, a CLI's output format, a third-party API). One researcher, or several in parallel with distinct questions, write one cited note the planner and the builder work from; uncertainty is stated, never guessed. Invoked by /core-next, /doug-plan, and /doug-next before planning, or on its own.
3
+ description: The research step before a plan, for a card whose goal depends on facts outside the repository (a Claude Code hook contract, a CLI's output format, a third-party API). One researcher, or several in parallel with distinct questions, write one cited note the planner and the builder work from; uncertainty is stated, never guessed. Invoked by /doug-next, doug-hand, and /doug-plan before planning, or on its own.
4
4
  allowed-tools: Agent, Read, Write, Bash(mkdir *), Bash(cat *), Bash(node *plan.mjs *)
5
5
  ---
6
6
 
@@ -31,11 +31,13 @@ Write the findings to `.doug/.state/research/<card-id>.md` (session state, ignor
31
31
  - the sources: a URL, a file path with a line, or a command and its output, one per fact;
32
32
  - what stayed unverified, marked `unverified`, with what was tried. Never guess a fact into the note; an unverified line is what keeps the planner from building on it.
33
33
 
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
+
34
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/`.
35
37
 
36
38
  Then hand it on:
37
39
 
38
40
  - Flow track (`/doug-plan`, `/doug-next`): give the planner the note's path in its prompt. The planner writes the facts it relied on into the plan's goal with their sources and marks any unverified one.
39
- - Hand track (`/core-next`): the builder reads the note before the test, cites the sources in the commit message where a fact decided the design, and names the note in the landing's `--note`.
41
+ - Hand track (doug-hand): the builder reads the note before the test, cites the sources in the commit message where a fact decided the design, and names the note in the landing's `--note`.
40
42
 
41
43
  The step spends agents only when invoked and publishes nothing.