@dougharness/cli 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board-serve.js +40 -6
- package/dist/board-serve.js.map +1 -1
- package/dist/init.js +1 -1
- package/dist/init.js.map +1 -1
- package/dist/memory-setup.d.ts +7 -0
- package/dist/memory-setup.js +70 -46
- package/dist/memory-setup.js.map +1 -1
- package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +6 -2
- package/node_modules/@dougharness/flow/agents/coder.md +23 -0
- package/node_modules/@dougharness/flow/agents/spec-writer.md +23 -0
- package/node_modules/@dougharness/flow/agents/tester.md +21 -0
- package/node_modules/@dougharness/flow/hooks/hooks.json +4 -1
- package/node_modules/@dougharness/flow/hooks/register.tsx +75 -0
- package/node_modules/@dougharness/flow/lib/autopilot-core.mjs +117 -0
- package/node_modules/@dougharness/flow/lib/autopilot-view.mjs +17 -0
- package/node_modules/@dougharness/flow/lib/board.mjs +91 -10
- package/node_modules/@dougharness/flow/lib/card-land.mjs +115 -0
- package/node_modules/@dougharness/flow/lib/card-loop.mjs +746 -0
- package/node_modules/@dougharness/flow/lib/coordinator.mjs +368 -0
- package/node_modules/@dougharness/flow/lib/cost.mjs +15 -5
- package/node_modules/@dougharness/flow/lib/embeddings.mjs +144 -3
- package/node_modules/@dougharness/flow/lib/land.mjs +27 -8
- package/node_modules/@dougharness/flow/lib/memory.mjs +48 -12
- package/node_modules/@dougharness/flow/lib/plan.mjs +32 -4
- package/node_modules/@dougharness/flow/lib/worktrees.mjs +3 -3
- package/node_modules/@dougharness/flow/package.json +1 -1
- package/node_modules/@dougharness/flow/scripts/autopilot.mjs +407 -0
- package/node_modules/@dougharness/flow/scripts/board.mjs +107 -32
- package/node_modules/@dougharness/flow/scripts/cost.mjs +16 -3
- package/node_modules/@dougharness/flow/scripts/memory.mjs +20 -9
- package/node_modules/@dougharness/flow/scripts/plan.mjs +100 -30
- package/node_modules/@dougharness/flow/skills/doug-hand/SKILL.md +8 -2
- package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +30 -10
- package/node_modules/@dougharness/flow/skills/doug-start/SKILL.md +80 -0
- package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +1 -1
- package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +6 -6
- package/node_modules/@dougharness/flow/workflows/card-queen.js +807 -0
- package/node_modules/@dougharness/flow/workflows/doug-implement.js +7 -7
- package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +1 -1
- package/node_modules/@dougharness/gates/lib/anchor.mjs +29 -4
- package/node_modules/@dougharness/gates/lib/bash-rules.mjs +196 -6
- package/node_modules/@dougharness/gates/lib/gate-lock.mjs +40 -8
- package/node_modules/@dougharness/gates/lib/proposals.mjs +41 -5
- package/node_modules/@dougharness/gates/lib/scope.mjs +43 -4
- package/node_modules/@dougharness/gates/package.json +1 -1
- package/node_modules/@dougharness/gates/scripts/reanchor.mjs +2 -1
- package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +138 -10
- package/node_modules/@dougharness/gates/scripts/verify.mjs +8 -4
- package/package.json +3 -3
- package/templates/board-app.js +46 -0
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CLI for the plan file. Used by the doug-plan, doug-approve, and doug-implement skills.
|
|
3
|
+
// Any command takes --card <id> (right after the command): it works on the card's plan .doug/plans/<id>.json
|
|
4
|
+
// and report .doug/.state/reports/<id>/last-report.json instead of .doug/plan.json (card parallel-card-queens)
|
|
5
|
+
// plan.mjs anchor --cards [dir]
|
|
6
|
+
// writes .doug/anchor.md as one section per approved per-card plan, keeping the
|
|
7
|
+
// "## Autopilot handoff" section of the old anchor
|
|
3
8
|
// plan.mjs validate [dir] [--file <path>]
|
|
4
9
|
// exit 0 if .doug/plan.json (or the plan at --file, a card's draft) is valid, 1 with errors otherwise
|
|
5
10
|
// plan.mjs show [dir] [--file <path>]
|
|
@@ -29,8 +34,12 @@
|
|
|
29
34
|
// land.deleteBranches: true in .doug/config.json) also deletes the plan's
|
|
30
35
|
// task branches after the merge with `git branch -d`; the integration
|
|
31
36
|
// branch is always kept
|
|
37
|
+
// plan.mjs land-card <id> --branch <b> --worktree <abs> [--base main]
|
|
38
|
+
// land a hand card with no plan file: rebase its worktree on the base, gate the rebased
|
|
39
|
+
// tree through verify.mjs, fast-forward the base, remove the card worktree; prints the
|
|
40
|
+
// commit, the gate line, lockWaitMs and the removed worktrees; exit 1 on a refusal
|
|
32
41
|
// plan.mjs set <key> <value> [dir]
|
|
33
|
-
// set install, baseBranch, integrationBranch, adversary.command,
|
|
42
|
+
// set install, baseBranch, integrationBranch, integrationWorktree (under .claude/worktrees/), adversary.command,
|
|
34
43
|
// adversary.fallback (<model>[/<effort>], or off), card, fixAttempts, swarm (on|off),
|
|
35
44
|
// or workerCheck (on|off) in the plan file
|
|
36
45
|
// (the file is what the workflow and land read; never pass these as launch args only)
|
|
@@ -44,13 +53,14 @@
|
|
|
44
53
|
|
|
45
54
|
import { writeFileSync } from "node:fs";
|
|
46
55
|
import { join, resolve } from "node:path";
|
|
47
|
-
import { loadPlan, savePlan, validatePlan, setStatus, renderPlan, levelize, withSpecHashes, loadReport, reusePlan, staleReuseErrors, REPORT_RELPATH, acceptanceEntries, humanGateLevels, planWarnings, mergePlans, draftPath, PLAN_RELPATH } from "../lib/plan.mjs";
|
|
56
|
+
import { integrationWorktreeFor, loadPlan, savePlan, validatePlan, setStatus, renderPlan, levelize, withSpecHashes, loadReport, reusePlan, staleReuseErrors, REPORT_RELPATH, acceptanceEntries, humanGateLevels, planWarnings, mergePlans, draftPath, PLAN_RELPATH, PLANS_RELPATH, planPathFor, reportPathFor } from "../lib/plan.mjs";
|
|
48
57
|
import { loadBoard, boardPath } from "../lib/board.mjs";
|
|
49
58
|
import { loadModels, validateModelRefs, resolvePlanModels, renderModels, isModelName, EFFORTS } from "../lib/models.mjs";
|
|
50
59
|
import { landPlan, INTEGRATION_WORKTREE } from "../lib/land.mjs";
|
|
60
|
+
import { landCard } from "../lib/card-land.mjs";
|
|
51
61
|
import { removeFinishedWorktrees, retireTaskWorktrees, retireIntegrationWorktree, branchHead, isGitRepo } from "../lib/worktrees.mjs";
|
|
52
62
|
import { readMemoryConfig, readMemoryStaleDays, readIndexConfig, createProvider } from "../lib/embeddings.mjs";
|
|
53
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
63
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
54
64
|
import { execFileSync } from "node:child_process";
|
|
55
65
|
|
|
56
66
|
// The project's install command from .doug/config.json, used when the plan does not name one.
|
|
@@ -255,9 +265,24 @@ async function attachCodeContext(plan, dir, provider) {
|
|
|
255
265
|
}
|
|
256
266
|
}
|
|
257
267
|
|
|
258
|
-
const SETTABLE = ["install", "baseBranch", "integrationBranch", "adversary.command", "adversary.fallback", "card", "fixAttempts", "swarm", "workerCheck"];
|
|
268
|
+
const SETTABLE = ["install", "baseBranch", "integrationBranch", "integrationWorktree", "adversary.command", "adversary.fallback", "card", "fixAttempts", "swarm", "workerCheck"];
|
|
259
269
|
|
|
260
|
-
const [cmd, ...
|
|
270
|
+
const [cmd, ...argsRaw] = process.argv.slice(2);
|
|
271
|
+
// --card=<id> is the same as --card <id>.
|
|
272
|
+
const argsWithCard = argsRaw.flatMap((a) => (a.startsWith("--card=") ? ["--card", a.slice("--card=".length)] : [a]));
|
|
273
|
+
// --card <id> (card parallel-card-queens): the command works on that card's plan .doug/plans/<id>.json and report
|
|
274
|
+
// .doug/.state/reports/<id>/last-report.json instead of the single .doug/plan.json. The flag and its value are
|
|
275
|
+
// stripped before any positional is read.
|
|
276
|
+
const cardFlag = argsWithCard.indexOf("--card");
|
|
277
|
+
const cardId = cardFlag >= 0 ? argsWithCard[cardFlag + 1] : undefined;
|
|
278
|
+
const rawArgs = cardFlag >= 0 ? argsWithCard.filter((_, i) => i !== cardFlag && i !== cardFlag + 1) : argsWithCard;
|
|
279
|
+
if (cardFlag >= 0 && (!cardId || cardId.startsWith("--") || !/^[A-Za-z0-9._-]+$/.test(cardId) || cardId.startsWith("."))) {
|
|
280
|
+
process.stderr.write("plan.mjs --card needs a card id (letters, digits, . _ -)\n");
|
|
281
|
+
process.exit(2);
|
|
282
|
+
}
|
|
283
|
+
const cardPlanRel = cardId ? planPathFor(cardId) : PLAN_RELPATH;
|
|
284
|
+
const reportRel = cardId ? reportPathFor(cardId) : REPORT_RELPATH;
|
|
285
|
+
const anchorCards = cmd === "anchor" && rawArgs.includes("--cards");
|
|
261
286
|
// --file <path>: validate or show a plan file other than .doug/plan.json (a card's draft), relative to the project dir.
|
|
262
287
|
const fileFlag = rawArgs.indexOf("--file");
|
|
263
288
|
const planFile = fileFlag >= 0 && (cmd === "validate" || cmd === "show") ? rawArgs[fileFlag + 1] : undefined;
|
|
@@ -269,7 +294,7 @@ const lastDirArg = (args) => {
|
|
|
269
294
|
const last = positional[positional.length - 1];
|
|
270
295
|
return last && existsSync(last) && statSync(last).isDirectory() ? last : undefined;
|
|
271
296
|
};
|
|
272
|
-
const dirArg = cmd === "set" || cmd === "gate" ? restArgs.slice(2).find((a) => !a.startsWith("--")) : cmd === "merge" ? lastDirArg(restArgs) : firstNonFlag(restArgs);
|
|
297
|
+
const dirArg = cmd === "land-card" ? undefined : cmd === "set" || cmd === "gate" ? restArgs.slice(2).find((a) => !a.startsWith("--")) : cmd === "merge" ? lastDirArg(restArgs) : firstNonFlag(restArgs);
|
|
273
298
|
const dir = resolve(process.env.CLAUDE_PROJECT_DIR || dirArg || process.cwd());
|
|
274
299
|
const deleteBranchesFlag = cmd === "land" && restArgs.includes("--delete-branches");
|
|
275
300
|
const forceDoneFlag = cmd === "done" && restArgs.includes("--force");
|
|
@@ -286,8 +311,8 @@ function fail(msg, code = 1) {
|
|
|
286
311
|
process.exit(code);
|
|
287
312
|
}
|
|
288
313
|
|
|
289
|
-
const plan = loadPlan(dir, planFile);
|
|
290
|
-
if (!plan && cmd !== undefined && cmd !== "models" && cmd !== "merge") fail(`No plan at ${join(dir, planFile ||
|
|
314
|
+
const plan = anchorCards ? null : loadPlan(dir, planFile || cardPlanRel);
|
|
315
|
+
if (!plan && cmd !== undefined && cmd !== "models" && cmd !== "merge" && cmd !== "land-card" && !anchorCards) fail(`No plan at ${join(dir, planFile || cardPlanRel)}. Run /doug-plan first.`, 2);
|
|
291
316
|
const models = loadModels(dir);
|
|
292
317
|
|
|
293
318
|
function allErrors(p) {
|
|
@@ -336,7 +361,7 @@ switch (cmd) {
|
|
|
336
361
|
const errors = allErrors(plan);
|
|
337
362
|
if (errors.length) fail("Refusing to approve an invalid plan:\n" + errors.map((e) => " - " + e).join("\n"));
|
|
338
363
|
if (plan.tasks.some((t) => t.reuse !== undefined)) {
|
|
339
|
-
const reuseErrors = staleReuseErrors(plan, loadReport(dir), { git: gitResolver });
|
|
364
|
+
const reuseErrors = staleReuseErrors(plan, loadReport(dir, reportRel), { git: gitResolver });
|
|
340
365
|
if (reuseErrors.length) fail("Refusing to approve: stale reuse marks:\n" + reuseErrors.map((e) => " - " + e).join("\n"));
|
|
341
366
|
}
|
|
342
367
|
if (plan.status === "approved") {
|
|
@@ -361,19 +386,19 @@ switch (cmd) {
|
|
|
361
386
|
for (const r of retired.renamed) process.stdout.write(` renamed branch ${r.from} to ${r.to}\n`);
|
|
362
387
|
}
|
|
363
388
|
printWarnings(plan);
|
|
364
|
-
savePlan(dir, setStatus(plan, "approved", new Date().toISOString()));
|
|
389
|
+
savePlan(dir, setStatus(plan, "approved", new Date().toISOString()), cardPlanRel);
|
|
365
390
|
process.stdout.write(`Approved: ${plan.title}. Run /doug-implement to execute it.\n`);
|
|
366
391
|
break;
|
|
367
392
|
}
|
|
368
393
|
case "reject": {
|
|
369
|
-
savePlan(dir, setStatus(plan, "rejected"));
|
|
394
|
+
savePlan(dir, setStatus(plan, "rejected"), cardPlanRel);
|
|
370
395
|
process.stdout.write("Plan rejected. Edit it or run /doug-plan again.\n");
|
|
371
396
|
break;
|
|
372
397
|
}
|
|
373
398
|
case "done": {
|
|
374
399
|
// A run paused at a human gate has levels still to run; landing it would merge half a plan. --force does not
|
|
375
400
|
// override this: a paused run genuinely has work left, unlike a stopped one that merely failed.
|
|
376
|
-
const lastReport = loadReport(dir);
|
|
401
|
+
const lastReport = loadReport(dir, reportRel);
|
|
377
402
|
if (lastReport && lastReport.paused && typeof lastReport.paused.level === "number" && lastReport.plan === plan.title && plan.status === "approved") fail(`Refusing to mark the plan done: the last run is paused at the human gate after level ${lastReport.paused.level} (next: ${(lastReport.paused.next || []).join(", ")}). Open it with plan.mjs gate open ${lastReport.paused.level} and resume the run with its id, or reject the plan.`);
|
|
378
403
|
// A run that stopped short (a failed level, a level adversary still blocked) or otherwise finished not-ok must
|
|
379
404
|
// not be markable done: that would let land merge a broken run. --force overrides with a printed warning.
|
|
@@ -389,48 +414,89 @@ switch (cmd) {
|
|
|
389
414
|
if (!forceDoneFlag) fail(`Refusing to mark the plan done: the last run is not ok; ${levelPart}${reasonsPart}. Re-plan with plan.mjs replan and run again, or plan.mjs done --force to override.`);
|
|
390
415
|
process.stderr.write(`Warning: marking the plan done although the last run is not ok (${typeof lastReport.stoppedAtLevel === "number" ? `stopped at level ${lastReport.stoppedAtLevel}` : "finished not ok with no stopped level"}).\n`);
|
|
391
416
|
}
|
|
392
|
-
savePlan(dir, setStatus(plan, "done", new Date().toISOString()));
|
|
417
|
+
savePlan(dir, setStatus(plan, "done", new Date().toISOString()), cardPlanRel);
|
|
393
418
|
process.stdout.write("Plan marked done.\n");
|
|
394
419
|
break;
|
|
395
420
|
}
|
|
396
421
|
case "anchor": {
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
422
|
+
const anchorLines = (p) => {
|
|
423
|
+
const cardLines = typeof p.card === "string" && p.card.trim() ? [`Card: ${p.card}`, ""] : Array.isArray(p.cards) && p.cards.length ? [`Cards: ${p.cards.join(", ")}`, ""] : [];
|
|
424
|
+
const acceptanceLines = acceptanceEntries(p.acceptance).flatMap(({ text, command }) => (command ? [`- ${text}`, ` $ ${command}`] : [`- ${text}`]));
|
|
425
|
+
const lines = [`# ${p.title}`, "", ...cardLines, p.goal, "", "Acceptance:", ...acceptanceLines, "", "Tasks and owned files:"];
|
|
426
|
+
for (const t of p.tasks) lines.push(`- ${t.id}${t.card ? ` [${t.card}]` : ""}: ${t.title} -> ${t.files.join(", ")}${t.reuse ? ` (reuse ${t.reuse})` : ""}`);
|
|
427
|
+
return lines;
|
|
428
|
+
};
|
|
429
|
+
if (anchorCards) {
|
|
430
|
+
// One section per approved, not-landed per-card plan; the "## Autopilot handoff" section of the old anchor is kept.
|
|
431
|
+
const plansDir = join(dir, PLANS_RELPATH);
|
|
432
|
+
const sections = [];
|
|
433
|
+
for (const name of existsSync(plansDir) ? readdirSync(plansDir).filter((f) => f.endsWith(".json")).sort() : []) {
|
|
434
|
+
const p = loadPlan(dir, `${PLANS_RELPATH}/${name}`);
|
|
435
|
+
if (p && p.status === "approved" && !p.landed) sections.push(anchorLines(p).join("\n"));
|
|
436
|
+
}
|
|
437
|
+
const anchorFile = join(dir, ".doug/anchor.md");
|
|
438
|
+
let handoff = "";
|
|
439
|
+
if (existsSync(anchorFile)) {
|
|
440
|
+
const old = readFileSync(anchorFile, "utf8");
|
|
441
|
+
const at = old.startsWith("## Autopilot handoff") ? 0 : old.indexOf("\n## Autopilot handoff");
|
|
442
|
+
if (at >= 0) {
|
|
443
|
+
const from = at === 0 ? 0 : at + 1;
|
|
444
|
+
const next = old.indexOf("\n## ", from + 1);
|
|
445
|
+
handoff = old.slice(from, next >= 0 ? next + 1 : undefined).replace(/\n*$/, "\n");
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
writeFileSync(anchorFile, [...sections.map((x) => x + "\n"), ...(handoff ? [handoff] : [])].join("\n"));
|
|
449
|
+
process.stdout.write(`Wrote .doug/anchor.md (${sections.length} card plans)\n`);
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
writeFileSync(join(dir, ".doug/anchor.md"), anchorLines(plan).join("\n") + "\n");
|
|
402
453
|
process.stdout.write("Wrote .doug/anchor.md\n");
|
|
403
454
|
break;
|
|
404
455
|
}
|
|
405
456
|
case "land": {
|
|
406
457
|
const deleteBranches = deleteBranchesFlag || configDeleteBranches(dir);
|
|
407
|
-
const r = landPlan(dir, withDefaults(plan, dir), { deleteBranches });
|
|
458
|
+
const r = landPlan(dir, withDefaults(plan, dir), { deleteBranches, ...(cardId ? { rebase: true, ownOnly: true, planRelpath: cardPlanRel } : {}) });
|
|
408
459
|
if (!r.ok) fail(`Refusing to land: ${r.reason}`);
|
|
409
460
|
process.stdout.write(`Landed ${r.branch} into ${r.base} as ${r.mergeCommit}.\n`);
|
|
461
|
+
process.stdout.write(`lockWaitMs ${r.lockWaitMs ?? 0}\n`);
|
|
410
462
|
for (const v of r.verify) process.stdout.write(` verify ${v.ok ? "ok" : "failed"}: ${v.command} (${v.durationMs} ms)\n`);
|
|
411
463
|
for (const a of r.acceptance) process.stdout.write(` acceptance ${a.ok ? "ok" : "failed"}: ${a.text} (${a.durationMs} ms)\n`);
|
|
412
464
|
for (const p of r.worktrees.removed) process.stdout.write(` removed worktree ${p}\n`);
|
|
413
465
|
for (const k of r.worktrees.kept) process.stdout.write(` kept worktree ${k.path}: ${k.reason}\n`);
|
|
414
466
|
for (const n of r.branches.deleted) process.stdout.write(` deleted branch ${n}\n`);
|
|
415
467
|
for (const k of r.branches.kept) process.stdout.write(` kept branch ${k.name}: ${k.reason}\n`);
|
|
416
|
-
process.stdout.write(`The branch ${r.branch} is kept; delete it when you no longer need it. Recorded in
|
|
468
|
+
process.stdout.write(`The branch ${r.branch} is kept; delete it when you no longer need it. Recorded in ${cardPlanRel} as landed.\n`);
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
case "land-card": {
|
|
472
|
+
// plan.mjs land-card <id> --branch <b> --worktree <abs> [--base main]: land a hand card (no plan file needed).
|
|
473
|
+
const opt = (name) => {
|
|
474
|
+
const i = restArgs.indexOf(name);
|
|
475
|
+
return i >= 0 ? restArgs[i + 1] : undefined;
|
|
476
|
+
};
|
|
477
|
+
const id = restArgs.find((a, i) => !a.startsWith("--") && !(i > 0 && restArgs[i - 1].startsWith("--")));
|
|
478
|
+
const r = landCard(dir, { id, branch: opt("--branch"), worktree: opt("--worktree"), base: opt("--base") || "main" });
|
|
479
|
+
if (!r.ok) fail(`Refusing to land: ${r.reason}`);
|
|
480
|
+
process.stdout.write(`Landed ${r.id || opt("--branch")} as ${r.commit}.\n${r.gateLine}\nlockWaitMs ${r.lockWaitMs}\n`);
|
|
481
|
+
for (const p of r.removed) process.stdout.write(` removed worktree ${p}\n`);
|
|
482
|
+
for (const k of r.kept) process.stdout.write(` kept worktree ${k.path}: ${k.reason}\n`);
|
|
417
483
|
break;
|
|
418
484
|
}
|
|
419
485
|
case "gate": {
|
|
420
486
|
// plan.mjs gate open <level>: release the human gate after that level so a resumed run continues past it.
|
|
421
|
-
const [, action, levelArg] =
|
|
487
|
+
const [, action, levelArg] = [cmd, ...rawArgs];
|
|
422
488
|
const gated = humanGateLevels(plan);
|
|
423
489
|
if (action !== "open" || !/^\d+$/.test(String(levelArg))) fail(`usage: plan.mjs gate open <level> [dir]${gated.length ? `\nlevels with a human gate: ${gated.join(", ")}` : "\nthis plan has no human gate"}`, 2);
|
|
424
490
|
if (plan.landed) fail("Refusing to change a landed plan.");
|
|
425
491
|
const level = Number(levelArg);
|
|
426
492
|
if (!gated.includes(level)) fail(`plan.mjs gate open: level ${level} has no human gate${gated.length ? ` (levels with one: ${gated.join(", ")})` : " (this plan has none)"}`, 2);
|
|
427
493
|
const opened = [...new Set([...(plan.gatesOpened || []), level])].sort((a, b) => a - b);
|
|
428
|
-
savePlan(dir, { ...plan, gatesOpened: opened });
|
|
494
|
+
savePlan(dir, { ...plan, gatesOpened: opened }, cardPlanRel);
|
|
429
495
|
process.stdout.write(`Opened the human gate after level ${level}. Resume the run with its id; levels already run replay, and the run continues with level ${level + 1}.\n`);
|
|
430
496
|
break;
|
|
431
497
|
}
|
|
432
498
|
case "set": {
|
|
433
|
-
const [, key, value] =
|
|
499
|
+
const [, key, value] = [cmd, ...rawArgs];
|
|
434
500
|
if (!SETTABLE.includes(key) || typeof value !== "string" || !value.trim()) fail(`usage: plan.mjs set <${SETTABLE.join("|")}> <value> [dir]`, 2);
|
|
435
501
|
if (existsSync(value) && statSync(value).isDirectory()) fail(`plan.mjs set ${key}: the value ${JSON.stringify(value)} is a directory; the value comes before the optional [dir] argument.\nusage: plan.mjs set <${SETTABLE.join("|")}> <value> [dir]`, 2);
|
|
436
502
|
if (plan.landed) fail("Refusing to change a landed plan.");
|
|
@@ -458,8 +524,12 @@ switch (cmd) {
|
|
|
458
524
|
if (!/^\d+$/.test(value)) fail(`plan.mjs set fixAttempts: the value must be a non-negative integer, got ${JSON.stringify(value)}`, 2);
|
|
459
525
|
next.fixAttempts = Number(value);
|
|
460
526
|
} else next[key] = value;
|
|
461
|
-
|
|
462
|
-
|
|
527
|
+
if (key === "integrationWorktree") {
|
|
528
|
+
const bad = validatePlan(next).filter((e) => e.includes("integrationWorktree"));
|
|
529
|
+
if (bad.length) fail(`plan.mjs set integrationWorktree: ${bad.join("; ")}`, 2);
|
|
530
|
+
}
|
|
531
|
+
savePlan(dir, next, cardPlanRel);
|
|
532
|
+
process.stdout.write(`Set ${key} in ${cardPlanRel}.\n`);
|
|
463
533
|
break;
|
|
464
534
|
}
|
|
465
535
|
case "merge": {
|
|
@@ -501,10 +571,10 @@ switch (cmd) {
|
|
|
501
571
|
}
|
|
502
572
|
case "replan": {
|
|
503
573
|
if (plan.landed) fail("Refusing to replan a landed plan.");
|
|
504
|
-
const report = loadReport(dir);
|
|
505
|
-
if (report === null) fail(`No report at ${
|
|
574
|
+
const report = loadReport(dir, reportRel);
|
|
575
|
+
if (report === null) fail(`No report at ${reportRel}; run the workflow first (doug-next saves its report there).`);
|
|
506
576
|
const { plan: next, decisions } = reusePlan(plan, report, { git: gitResolver });
|
|
507
|
-
savePlan(dir, next);
|
|
577
|
+
savePlan(dir, next, cardPlanRel);
|
|
508
578
|
process.stdout.write(`Plan "${plan.title}" is a draft again.\n`);
|
|
509
579
|
for (const d of decisions) {
|
|
510
580
|
if (d.reuse) process.stdout.write(d.renamed ? ` ${d.id}: reuse ${d.reuse} (${d.reason})\n` : ` ${d.id}: reuse ${d.reuse}\n`);
|
|
@@ -527,11 +597,11 @@ switch (cmd) {
|
|
|
527
597
|
// The integration worktree is cleared too: a leftover from the plan's last run must not be reused as is by
|
|
528
598
|
// the next one (card integration-worktree-stale). Its branch is untouched; the workflow's first integration
|
|
529
599
|
// recreates the worktree from the base branch.
|
|
530
|
-
const integrationRetired = retireIntegrationWorktree(dir);
|
|
600
|
+
const integrationRetired = retireIntegrationWorktree(dir, integrationWorktreeFor(plan));
|
|
531
601
|
if (integrationRetired.removed) process.stdout.write(` removed integration worktree ${integrationRetired.removed}\n`);
|
|
532
602
|
process.stdout.write("Review it with plan.mjs show, edit .doug/plan.json if a fresh task needs a new spec, then approve.\n");
|
|
533
603
|
break;
|
|
534
604
|
}
|
|
535
605
|
default:
|
|
536
|
-
fail("usage: plan.mjs <validate|show|json|models|approve|reject|done|anchor|land|set|replan|gate|merge> [dir]", 2);
|
|
606
|
+
fail("usage: plan.mjs <validate|show|json|models|approve|reject|done|anchor|land|land-card|set|replan|gate|merge> [dir]", 2);
|
|
537
607
|
}
|
|
@@ -38,6 +38,8 @@ Batch: one `AskUserQuestion` names every remaining card of the batch (id, title,
|
|
|
38
38
|
- **Skip**: go back to step 1 and take the next hand-track card after this one.
|
|
39
39
|
- **Stop**: end.
|
|
40
40
|
|
|
41
|
+
A size L card with no `docs/specs/<card-id>/spec.md` is offered feature mode first (ADR 0012): after Start and before 2b, ask with `AskUserQuestion` whether to run `/doug-start <id>` (specification, optional pseudocode, architecture, refinement) or build now; `/doug-start <id>` ends this loop and the card stays In flow. S and M cards go straight on, and any card can still run `/doug-start <id>` on request. Under autopilot the offer is not asked and the card goes straight on.
|
|
42
|
+
|
|
41
43
|
## 2b. Shape: by hand, or a swarm
|
|
42
44
|
|
|
43
45
|
Size S, or a card with no size at all: by hand, no question.
|
|
@@ -46,6 +48,8 @@ Size M or L: by hand without asking when the goal names a file the running workf
|
|
|
46
48
|
|
|
47
49
|
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
50
|
|
|
51
|
+
Recommend a swarm only when the goal asks for two or more independent deliverables: separate pieces of work, each with its own test file, where none uses a function, type, or file another one adds (the 2026-09-19 split probe's four report commands, docs/live-runs.md); otherwise recommend by hand. Say in the question, in one clause, which side of this rule the card is on. The recommendation is the lead's reading of the goal, not a default.
|
|
52
|
+
|
|
49
53
|
Otherwise, for one card, ask with `AskUserQuestion`: run it as a swarm, or by hand. Swarm: spawn the planner the way `doug-plan` does, on the `plan` row: read it with the `plan.mjs models` command, `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` (`roles.plan.model`, `roles.plan.effort`), and pass that row's model (omit `model` when the row says `inherit`), but tell it this plan runs as a swarm the way `/doug-swarm` does: `Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "Board card <id>, title \"<title>\": <goal>. This plan runs as a swarm: set \"swarm\": true in the plan file, and size every task M or L unless it is truly one bounded change (a size-S task keeps a single implementer). Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json and return the rendered plan." })`. Then `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <id>` and `plan.mjs set swarm on` (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set swarm on`) guarantee the card and the opt-in even if the planner forgot, put `plan.mjs show` in the chat, and ask the approval gate with `AskUserQuestion`: approve as written, edit first, or stop. Approval is never automatic.
|
|
50
54
|
|
|
51
55
|
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`.
|
|
@@ -84,10 +88,12 @@ Stay within the card's goal. A defect found on the way that the goal does not co
|
|
|
84
88
|
|
|
85
89
|
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
90
|
|
|
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>"`
|
|
91
|
+
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>" --cost <usd>` (`--cost` is the measured session cost run-report's hand form takes with `cost.mjs --session <session> --since <openedAt>`, left out when nothing was measured; 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 "<key>=<real|marginal|false>: <reason>"` (the key is "`R<n>` or `<card>/R<n>`") 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. The landing block is printed by `board.mjs record` (both forms) and by `board.mjs summary --card` as their last three lines (Changed, Outcome, Learned; at most 100 words, `--note` feeds Learned); plain `summary` (no `--card`) prints no block. Your final message carries it verbatim, and the Stop gate blocks until it does.
|
|
88
92
|
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
93
|
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
94
|
|
|
95
|
+
After the landing block, the lead prints the card's recap, a few lines in plain words: what the task was, what the outcome was, any other important facts (a follow-up worth filing, an edge case taken conservatively, a cost or wall-clock surprise), and, for a new feature, how to use it. In a batch, each card's recap follows that card's landing block.
|
|
96
|
+
|
|
91
97
|
## 5. Follow-up cards
|
|
92
98
|
|
|
93
99
|
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.
|
|
@@ -98,7 +104,7 @@ The lead says in the chat what it filed and where: each new card's id, title, an
|
|
|
98
104
|
|
|
99
105
|
## Cards the user asks for
|
|
100
106
|
|
|
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,
|
|
107
|
+
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, 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. The goal opens with the user's request quoted verbatim under Context, and each Decision line is marked (user answer) or (lead, not asked). One question names the broadest word in the user's request and offers its readings. An option is marked Recommended only when its description cites the fact it rests on, checked before asking; an option resting on an unchecked fact says (unverified) and is not Recommended, and a question with no checked option has no Recommended. When the user asked for research, "Let the research decide" is the Recommended option. 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
108
|
|
|
103
109
|
## 6. Human gate: continue
|
|
104
110
|
|
|
@@ -31,6 +31,8 @@ Move it: `node "${CLAUDE_PLUGIN_ROOT}/scripts/board.mjs" move <id> flow` prints
|
|
|
31
31
|
|
|
32
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
33
|
|
|
34
|
+
A size L card with no `docs/specs/<card-id>/spec.md` is offered feature mode first (ADR 0012): before the planner, ask with `AskUserQuestion` whether to run `/doug-start <id>` (specification, optional pseudocode, architecture, refinement) or plan now; `/doug-start <id>` ends this loop and the card stays In flow. S and M cards go straight to the plan, and any card can still run `/doug-start <id>` on request. Under autopilot the offer is not asked and the card goes straight to the plan.
|
|
35
|
+
|
|
34
36
|
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
37
|
|
|
36
38
|
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`).
|
|
@@ -56,7 +58,7 @@ Ask with `AskUserQuestion`, only after the full plan is in the chat: approve as
|
|
|
56
58
|
|
|
57
59
|
## 4. Implement
|
|
58
60
|
|
|
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.
|
|
61
|
+
`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. A background run's result arrives in its task-notification `<result>`. 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
62
|
|
|
61
63
|
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
64
|
|
|
@@ -69,10 +71,12 @@ If `ok` is not true: the workflow has already relaunched the implementer for eve
|
|
|
69
71
|
Then, with the merge commit it printed:
|
|
70
72
|
|
|
71
73
|
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`, 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.
|
|
74
|
+
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. The landing block is printed by `board.mjs record` (both forms) and by `board.mjs summary --card` as their last three lines (Changed, Outcome, Learned; at most 100 words, `--note` feeds Learned); plain `summary` (no `--card`) prints no block. Your final message carries it verbatim, and the Stop gate blocks until it does.
|
|
73
75
|
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
76
|
|
|
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.
|
|
77
|
+
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. In a batch, each card's landing block (one per card) goes in the final message verbatim.
|
|
78
|
+
|
|
79
|
+
After the landing block, the lead prints the card's recap, a few lines in plain words: what the task was, what the outcome was, any other important facts (a follow-up worth filing, an edge case taken conservatively, a cost or wall-clock surprise), and, for a new feature, how to use it. In a batch, each card's recap follows that card's landing block.
|
|
76
80
|
|
|
77
81
|
## 6. Follow-up cards
|
|
78
82
|
|
|
@@ -84,7 +88,7 @@ The lead says in the chat what it filed and where: each new card's id, title, an
|
|
|
84
88
|
|
|
85
89
|
## Cards the user asks for
|
|
86
90
|
|
|
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,
|
|
91
|
+
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, 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. The goal opens with the user's request quoted verbatim under Context, and each Decision line is marked (user answer) or (lead, not asked). One question names the broadest word in the user's request and offers its readings. An option is marked Recommended only when its description cites the fact it rests on, checked before asking; an option resting on an unchecked fact says (unverified) and is not Recommended, and a question with no checked option has no Recommended. When the user asked for research, "Let the research decide" is the Recommended option. 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.
|
|
88
92
|
|
|
89
93
|
## 7. Human gate: continue
|
|
90
94
|
|
|
@@ -92,13 +96,28 @@ Ask with `AskUserQuestion` whether to take the next Ready card. Yes: go to step
|
|
|
92
96
|
|
|
93
97
|
## Autopilot (--auto)
|
|
94
98
|
|
|
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:
|
|
99
|
+
`/doug-next --auto [n]` takes the first n runnable Ready cards of either track in board order (`board.mjs next --batch <n> --track any`). With n, the queue is fixed at the approval. Without n, autopilot is unlimited: whenever a slot is free it re-reads Ready with `autopilot.mjs pick` and takes the next runnable Ready cards in board order. It never takes a Backlog card: follow-ups still go to Backlog, and a card the user promotes to Ready during the run is picked up. It shows the queue once, each card's id, size, track, and class, with the caps: cards, wall clock, and USD. The caps default to the queue's length in cards, the wall cap that board.mjs next --batch prints (the sum of each card's budget from the size x class wall budget table in board.mjs, at the p80 of measured landings: a size/class cell with fewer than 5 landings falls back to its size's all-class figure, a card with no class uses its size's all-class figure, and a card with no size counts as M), and the usd cap it prints beside it (the sum of each card's budget from the size x class usd budget table in board.mjs, at the p80 of measured cost over flow and hand landings, with the same fallbacks), and the user may change them at the approval. The USD cap has no fixed default and no floor: it is the sum of the per-card budgets, and the user may raise it at the approval. Unlimited autopilot shows the runnable Ready cards at the approval, and the approval covers every card it later takes from Ready. Unlimited autopilot has no card cap: its wall cap and its USD cap grow as each card is taken, by that card's budget from the same size x class tables (`board.mjs budgets` and `board.mjs budgets --usd` print them); only the totals are checked on every queen notification. When the measured walls or costs drift, the lead reruns `board.mjs budgets --from docs/live-runs.md` and `board.mjs budgets --usd --from docs/live-runs.md`, which recompute the tables and print each cell's n, and replaces each table in the `board.mjs` source with the printed one. Autopilot asks one approval, for the whole queue, and no other gate: no start gate, no shape question, and no continue gate.
|
|
96
100
|
|
|
97
101
|
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
102
|
|
|
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
|
|
103
|
+
Each card still runs its track unchanged (research when needed, tester, coder, reviewer, mutations, gate, commit, record, Done; a lean-lane card has no tester and no mutation list). A hand card runs card-queen, whose queen follows doug-hand from step 3, with step 2's start gate question, 2b's question, and step 6's continue gate skipped; a hand card that asks for a swarm takes 2b's swarm branch instead of step 3, as below. A flow card runs doug-implement per card, following sections 2 to 6 above, with section 3's approval replaced as below and section 7 skipped. Only the questions are skipped, not the actions behind them. Under autopilot a card still gets everything the skipped gates' Start or Approve branches do: a hand card gets doug-hand step 2's class, move to flow, and condition open, and the wall clock is noted when the card starts; a flow card gets section 1's move and section 3's Approve steps.
|
|
104
|
+
|
|
105
|
+
The queue approval stands for each flow-track card's plan approval: the lead puts plan.mjs show in the chat and runs plan.mjs approve itself. A card runs by hand unless its goal asks for a swarm. A hand card whose goal asks for a swarm follows doug-hand's 2b swarm branch, with the queue approval standing for its plan approval. Follow-ups are filed to Backlog as section 6 says, without a question. On every queen notification the lead measures the session's cost with cost.mjs --session <id> and checks every cap.
|
|
106
|
+
|
|
107
|
+
Parallel cards: up to `workers.max` cards (default 3) run at once, each under its own queen, a background run in the card's own worktree, and main only coordinates. Main picks with `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" pick` up to workers.max cards that are disjoint: for hand cards, goals that name no common path and components that differ; for flow cards, plans whose owned files do not overlap. Flow cards in `needsPlan` get one `doug-flow:planner` each, all in one message, on the plan row, each writing `.doug/plans/<id>.json` and validating it with `plan.mjs validate --card <id>`. For each planned card main runs `plan.mjs set card <id> --card <id>`, `plan.mjs set integrationBranch doug/<id> --card <id>`, and `plan.mjs set integrationWorktree .claude/worktrees/doug-integration-<id> --card <id>`, puts `plan.mjs show --card <id>` in the chat, and runs `plan.mjs approve --card <id>`, because the queue approval stands for it; then main picks again. A flow card gets section 1's move and a hand card gets doug-hand step 2's class, move, and condition open, before its launch. A flow queen is `Workflow({ name: "doug-flow:doug-implement", args: <plan.mjs json --card <id>> })`; a hand queen is `Workflow({ scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/card-queen.js", args })` with args `{ card, options, models, repoRoot, worktreePath }`, the worktreePath being `<repoRoot>/.claude/worktrees/card-<id>`. To launch a queen, main runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" queen-args <id> --kind <flow|hand>`, which writes `.doug/.state/reports/<id>/queen-args.json` and prints its path; then the Workflow call with that file's JSON as args; then `autopilot.mjs launched <id> --run <wf_id> --kind <flow|hand> --worktree <abs> --args-file <path>`, the flow worktree being `<repoRoot>/<the plan's integrationWorktree>`, because that value is relative. Main starts all the Workflow calls in one message, and it never briefs, builds, or reviews a queen card. Each queen's report arrives in that run's task-notification `<result>`, not in the Workflow tool's return, and main pipes it through `autopilot.mjs report --run <wf_id> --file .doug/.state/briefs/<id>-result.json`, the queen's result saved there, which prints land, resume, or ask. Land means the landing steps below; resume relaunches the queen with `resumeFromRunId` and the saved `queen-args.json`, then runs `autopilot.mjs resumed <id> --run <new wf_id>`, once, and a second resume is ask; ask stops that card and asks the user. Main lands one card at a time: a flow card with `plan.mjs done --card <id>` then `plan.mjs land --card <id>`, a hand card with `plan.mjs land-card <id> --branch card/<id> --worktree <abs>`. After each landing main runs `board.mjs move`, the run-report skill with `cost.mjs <wf_id> --report <per-card report path>`, which is `.doug/.state/reports/<id>/last-report.json` for a flow card and `.doug/.state/reports/<id>/card-report.json` for a hand card, and `autopilot.mjs lock-wait <id> --ms <n>`, where n is the `lockWaitMs` line that `plan.mjs land` or `land-card` printed, and the landing commit carries the card's plan `.doug/plans/<id>.json`. A resume never replans. When a queen's run needs the user, main asks while the other queens keep running. A cap reached launches no new queen and lets the in-flight ones finish. In manual or accept-edits mode every queen launch prompts, and the Workflow allow rule or "Yes, and don't ask again" runs n queens unattended (docs/research/parallel-cards-research.md section 4). A card tagged swarm (or `swarm: true`, or whose goal says to run it as a swarm) and a decision card run alone, as today, after the in-flight queens finish; `/doug-next <id>` by hand is unchanged.
|
|
108
|
+
|
|
109
|
+
The lead keeps the run's state file, `.doug/.state/autopilot.json` (uncommitted), current through `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs"`, which the live pane and the board's Autopilot panel (`doug board serve`) read:
|
|
110
|
+
|
|
111
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" start --cards <id,id,...> --usd-cap <n> --wall-cap <min>` at the queue approval.
|
|
112
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" add <id> [--usd-cap <n>] [--wall-cap <min>]` for each card unlimited autopilot takes from Ready later, with the grown caps.
|
|
113
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" card <id>` when a card that main runs itself starts.
|
|
114
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" step <id> <step>` as a card that main runs itself enters each of `research`, `tester`, `coder`, `reviewer`, `gate`, `commit`, and `record`, never for a queen card.
|
|
115
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" land <id> --commit <sha>` when the card lands, or `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" stop <id> --reason "<why>"` when it stops.
|
|
116
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" filed <id> --kind <follow-up|user> [--from <card>] --position "<text>"` for each follow-up or user card filed to Backlog.
|
|
117
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" caps --usd <n> --wall <min> --context <pct>` after each cap check on a queen notification.
|
|
118
|
+
- `node "${CLAUDE_PLUGIN_ROOT}/scripts/autopilot.mjs" end` at the closing summary.
|
|
100
119
|
|
|
101
|
-
|
|
120
|
+
A write that fails never stops the run. The live pane (a Claude Code pane, drawn by the doug-flow plugin's hooks module) opens on its own when the run starts and `/doug-autopilot` reopens it; the same run shows on the board page; with no run the pane shows a "No autopilot run." line and the board shows no panel; a finished run shows its last summary.
|
|
102
121
|
|
|
103
122
|
Stop conditions, one per line:
|
|
104
123
|
|
|
@@ -110,14 +129,15 @@ Stop conditions, one per line:
|
|
|
110
129
|
- It stops and asks when a workflow run's report is not ok, on either track.
|
|
111
130
|
- It stops and asks when a workflow run pauses at a human gate, on either track.
|
|
112
131
|
- It stops and asks when cost.mjs reports an unpriced transcript, because the USD cap cannot be checked.
|
|
132
|
+
- It stops and asks when a card queen's run stops; the other cards keep running.
|
|
113
133
|
|
|
114
134
|
It ends with the summary below, one condition per line:
|
|
115
135
|
|
|
116
136
|
- It ends when Ready has no runnable card, under unlimited autopilot.
|
|
117
|
-
- It ends when the user says stop: the
|
|
118
|
-
- It ends when the lead's context is at or above autopilot.contextThreshold in .doug/config.json (default 60):
|
|
137
|
+
- It ends when the user says stop: the in-flight cards are finished or left In flow.
|
|
138
|
+
- It ends when the lead's context is at or above autopilot.contextThreshold in .doug/config.json (default 60): on every queen notification the lead runs `node "${CLAUDE_PLUGIN_ROOT}/scripts/context.mjs"`, which prints the context percentage of the lead's own session transcript, and treats a run that prints no reading as at the threshold.
|
|
119
139
|
- 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
140
|
|
|
121
141
|
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
142
|
|
|
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,
|
|
143
|
+
It waits for the answer, then resumes the queue at the card it stopped on; the other queens keep running while main waits. 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, gate line, and recap; each stopped card and why; each edge case taken conservatively; and the follow-ups filed.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doug-start
|
|
3
|
+
description: Start a new project or a large feature one phase at a time, each phase a committed file behind a human gate. With no argument in a repo with no docs/product/idea.md it walks Idea, Architecture, Walking skeleton, and Scope; with a card id or a goal it walks Specification, optional Pseudocode, Architecture, and Refinement into an approvable plan.
|
|
4
|
+
when_to_use: The user runs /doug-start, wants to begin a project from an idea, or wants a large card specified and designed before it is planned.
|
|
5
|
+
allowed-tools: Agent, AskUserQuestion, Read, Write, Edit, Bash(doug board *), Bash(node *bin.js board *), Bash(node *bin.js init *), Bash(doug init *), Bash(node *plan.mjs *), Bash(node *memory.mjs *), Bash(node *learn.mjs *), Bash(node *board.mjs *), Bash(git *)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# doug-start
|
|
9
|
+
|
|
10
|
+
One skill, two modes, no MCP server and no daemon. Each phase writes one committed file and stops at a human gate (AskUserQuestion). A rerun of a phase shows the diff and asks; it never overwrites silently.
|
|
11
|
+
|
|
12
|
+
Mode and resume are derived from disk, with no state file: a card id or goal argument picks feature mode; no argument and no `docs/product/idea.md` picks project mode; a run resumes at the first missing phase file.
|
|
13
|
+
|
|
14
|
+
An argument that is not a card id on the board, in a repo with no `docs/product/idea.md`, is ambiguous (an idea or a goal): ask at an AskUserQuestion gate (project / feature) unless the conversation already says `project mode` or `feature mode`. Once the mode is project, text the user gave is the idea, not a feature goal. A card id on the board, or any goal once `docs/product/idea.md` exists, picks feature mode.
|
|
15
|
+
|
|
16
|
+
The Walking skeleton writes no phase file; it is done when the `walking-skeleton` card is in Done on the board and `stopGate.commands` in `.doug/config.json` is non-empty, and a run resumes at it until both hold.
|
|
17
|
+
|
|
18
|
+
## Phase rules (both modes)
|
|
19
|
+
|
|
20
|
+
- Only a committed phase file counts as done: a phase file that exists but is uncommitted means that phase is still waiting at its gate, so the next run re-asks that gate, showing the file, and does not move on.
|
|
21
|
+
- A phase file is written by the lead (this session). The spec-writer and researcher agents return text and write nothing.
|
|
22
|
+
- Before writing a phase, check whether its file exists. If it does, write the new text to `.doug/.state/scratch/<name>.new`, show `git diff --no-index` of the two, and ask (AskUserQuestion: replace / keep / revise). Never overwrite a phase file without that answer.
|
|
23
|
+
- The gate after each phase asks AskUserQuestion with the options approve / revise / stop. Approve: commit the phase file (a plain message, no trailers) and go to the next phase. Revise: take the user's note, redraft, show the diff, ask again. Stop: leave the file uncommitted and end; a later `/doug-start` resumes at the first missing phase file.
|
|
24
|
+
- At every gate the lead calls AskUserQuestion, even when the session looks non-interactive; a denied or unanswered call ends the phase there, and the lead does nothing past it (it names the phase that is waiting).
|
|
25
|
+
|
|
26
|
+
## Project mode
|
|
27
|
+
|
|
28
|
+
Start here: if `.doug/config.json` is missing, run `doug init --yes` first. It writes the project's `architect`, `tester`, `coder`, and `reviewer` agents into `.claude/agents/`, which the phases below spawn.
|
|
29
|
+
|
|
30
|
+
### 1. Idea
|
|
31
|
+
|
|
32
|
+
Writes `docs/product/idea.md` (problem, users, success, non-goals). Spawn the spec-writer with the user's idea and ask it for those four sections:
|
|
33
|
+
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<the user's idea verbatim>. Draft docs/product/idea.md with sections: problem, users, success, non-goals. Return the file text." })`
|
|
34
|
+
Write the file, then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
35
|
+
|
|
36
|
+
### 2. Architecture
|
|
37
|
+
|
|
38
|
+
Writes `docs/product/architecture.md` plus the ADR `docs/decisions/0001-stack.md` (through the proposal path, below), from the project's `architect` agent, never drafted by the lead: the stack, the modules, the commands that build and test it, and the one decision the stack rests on. Spawn a researcher for any stack fact that lives outside the repository (a tool version, a package's constraint):
|
|
39
|
+
`Agent({ subagent_type: "doug-flow:researcher", prompt: "<one stack question>. Cite each fact with its source." })`
|
|
40
|
+
Then spawn the architect on the `plan` row's model (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models`; omit `model` when it says `inherit`):
|
|
41
|
+
`Agent({ subagent_type: "architect", model: "<the plan row's model>", prompt: "<the approved idea and the research>. Return the text of docs/product/architecture.md and, separately, the body of the stack ADR without its title line." })`
|
|
42
|
+
Write `docs/product/architecture.md` from its returned text. `docs/decisions/` is proposal-only, so the lead never Writes it directly. Save the ADR body under `.doug/.state/scratch/` and run `node "${CLAUDE_PLUGIN_ROOT}/scripts/memory.mjs" decision propose --title "Stack" --file <body>`, which renders the diff for `docs/decisions/0001-stack.md` under `.doug/.state/learn/` and writes nothing else. Then ask at the AskUserQuestion gate (approve / revise / stop). On approve, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/learn.mjs" apply <proposal-file>`, which writes the ADR, and commit. Unapproved, the proposal stays pending and the ADR does not exist.
|
|
43
|
+
|
|
44
|
+
### 3. Walking skeleton
|
|
45
|
+
|
|
46
|
+
Writes the first card with `doug board add` (id `walking-skeleton`, size S, class code) and builds it: the thinnest program that runs and has one passing test, built tester, then coder, then reviewer (the project's `tester` and `coder` agents on the implement row, the tester first writing the test; the project's `reviewer` agent on the review row). Mandatory: no feature card starts before it, because until then the gates check nothing. When it is built and its tests pass, leave it uncommitted (approval at the gate commits it); then `doug init` re-runs so `stopGate.commands` are detected; confirm `.doug/config.json` lists at least one command. Leave the work uncommitted and the card out of Done until the gate. Ask at the AskUserQuestion gate (approve / revise / stop) with the commands shown. On approve, run `doug board move walking-skeleton done` and commit.
|
|
47
|
+
|
|
48
|
+
### 4. Scope
|
|
49
|
+
|
|
50
|
+
Writes `docs/product/scope.md` (v1 in one paragraph, then one line per card) and the board cards. Spawn the spec-writer with the approved idea and architecture; it returns the text of `scope.md` and the card list as one line per card (id, title, goal, size, class, column):
|
|
51
|
+
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<idea and architecture paths>. Draft docs/product/scope.md and its card list: id, title, goal, size, class, column. Return the file text." })`
|
|
52
|
+
The lead never drafts the scope; it writes the returned `scope.md` and runs `doug board add` only for each card the user approved. A card counts as approved at the gate, or in advance in the conversation. Cards are written with `doug board add`, never by a script editing `.doug/board.json`. Put at most 7 to Ready for v1 and the rest to Backlog, each with a size; a large card then goes through feature mode (`/doug-start <card-id>`). Show the board (`doug board list`) and ask at the AskUserQuestion gate (approve / revise / stop).
|
|
53
|
+
|
|
54
|
+
## Feature mode
|
|
55
|
+
|
|
56
|
+
Files live under `docs/specs/<card-id>/`. A goal with no card id gets a short kebab-case id from the lead, confirmed in the first gate. Read the card first (`doug board list --json`) when one exists.
|
|
57
|
+
|
|
58
|
+
### 1. Specification
|
|
59
|
+
|
|
60
|
+
Writes `spec.md`: what must be true when the work is done, as outcomes a command can check, plus non-goals and open questions.
|
|
61
|
+
`Agent({ subagent_type: "doug-flow:spec-writer", prompt: "<card goal or the user's goal verbatim>. Draft docs/specs/<card-id>/spec.md: outcomes, non-goals, open questions. Return the file text." })`
|
|
62
|
+
Write the file, then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
63
|
+
|
|
64
|
+
### 2. Pseudocode (optional)
|
|
65
|
+
|
|
66
|
+
Writes `pseudocode.md`, only when the user asks for it at this gate (AskUserQuestion: write it / skip). Skip leaves no file and goes to Architecture; a missing `pseudocode.md` counts as skipped once `architecture.md` exists, or when the user said they skipped it; resuming with only `spec.md` asks write / skip once. When written, spawn the spec-writer again for the algorithm in plain steps, and gate it as above.
|
|
67
|
+
|
|
68
|
+
### 3. Architecture
|
|
69
|
+
|
|
70
|
+
Writes `architecture.md` in the shape of the architect agent: modules, files, interfaces, the test for each. Every file it names will be owned by a plan task, so name each exactly. Spawn the project's `architect` agent on the `plan` row's model with `spec.md` (and `pseudocode.md` if present) and the code it touches: `Agent({ subagent_type: "architect", model: "<the plan row's model>", prompt: "<the spec path>. Return the text of docs/specs/<card-id>/architecture.md." })`. The lead writes its returned text to the file and never drafts it. Then ask at the AskUserQuestion gate (approve / revise / stop).
|
|
71
|
+
|
|
72
|
+
### 4. Refinement
|
|
73
|
+
|
|
74
|
+
The planner writes an approvable plan whose tasks own the files `architecture.md` names. Read the `plan` row with `node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" models` and pass its model (omit `model` when it says `inherit`):
|
|
75
|
+
`Agent({ subagent_type: "doug-flow:planner", model: "<the plan row's model>", prompt: "<card goal>. Spec: docs/specs/<card-id>/spec.md. Architecture: docs/specs/<card-id>/architecture.md. Scripts: ${CLAUDE_PLUGIN_ROOT}/scripts. Write .doug/plan.json as a draft and return the rendered plan." })`
|
|
76
|
+
Set the card when the argument was a board card id (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" set card <card-id>`), render it (`node "${CLAUDE_PLUGIN_ROOT}/scripts/plan.mjs" show`) and ask at the AskUserQuestion gate (approve / revise / stop). The plan is approved with `/doug-approve`, by the user; this skill never approves it.
|
|
77
|
+
|
|
78
|
+
### 5. Completion
|
|
79
|
+
|
|
80
|
+
`/doug-implement` and land, unchanged. Tell the user to run `/doug-approve`, then `/doug-implement`.
|
|
@@ -36,7 +36,7 @@ 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. 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.
|
|
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. After the landing block, the lead prints the card's recap, a few lines in plain words: what the task was, what the outcome was, any other important facts (a follow-up worth filing, an edge case taken conservatively, a cost or wall-clock surprise), and, for a new feature, how to use it.
|
|
40
40
|
|
|
41
41
|
## 6. Follow-up cards
|
|
42
42
|
|