@dougharness/cli 1.0.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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ablate.d.ts +96 -0
  3. package/dist/ablate.js +440 -0
  4. package/dist/ablate.js.map +1 -0
  5. package/dist/apply.d.ts +2 -0
  6. package/dist/apply.js +19 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/bin.d.ts +2 -0
  9. package/dist/bin.js +80 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/board-page.d.ts +15 -0
  12. package/dist/board-page.js +27 -0
  13. package/dist/board-page.js.map +1 -0
  14. package/dist/board-serve-process.d.ts +24 -0
  15. package/dist/board-serve-process.js +227 -0
  16. package/dist/board-serve-process.js.map +1 -0
  17. package/dist/board-serve.d.ts +9 -0
  18. package/dist/board-serve.js +302 -0
  19. package/dist/board-serve.js.map +1 -0
  20. package/dist/board.d.ts +6 -0
  21. package/dist/board.js +515 -0
  22. package/dist/board.js.map +1 -0
  23. package/dist/detect/fs.d.ts +6 -0
  24. package/dist/detect/fs.js +50 -0
  25. package/dist/detect/fs.js.map +1 -0
  26. package/dist/detect/index.d.ts +3 -0
  27. package/dist/detect/index.js +13 -0
  28. package/dist/detect/index.js.map +1 -0
  29. package/dist/detect/node.d.ts +3 -0
  30. package/dist/detect/node.js +179 -0
  31. package/dist/detect/node.js.map +1 -0
  32. package/dist/detect/python.d.ts +2 -0
  33. package/dist/detect/python.js +388 -0
  34. package/dist/detect/python.js.map +1 -0
  35. package/dist/detect/repo.d.ts +2 -0
  36. package/dist/detect/repo.js +102 -0
  37. package/dist/detect/repo.js.map +1 -0
  38. package/dist/detect/types.d.ts +98 -0
  39. package/dist/detect/types.js +2 -0
  40. package/dist/detect/types.js.map +1 -0
  41. package/dist/diff.d.ts +12 -0
  42. package/dist/diff.js +105 -0
  43. package/dist/diff.js.map +1 -0
  44. package/dist/generate/agents.d.ts +19 -0
  45. package/dist/generate/agents.js +184 -0
  46. package/dist/generate/agents.js.map +1 -0
  47. package/dist/generate/claude-md.d.ts +9 -0
  48. package/dist/generate/claude-md.js +111 -0
  49. package/dist/generate/claude-md.js.map +1 -0
  50. package/dist/generate/config.d.ts +51 -0
  51. package/dist/generate/config.js +155 -0
  52. package/dist/generate/config.js.map +1 -0
  53. package/dist/generate/proposal.d.ts +11 -0
  54. package/dist/generate/proposal.js +175 -0
  55. package/dist/generate/proposal.js.map +1 -0
  56. package/dist/generate/settings.d.ts +35 -0
  57. package/dist/generate/settings.js +213 -0
  58. package/dist/generate/settings.js.map +1 -0
  59. package/dist/generate/skills.d.ts +8 -0
  60. package/dist/generate/skills.js +264 -0
  61. package/dist/generate/skills.js.map +1 -0
  62. package/dist/init.d.ts +10 -0
  63. package/dist/init.js +69 -0
  64. package/dist/init.js.map +1 -0
  65. package/dist/trace.d.ts +9 -0
  66. package/dist/trace.js +106 -0
  67. package/dist/trace.js.map +1 -0
  68. package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +10 -0
  69. package/node_modules/@dougharness/flow/LICENSE +21 -0
  70. package/node_modules/@dougharness/flow/agents/adversary-claude.md +33 -0
  71. package/node_modules/@dougharness/flow/agents/adversary.md +29 -0
  72. package/node_modules/@dougharness/flow/agents/implementer.md +23 -0
  73. package/node_modules/@dougharness/flow/agents/lead.md +35 -0
  74. package/node_modules/@dougharness/flow/agents/planner.md +70 -0
  75. package/node_modules/@dougharness/flow/agents/researcher.md +31 -0
  76. package/node_modules/@dougharness/flow/agents/reviewer.md +26 -0
  77. package/node_modules/@dougharness/flow/agents/verifier.md +24 -0
  78. package/node_modules/@dougharness/flow/lib/board.mjs +840 -0
  79. package/node_modules/@dougharness/flow/lib/code-index.mjs +629 -0
  80. package/node_modules/@dougharness/flow/lib/cost.mjs +392 -0
  81. package/node_modules/@dougharness/flow/lib/decisions.mjs +314 -0
  82. package/node_modules/@dougharness/flow/lib/embeddings.mjs +539 -0
  83. package/node_modules/@dougharness/flow/lib/land.mjs +219 -0
  84. package/node_modules/@dougharness/flow/lib/learn.mjs +742 -0
  85. package/node_modules/@dougharness/flow/lib/memory-import.mjs +145 -0
  86. package/node_modules/@dougharness/flow/lib/memory.mjs +1641 -0
  87. package/node_modules/@dougharness/flow/lib/models.mjs +148 -0
  88. package/node_modules/@dougharness/flow/lib/plan.mjs +773 -0
  89. package/node_modules/@dougharness/flow/lib/rehearse.mjs +825 -0
  90. package/node_modules/@dougharness/flow/lib/worktrees.mjs +215 -0
  91. package/node_modules/@dougharness/flow/package.json +19 -0
  92. package/node_modules/@dougharness/flow/scripts/board.mjs +260 -0
  93. package/node_modules/@dougharness/flow/scripts/cost.mjs +66 -0
  94. package/node_modules/@dougharness/flow/scripts/learn.mjs +161 -0
  95. package/node_modules/@dougharness/flow/scripts/memory.mjs +1274 -0
  96. package/node_modules/@dougharness/flow/scripts/plan.mjs +547 -0
  97. package/node_modules/@dougharness/flow/scripts/rehearse.mjs +129 -0
  98. package/node_modules/@dougharness/flow/skills/core-next/SKILL.md +92 -0
  99. package/node_modules/@dougharness/flow/skills/doug-approve/SKILL.md +15 -0
  100. package/node_modules/@dougharness/flow/skills/doug-decide/SKILL.md +26 -0
  101. package/node_modules/@dougharness/flow/skills/doug-implement/SKILL.md +22 -0
  102. package/node_modules/@dougharness/flow/skills/doug-learn/SKILL.md +20 -0
  103. package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +87 -0
  104. package/node_modules/@dougharness/flow/skills/doug-plan/SKILL.md +22 -0
  105. package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +55 -0
  106. package/node_modules/@dougharness/flow/skills/harness-fix/SKILL.md +85 -0
  107. package/node_modules/@dougharness/flow/skills/research/SKILL.md +41 -0
  108. package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +51 -0
  109. package/node_modules/@dougharness/flow/skills/swarm-launch/SKILL.md +27 -0
  110. package/node_modules/@dougharness/flow/workflows/doug-implement.js +2687 -0
  111. package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +10 -0
  112. package/node_modules/@dougharness/gates/LICENSE +21 -0
  113. package/node_modules/@dougharness/gates/hooks/hooks.json +184 -0
  114. package/node_modules/@dougharness/gates/lib/agent-context.mjs +111 -0
  115. package/node_modules/@dougharness/gates/lib/anchor.mjs +110 -0
  116. package/node_modules/@dougharness/gates/lib/baseline.mjs +160 -0
  117. package/node_modules/@dougharness/gates/lib/bash-rules.mjs +1522 -0
  118. package/node_modules/@dougharness/gates/lib/block-ledger.mjs +127 -0
  119. package/node_modules/@dougharness/gates/lib/checkpoint.mjs +97 -0
  120. package/node_modules/@dougharness/gates/lib/config.mjs +168 -0
  121. package/node_modules/@dougharness/gates/lib/evidence.mjs +59 -0
  122. package/node_modules/@dougharness/gates/lib/glob.mjs +86 -0
  123. package/node_modules/@dougharness/gates/lib/io.mjs +91 -0
  124. package/node_modules/@dougharness/gates/lib/paths.mjs +101 -0
  125. package/node_modules/@dougharness/gates/lib/proposals.mjs +201 -0
  126. package/node_modules/@dougharness/gates/lib/run.mjs +147 -0
  127. package/node_modules/@dougharness/gates/lib/scope.mjs +117 -0
  128. package/node_modules/@dougharness/gates/lib/secret-rules.mjs +268 -0
  129. package/node_modules/@dougharness/gates/lib/state.mjs +114 -0
  130. package/node_modules/@dougharness/gates/lib/trace.mjs +196 -0
  131. package/node_modules/@dougharness/gates/package.json +17 -0
  132. package/node_modules/@dougharness/gates/scripts/edit-loop.mjs +39 -0
  133. package/node_modules/@dougharness/gates/scripts/format-on-edit.mjs +45 -0
  134. package/node_modules/@dougharness/gates/scripts/guard-bash.mjs +32 -0
  135. package/node_modules/@dougharness/gates/scripts/protect-paths.mjs +49 -0
  136. package/node_modules/@dougharness/gates/scripts/reanchor.mjs +81 -0
  137. package/node_modules/@dougharness/gates/scripts/research-cap.mjs +295 -0
  138. package/node_modules/@dougharness/gates/scripts/secret-scan.mjs +26 -0
  139. package/node_modules/@dougharness/gates/scripts/session-start-baseline.mjs +57 -0
  140. package/node_modules/@dougharness/gates/scripts/statusline.mjs +133 -0
  141. package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +1422 -0
  142. package/node_modules/@dougharness/gates/scripts/trace.mjs +75 -0
  143. package/package.json +33 -0
  144. package/templates/board-app.js +533 -0
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ // On-demand live rehearsal of the flow, swarm, and hand tracks on a small real card (the ts-basic fixture's
3
+ // fix-hours), spawning the real `claude` binary. It is a script, not a test, and it never runs in the gate:
4
+ // evals/ already spends real money to measure arms and must not gain correctness assertions, and seam-contracts
5
+ // settles the deterministic side without a new top-level suite, so this lives here, next to cost.mjs and
6
+ // board.mjs, as a spend-and-record script of the flow plugin that reads the same skills it exercises.
7
+ //
8
+ // rehearse.mjs [flow|swarm|hand|all] --card <id> --spend [dir]
9
+ // no arguments print the estimate table and exit 2; nothing is spawned
10
+ // a scenario, no --spend print that scenario's per-stage estimate and exit 2; nothing is spawned
11
+ // --spend, no --card exit 2 ("--card <id> names the card this rehearsal records under"); nothing is spawned
12
+ // --spend and --card run it: [dir] is the real repository (default cwd) the outcome records under
13
+ // <the id> on its board; exit 0 when every run scenario is "passed", 1 otherwise
14
+ //
15
+ // Recording goes through the existing `board.mjs record` CLI only, never lib/board.mjs directly (that would
16
+ // bypass its research-note promotion and batch handling): the run form for flow/swarm (a synthetic, honest
17
+ // report when the workflow never wrote one, never invented task rows) and the hand form for hand, both carrying
18
+ // a rehearsal --note and --rehearsal <scenario> (the entry's heading and commit sha read as the fixture's, not
19
+ // this repository's landing); never memory.mjs (a fixture run has no run id the cost script can find, and the
20
+ // outcome log is for this repository's own runs). Every adversary block a real report carries is printed for the
21
+ // user to classify by hand.
22
+ //
23
+ // A passed scenario's fixture is removed after its stream logs and last report are archived into this
24
+ // repository's own .doug/.state/rehearsal/<scenario>-<timestamp>/ (a failed scenario keeps the whole fixture
25
+ // instead, as before). Every session this script spawns disallows the Artifact tool, since a gate or build stage
26
+ // that reaches the doug-board skill must never actually publish from a rehearsal.
27
+ //
28
+ // The `claude` binary name is overridable with $DOUG_REHEARSE_CLAUDE; a real user never sets it.
29
+
30
+ import { readFileSync, rmSync } from "node:fs";
31
+ import { resolve } from "node:path";
32
+ import { loadBoard, findCard, adversaryBlocks } from "../lib/board.mjs";
33
+ import { parseArgs, estimateLines, SCENARIOS, scenarioTotal, runScenario, recordRunOutcome, recordHandOutcome, rehearsalNote, extractGateLine, archiveEvidence } from "../lib/rehearse.mjs";
34
+
35
+ function fail(msg, code = 2) {
36
+ process.stderr.write(msg + "\n");
37
+ process.exit(code);
38
+ }
39
+
40
+ function printEstimates(scenario) {
41
+ process.stdout.write("Estimated cost (USD), from docs/live-runs.md measurements:\n");
42
+ for (const l of estimateLines(scenario)) process.stdout.write(l + "\n");
43
+ }
44
+
45
+ const opts = parseArgs(process.argv.slice(2));
46
+
47
+ if (!opts.scenario) {
48
+ printEstimates(null);
49
+ process.stdout.write("\nusage: rehearse.mjs [flow|swarm|hand|all] --card <id> --spend [dir]\n");
50
+ process.exit(2);
51
+ }
52
+ if (opts.scenario !== "all" && !SCENARIOS.includes(opts.scenario)) fail(`unknown scenario "${opts.scenario}"; scenarios are ${SCENARIOS.join(", ")}, all`, 2);
53
+
54
+ if (!opts.spend) {
55
+ printEstimates(opts.scenario === "all" ? null : opts.scenario);
56
+ process.stdout.write("\nAdd --spend to run it.\n");
57
+ process.exit(2);
58
+ }
59
+ if (!opts.card) fail("--card <id> names the card this rehearsal records under", 2);
60
+
61
+ const dir = resolve(opts.dir || process.cwd());
62
+ let board;
63
+ try {
64
+ board = loadBoard(dir);
65
+ } catch (err) {
66
+ fail(`cannot read the board at ${dir}: ${err.message}`, 2);
67
+ }
68
+ if (!findCard(board, opts.card)) fail(`no card "${opts.card}" on the board at ${dir}`, 2);
69
+
70
+ const scenarios = opts.scenario === "all" ? SCENARIOS : [opts.scenario];
71
+ process.stdout.write(`Estimated cost: $${scenarios.reduce((s, n) => s + scenarioTotal(n), 0).toFixed(2)}. Running ${scenarios.join(", ")} against ${opts.card} at ${dir}.\n`);
72
+
73
+ let allPassed = true;
74
+ for (const scenario of scenarios) {
75
+ const result = runScenario(scenario);
76
+ const passed = result.outcome === "passed";
77
+ allPassed = allPassed && passed;
78
+
79
+ const outcomeForNote = result.reportSynthesized ? `no report: the session ended at stage ${result.failedStage}: ${result.failedMessage}` : result.outcome;
80
+ const note = rehearsalNote({ scenario, outcome: outcomeForNote, stages: result.stages, cost: result.totalCost, agentNames: result.agentNames });
81
+
82
+ process.stdout.write(`\n${scenario}: ${result.outcome}\n`);
83
+ process.stdout.write(` wall clock: ${result.wallClock}\n`);
84
+ process.stdout.write(` measured cost: $${result.totalCost.toFixed(2)}\n`);
85
+ for (const s of result.stages) process.stdout.write(` stage ${s.name}: wall ${s.wallClock}${s.cost !== null ? `, cost $${s.cost.toFixed(2)}` : ""}${s.streamPath ? `, stream ${s.streamPath}` : ""}\n`);
86
+
87
+ // A passed fixture's evidence (its stream logs and last report) is archived into this repository before the
88
+ // fixture is removed. Archiving lives in the same defended path as recording, right below: a throw here (an
89
+ // unwritable .doug/.state, ENOSPC, ...) must not abort the loop before the outcome is recorded, and the evidence
90
+ // must survive somewhere, so an archive failure is reported on its own line and keeps the fixture instead of
91
+ // removing it, exactly like a failed scenario already does.
92
+ let evidencePath = null;
93
+ let keepFixture = !passed;
94
+ if (passed) {
95
+ try {
96
+ evidencePath = archiveEvidence({ fixtureDir: result.fixtureDir, dir, scenario });
97
+ process.stdout.write(` evidence archived: ${evidencePath}\n`);
98
+ } catch (err) {
99
+ keepFixture = true;
100
+ process.stdout.write(` evidence not archived: ${err.message}\n`);
101
+ }
102
+ }
103
+ process.stdout.write(` fixture: ${result.fixtureDir}${keepFixture ? " (kept for inspection)" : " (removed)"}\n`);
104
+
105
+ try {
106
+ let recorded;
107
+ if (scenario === "hand") {
108
+ recorded = recordHandOutcome(dir, opts.card, { commit: result.commit, wallClock: result.wallClock, gate: extractGateLine(result.fixtureDir) || "not observed", note, rehearsal: scenario });
109
+ } else {
110
+ recorded = recordRunOutcome(dir, opts.card, { reportFile: result.reportFile, wallClock: result.wallClock, cost: result.totalCost, mergeCommit: result.commit, note, rehearsal: scenario });
111
+ if (!result.reportSynthesized) {
112
+ const report = JSON.parse(readFileSync(result.reportFile, "utf8"));
113
+ const blocks = adversaryBlocks(report);
114
+ if (blocks.length) {
115
+ process.stdout.write(" adversary blocks (classify by hand with board.mjs record --adversary):\n");
116
+ for (const b of blocks) process.stdout.write(` ${b.task}/${b.id}${b.pass !== null ? ` (pass ${b.pass})` : ""}: ${b.description}\n`);
117
+ }
118
+ }
119
+ }
120
+ process.stdout.write(` recorded: ${recorded}`);
121
+ } catch (err) {
122
+ process.stdout.write(` recording failed: ${err.message}\n`);
123
+ allPassed = false;
124
+ }
125
+
126
+ if (!keepFixture) rmSync(result.fixtureDir, { recursive: true, force: true });
127
+ }
128
+
129
+ process.exit(allPassed ? 0 : 1);
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: core-next
3
+ description: Take the next hand-track board card (harness work, decision 0005) start to finish, or several together as one swarmed batch (`/core-next <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. Only the user invokes this.
4
+ disable-model-invocation: true
5
+ allowed-tools: AskUserQuestion, Agent, Workflow, Read, Edit, Write, Grep, Glob, Bash
6
+ ---
7
+
8
+ # core-next
9
+
10
+ One hand-track card, start to finish, with two human gates: start and continue. A hand-track card is harness work (plugins/doug-flow, plugins/doug-gates, packages/doug-codex, evals) built by hand directly in the checkout with tests and the 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; decision 0005, `docs/decisions/0005-two-tracks.md`, as amended 2026-09-07). Nothing starts before the user says so, and the loop never continues to another card without being asked.
11
+
12
+ `/core-next` takes the first Ready hand-track card whose dependencies are Done. `/core-next <card-id>` takes that card.
13
+
14
+ **Several cards in one swarmed batch.** `/core-next <id> <id>...` takes those hand-track cards together, and `/core-next --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.
15
+
16
+ ## 1. Pick the card
17
+
18
+ Start from a clean working tree on the base branch (`git status --porcelain` empty apart from `.doug/` and `.claude/worktrees/`).
19
+
20
+ - 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>`.
21
+ - 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.
22
+ - 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.
23
+
24
+ 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 `plugins/doug-flow/workflows/doug-implement.js`, anything under `plugins/doug-gates/scripts/` or `.doug/hooks/scripts/`, or anything under `plugins/doug-flow/agents/`) 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 `/core-next <id>` takes it alone by hand. If eligibility sets every card of the batch aside, say which and why, and stop: nothing moves.
25
+
26
+ 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.
27
+
28
+ 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.
29
+
30
+ ## 2. Human gate: start
31
+
32
+ Ask with `AskUserQuestion`: start this card, skip to the next hand-track card, or stop. Starting is never automatic.
33
+
34
+ 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.
35
+
36
+ - **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.
37
+ - **Skip**: go back to step 1 and take the next hand-track card after this one.
38
+ - **Stop**: end.
39
+
40
+ ## 2b. Shape: by hand, or a swarm
41
+
42
+ Size S, or a card with no size at all: by hand, no question.
43
+
44
+ Size M or L: by hand without asking when the goal names a file the workflow cannot touch under an approved plan: `plugins/doug-flow/workflows/doug-implement.js`, anything under `plugins/doug-gates/scripts/` or `.doug/hooks/scripts/`, anything under `plugins/doug-flow/agents/`. The workflow cannot rewrite itself, its gate hooks, or its agents while it runs them, so those stay by hand.
45
+
46
+ 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, 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).
47
+
48
+ 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). 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.
49
+
50
+ 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`.
51
+
52
+ Then continue at step 4.
53
+
54
+ ## 3. Build it by hand
55
+
56
+ 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.
57
+
58
+ 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), harness-fix, the brief, the tester, the coder, the reviewer, then the lead's own gate and commit.
59
+
60
+ 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.
61
+
62
+ Invoke the `harness-fix` skill to learn the test file that covers each module and the rules for harness code (the workflow file's constraints, gate scripts copied into `.doug/hooks/scripts`, the CLI's type declarations), and put its rules into the brief: read the goal as the acceptance list, name the test file harness-fix's table gives — 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 rule 7 now demands (one mutation per case the card's goal names, each with the test that must fail). 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. 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.
63
+
64
+ 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 `harness-fix` names, 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.
65
+
66
+ 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.
67
+
68
+ 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 (`pnpm typecheck` and `pnpm test:unit`), which the reviewer runs and the lead re-runs before the commit, says what ran, and records. A row that says `inherit` means the session model, and that is the user's choice in the table, not the lead's.
69
+
70
+ Before the commit the lead itself runs `pnpm typecheck` and `pnpm test:unit`, commits without attribution trailers, 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.
71
+
72
+ 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.
73
+
74
+ ## 4. Record and land
75
+
76
+ 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):
77
+
78
+ 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 "typecheck 0; unit <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). 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>` (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>` and put 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> --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>` per card with each card's three lines in the chat verbatim.
79
+ 2. `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> done --source "commit <sha>"`. Batch: once per card of the batch, same merge sha.
80
+ 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`.
81
+
82
+ ## 5. Follow-up cards
83
+
84
+ 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 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 coder's report, the adversary block id) and the landed commit.
85
+
86
+ 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.
87
+
88
+ 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.
89
+
90
+ ## 6. Human gate: continue
91
+
92
+ Ask with `AskUserQuestion` whether to take the next hand-track card. Yes: go to step 1 with no id. No: end.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: doug-approve
3
+ description: Approve the current .doug/plan.json so the doug-implement workflow may run it. Validates the plan first and writes the task anchor that survives compaction. Only the user invokes this.
4
+ disable-model-invocation: true
5
+ allowed-tools: Bash(node *plan.mjs *)
6
+ ---
7
+
8
+ # doug-approve
9
+
10
+ Approval is the user's act. This skill records it.
11
+
12
+ 1. Validate and approve: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" approve`
13
+ If validation fails, show the errors and stop. Do not edit the plan to make it pass; report what is wrong.
14
+ 2. Write the anchor: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" anchor`
15
+ 3. Confirm to the user that the plan is approved and that `/doug-implement` will execute it.
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: doug-decide
3
+ description: Propose a new ADR, an ADR amendment, or a rules file under docs/decisions/ or .claude/rules/ as an approvable diff (card memory-decisions). Those paths are writable only through this proposal path - protect-paths.mjs refuses an Edit/Write there. Only the user invokes this, and it never applies a change without asking.
4
+ disable-model-invocation: true
5
+ allowed-tools: AskUserQuestion, Read, Bash(node *memory.mjs *), Bash(node *learn.mjs *), Bash(git *)
6
+ ---
7
+
8
+ # doug-decide
9
+
10
+ `docs/decisions/**` and `.claude/rules/**` are proposal-only paths (`proposalPaths` in `.doug/config.json`): `protect-paths.mjs` refuses an Edit/Write/MultiEdit/NotebookEdit there, and the Stop gate flags a Bash-written change to them that is not recorded in the applied-proposal ledger. This skill is the one honest way to change them: render a diff under `.doug/.state/learn/`, show it, ask, then apply it with `learn.mjs apply` - never Edit or Write either path directly, even after the diff exists.
11
+
12
+ ## Forms
13
+
14
+ - `/doug-decide decision "<title>" --file <body.md>` - a new ADR. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" decision propose --title "<title>" --file <body.md>`. Refuses when the target ADR already exists, or the title/body is empty.
15
+ - `/doug-decide amend <NNNN> --file <text.md>` - a dated amendment to the one existing ADR matching `<NNNN>`. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" decision amend <NNNN> --file <text.md>`. Refuses when none or several ADRs match `<NNNN>`.
16
+ - `/doug-decide rule <name> --paths <globs> --file <body.md>` - a rules file. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" rule propose <name> --file <body.md> [--paths <glob,glob>]`. With `--paths`, the rule loads only when a matching file is read; with none, it loads unconditionally for every file at session start.
17
+
18
+ ## Steps
19
+
20
+ 1. Run the matching command above.
21
+ 2. On a refusal, put the reason in the chat - there is nothing to apply.
22
+ 3. On success, `Read` the printed diff file and show it to the user.
23
+ 4. Ask with `AskUserQuestion` whether to apply it (apply / skip) - one proposal, one question, never batched.
24
+ 5. On apply, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" apply <diff>` and report the target file it wrote. On skip, say so and stop.
25
+
26
+ This skill never commits (the caller does) and never applies a proposal the user has not been asked about.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: doug-implement
3
+ description: Execute the approved .doug/plan.json with the doug-implement workflow: one implementer per task in its own worktree, independent verification, two-stage review, and integration per dependency level. Only the user invokes this.
4
+ disable-model-invocation: true
5
+ allowed-tools: Bash(node *plan.mjs *), Workflow, Read
6
+ ---
7
+
8
+ # doug-implement
9
+
10
+ Runs the deterministic workflow on an approved plan. The user invoking this skill is the opt-in for multi-agent orchestration.
11
+
12
+ A plan that carries `swarm` on (`plan.mjs set swarm on`, printed by `plan.mjs show` as `Swarm: on`) runs each full-shape task as a swarm: a lead splits it into worker briefs, workers implement them in parallel on the `worker` row, and the lead merges before the checks. The launch is the same; the `swarm-launch` skill describes what the run does per task and how to read its report, and is the step `/doug-swarm` shares.
13
+
14
+ 1. Check the plan: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" validate`
15
+ If the status is not `approved`, stop and tell the user to run `/doug-approve`.
16
+ 2. Load it as JSON: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" json`
17
+ This resolves the CLAUDE.md `## Models` table into per-task model and effort; do not hand-edit the result.
18
+ 3. Launch the workflow with the plan object as `args` (an object, not a string):
19
+ `Workflow({ name: "doug-flow:doug-implement", args: <plan object> })`; the registered name carries the plugin prefix.
20
+ If the named workflow is not found, use `Workflow({ scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/doug-implement.js", args: <plan object> })`.
21
+ 4. When the run completes, read its report. For each level, state which tasks were implemented, verified, reviewed, adversarially reviewed (the `adversary` field: verdict, blockers, or why it did not run), and integrated, and quote the integration verify results. Name the integration branch. The per-task `acceptance` array holds the verifier's per-criterion exit codes and any ok=false entry is to be listed. A task whose fix loop stalled carries `supervisor` (`ran`, `stalled`, `brief`, and `stop` when it stopped): the supervisor step on the cheap tier ran before a fix pass after the first, because the same finding blocked twice or a fix pass re-attacked a file an earlier pass had cleared, and its brief was put in front of the next fix; say so, and quote the brief's directions when the task stopped as `stalled`.
22
+ 5. If the report carries `paused`, the run stopped at a human gate after that level integrated (a task there carries `gate: human`): say which tasks wait in `next`, and that the user continues with `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" gate open <level>` followed by a relaunch of the same run id (`Workflow` with `resumeFromRunId` and the plan's fresh JSON); `plan.mjs done` refuses while the last report is paused. If `ok` is true, tell the user the branch is ready to merge and offer `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" done` followed by `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" land`. `land` re-runs the plan's verify commands and then every acceptance command on the integration branch and merges it into the base branch with a merge commit only if they pass; it refuses otherwise and says why. If `ok` is not true, list what failed and stop; `plan.mjs done` also refuses a report that is not ok, naming the stopped level and each task's `stopReason`, overridable with `--force`. Do not fix things outside the workflow; re-plan instead.
@@ -0,0 +1,20 @@
1
+ ---
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.
4
+ disable-model-invocation: true
5
+ allowed-tools: AskUserQuestion, Read, Bash(node *learn.mjs *), Bash(git *)
6
+ ---
7
+
8
+ # doug-learn
9
+
10
+ This skill never changes a tracked file on its own initiative. `learn.mjs propose` only ever writes under `.doug/.state/learn/` (never a tracked file); `learn.mjs apply` is the only command that ever touches the checkout, and this skill calls it only after the user says yes to that one proposal. It never applies without asking, and it never runs unless the user invoked `/doug-learn`.
11
+
12
+ 1. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" signals` and put the summary (outcomes, lessons, trace counts) in the chat.
13
+ 2. Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" propose`. It collects signals, writes one diff per candidate change under `.doug/.state/learn/<timestamp>/`, and prints one line per proposal (`<NN> <kind> <target>: <reason>`), or `no proposals` when there is nothing to propose - stop here if so.
14
+ 3. Put every proposal line in the chat.
15
+ 4. For each proposal, in order:
16
+ - a proposal with `diff: null`, or whose target is a protected path (`.doug/config.json` for a `demote` proposal, always) - read `proposals.json` in the written directory for its `reason`, tell the user what it found and what they would do by hand, and do not ask a question for it: there is nothing to apply.
17
+ - a proposal with a real diff - show the diff (`Read` the `NN-<kind>.diff` file), then ask with `AskUserQuestion` whether to apply it (apply / skip). Never batch several proposals into one question; one proposal, one question.
18
+ 5. Apply only the proposals the user approved, one at a time: `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" apply <proposal-file>`. It exits 1 with the refusal reason when a diff no longer applies cleanly - report that instead of retrying on your own. Say what was applied (the target file) after each one, and what was skipped.
19
+
20
+ Never call `apply` for a proposal the user has not been asked about, and never call it before step 4's question for that proposal has been answered.
@@ -0,0 +1,87 @@
1
+ ---
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.
4
+ disable-model-invocation: true
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
+ ---
7
+
8
+ # doug-next
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.
11
+
12
+ `/doug-next` takes the first Ready card whose dependencies are Done. `/doug-next <card-id>` takes that card.
13
+
14
+ **Several cards in one plan.** `/doug-next <id> <id>...` takes those cards together, and `/doug-next --batch <n>` the first n runnable Ready cards (`board.mjs next --batch <n>`, board order). One planner per card runs in parallel, `plan.mjs merge` joins the drafts into one plan whose tasks carry their card, and the batch then goes through one approval, one run, and one land; each card gets its own Done move and its own run entry. Where the text below says "the card", read "each card of the batch" and follow the batch paragraphs marked as such.
15
+
16
+ ## Who does the work
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.
19
+
20
+ ## 1. Pick the card
21
+
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
+
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.
27
+
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
+
30
+ ## 2. Plan
31
+
32
+ If `.doug/plan.json` is already a draft whose `card` is this id (or whose `cards` are this batch) and any task carries `reuse`, it is the second pass of a blocked run (section 4 made it): do not spawn the planner; skip to the `plan.mjs show` paragraph below.
33
+
34
+ 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.
35
+
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
+
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." })`
40
+
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
+
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." })`
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
+
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
+
49
+ ## 3. Human gate: approval
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.
52
+
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
+ - **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.
55
+ - **Stop**: move the card back with `board.mjs move <id> ready` (each card of a batch) and end.
56
+
57
+ ## 4. Implement
58
+
59
+ `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" json` and pass its output verbatim as the args (do not add fields at launch): `Workflow({ name: "doug-flow:doug-implement", args: <plan object> })`; the registered name carries the plugin prefix. If it is not found, use `scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/doug-implement.js"`. 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 (that directory is ignored by git and always allowed by the scope gate; anything else under `.doug/` would block the turn) and summarize it as `/doug-implement` does: per level, what was implemented, verified, reviewed, adversarially reviewed, and integrated, with the integration verify results. A task whose `attempts` array has more than one entry was fixed in the run: say how many passes it took and quote the `retriable.reason` of each blocked pass; a task with a `stopReason` gets it quoted verbatim.
60
+
61
+ If the report carries `paused` (`{ level, gate: "human", next }`), the run stopped on purpose at a human gate after that level integrated: nothing failed, and the levels after it have not run. Summarize the levels that ran as above, name the tasks in `next`, and ask with `AskUserQuestion` whether to open the gate. Yes: `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" gate open <level>`, then relaunch the same run with the plan file's fresh JSON: `Workflow({ scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/doug-implement.js", resumeFromRunId: "<the wf_ id>", args: <plan.mjs json> })`; the levels already run replay from the runtime's cache and the run continues with the next level; then read the new report from the top of this section. No: leave the card In flow and the plan approved and stop; `plan.mjs done` refuses while the last report is paused, and also refuses a report that is not ok (naming the stopped level and each task's `stopReason`, overridable with `--force`), so a paused or failed run cannot be skipped by landing.
62
+
63
+ If `ok` is not true: the workflow has already relaunched the implementer for every retriable block up to `fixAttempts` times (default 5), so what stopped it is either not retriable (a finding on a file outside the task's owned files, an implementer that reported itself blocked, a verifier or reviewer that returned nothing, an adversary that did not run, or a finding marked `SPEC CONTRADICTS ACCEPTANCE`) or survived every attempt; each task's `stopReason` says which. Leave the card In flow and print the report summary. Each task's `acceptance` array is the verifier's per-criterion result (text, command, exit code); list any entry with ok=false. Then run `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" replan`: it reads `.doug/.state/last-report.json`, sets the plan back to draft, marks every task that passed verify, review, and the adversary with `reuse: <branch>`, and prints why each other task is fresh (its stage failed, its spec changed since the recorded pass, or it depends on a task that is not reused). Replan also prints the task worktrees it removed and any it kept, to be included in the chat verbatim. Put that output in the chat verbatim, then go back to the `plan.mjs show` paragraph of section 2 and the approval gate of section 3 with the same three options. Say which tasks will be reused and that the usual fix is to edit the fresh task's spec in `.doug/plan.json` before approving (a `SPEC CONTRADICTS ACCEPTANCE` finding means the spec or the acceptance line must change); editing a reused task's spec makes `plan.mjs approve` refuse until `replan` runs again. Do not spawn the planner again unless the user asks for a fresh plan, and do not fix things by hand. On approve, this section runs the workflow again: reused tasks skip the implementer, start from their recorded branch, and are verified, reviewed, and adversarially reviewed again before integration; a reused task that blocks is retried in place like any other.
64
+
65
+ ## 5. Land
66
+
67
+ `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" done` (it refuses when the last report is not ok, naming the stopped level and each task's `stopReason`; `--force` overrides with a printed warning) then read `.doug/config.json` and run `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" land` or `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" land --delete-branches` depending on whether the config sets `land.deleteBranches` to true (land reads the key itself too, so the flag only makes the choice visible in the command). Land re-runs the plan's verify commands and then every acceptance command on the integration branch and merges only if they all pass; a refusal names the failing criterion and shows the output tail. If it refuses, print its reason, leave the card In flow, and stop. Include land's `removed worktree`, `kept worktree`, and `deleted branch` lines in what is reported.
68
+
69
+ Then, with the merge commit it printed:
70
+
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.
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
+
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>"`.
76
+
77
+ ## 6. Follow-up cards
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.
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.
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.
84
+
85
+ ## 7. Human gate: continue
86
+
87
+ 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.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: doug-plan
3
+ description: Write an approvable implementation plan (.doug/plan.json) for a feature, fix, or refactor before any code is written. Use when the user asks to plan, scope, break down, or "make a plan for" work that touches more than one file, or says "doug plan".
4
+ when_to_use: The user wants a plan, task breakdown, or scoped spec before implementation; or the request is a multi-file feature and no .doug/plan.json exists yet.
5
+ allowed-tools: Agent, Bash(node *plan.mjs *), Read
6
+ ---
7
+
8
+ # doug-plan
9
+
10
+ Produce a plan file the user can approve. No code is written by this skill.
11
+
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
+
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." })`
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
+ 2. When it returns, validate and render the plan:
18
+ `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show`
19
+ 3. Show the rendered plan to the user. Point out any assumption the planner stated and any task that owns many files.
20
+ 4. Tell the user how to proceed: `/doug-approve` to approve it as written, edit `.doug/plan.json` by hand and then approve, or ask for changes and run `/doug-plan` again.
21
+
22
+ Do not implement anything. Do not approve the plan yourself.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: doug-swarm
3
+ description: A swarm from a board card or a goal, outside /doug-implement: the planner plans on the plan row with swarm on, you approve at the same gate /doug-next uses, the swarm-launch skill runs the plan through the workflow (a lead splits each task into worker briefs, workers implement in parallel, the lead merges), and land, the board move, and the run entry follow /doug-next's steps. Only the user invokes this; no swarm runs without an approved plan.
4
+ disable-model-invocation: true
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
+ ---
7
+
8
+ # doug-swarm
9
+
10
+ `/doug-swarm <card-id>` runs a board card as a swarm; `/doug-swarm <goal text>` runs a goal that has no card. Either way the plan is the contract: nothing runs before you approve it, the lead may split a task but never widen it, and the same verify, review, adversary, and land read the result. The user invoking this command is the opt-in for multi-agent orchestration; no swarm runs in the background or on a schedule.
11
+
12
+ The swarm pays on a task with independent deliverables that share no new symbol; a plan of small coupled tasks runs as the pipeline (`docs/decisions/0001-hierarchical-swarms.md`, Amendment 2026-09-20: one brief per task at +2.67 USD and +6.1 min against the pipeline on 2026-09-11; five briefs, 8/8 hidden tests, 8.31 USD, 8.5 min on 2026-09-19).
13
+
14
+ ## 1. Pick the work
15
+
16
+ Start from a clean working tree on the base branch (`git status --porcelain` empty apart from `.doug/` and `.claude/worktrees/`).
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.
19
+ - A goal: no board record changes; the plan carries no `card`.
20
+
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
+
23
+ ## 2. Plan on the plan row
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.
26
+
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
+
29
+ ## 3. Human gate: approval
30
+
31
+ Ask with `AskUserQuestion`, only after the full plan is in the chat: approve as written, edit first, or stop. Approval is never automatic and never inferred. Approve, edit, and stop mean what they mean in `/doug-next` step 3 (fill `install` and `adversary.command` from `.doug/config.json` when the plan has none; then `plan.mjs approve` and `plan.mjs anchor`; edit stops here; stop moves a card back to Ready).
32
+
33
+ ## 4. Run
34
+
35
+ Invoke the `swarm-launch` skill: it checks the plan is approved with swarm on and launches the workflow with `plan.mjs json` as the args. Note the wall clock. Save the `result` object of the Workflow tool's output to `.doug/.state/last-report.json` and read it as `/doug-next` step 4 does, plus the swarm's own rows: each task's `workers` (brief, files, branch, commit, and a block with its reason), and a lead block that says `lead brief <id> names a file the task does not own`, which means the split tried to widen and the fix is a wider task at approval, never a wider brief. A paused run, a blocked run, and `plan.mjs replan` work exactly as in `/doug-next` step 4; reused tasks skip the lead and the workers alike.
36
+
37
+ ## 5. Land and record
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.
40
+
41
+ ## 6. Follow-up cards
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.
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.
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.
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.
50
+
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
+
53
+ ## 7. Human gate: continue
54
+
55
+ Ask with `AskUserQuestion` whether to swarm the next Ready card (`board.mjs next`). Yes: step 1 with that id. No: end.