@dotdotgod/cli 0.1.35 → 0.1.36

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/README.md CHANGED
@@ -32,6 +32,7 @@ Command-line tools for dotdotgod project memory. The CLI turns explicit, maintai
32
32
  - Initialize `AGENTS.md`, thin agent entrypoints, docs indexes, active-plan space, archive map, and local cache ignores with `dotdotgod init`.
33
33
  - Replace ad-hoc docs checks with `dotdotgod validate`.
34
34
  - Keep generated Markdown traceability links synchronized with `dotdotgod traceability links`.
35
+ - Validate executable active plans with `dotdotgod plan validate docs/plan/<task-slug>/README.md`.
35
36
  - Build `.dotdotgod/` as a local ignored cache of file fingerprints and compact graph shards derived from maintained project links.
36
37
  - Use `load-snapshot` as a bounded first-pass map for high-quality agent loading.
37
38
  - Resolve explicit `[[...]]` references or high-signal fuzzy references before broad text search.
@@ -56,6 +57,7 @@ dotdotgod expand . "Update [[PLAN_MODE]] and [[HOOKS]]"
56
57
  dotdotgod expand . "PLAN_MODE 수정하자" --fuzzy
57
58
  dotdotgod traceability links . --check
58
59
  dotdotgod traceability links . --write
60
+ dotdotgod plan validate docs/plan/<task-slug>/README.md
59
61
  dotdotgod graph impact . --changed <path>
60
62
  dotdotgod graph impact . --changed <path> --compact
61
63
  dotdotgod graph impact . --changed <path> --yml
@@ -71,6 +73,7 @@ dotdotgod graph communities .
71
73
  | `dotdotgod validate <root>` | Checks dotdotgod docs/project-memory structure, local links, traceability blocks, generated traceability-link drift, config validity, and optional index freshness. |
72
74
  | `dotdotgod traceability links <root> --check` | Runs only the generated traceability-link and compact JSON drift check. |
73
75
  | `dotdotgod traceability links <root> --write` | Repairs generated traceability-link sections and rewrites canonical `json dotdotgod` blocks as compact JSON. |
76
+ | `dotdotgod plan validate <plan-readme>` | Checks canonical eight-stage active-plan structure, required Markdown sections, role/area workstreams, decision/assumption blockers, and atomic-task verification before execution. |
74
77
  | `pnpm run verify` | Runs the repository-level quality gate: generated-resource checks, package verify contracts, tests, typecheck, and docs validation where each package defines it. |
75
78
 
76
79
  Most users start with `validate`. Use `traceability links --write` only when generated traceability output needs repair, and use `verify` before release-style handoff.
package/bin/dotdotgod.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import { runCli } from '../src/core.mjs';
3
3
 
4
- runCli();
4
+ await runCli();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/cli",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "CLI for dotdotgod validation, project memory snapshots, and graph indexing.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
package/src/cli/usage.mjs CHANGED
@@ -34,12 +34,43 @@ Create dotdotgod.config.json with the built-in default memory, traceability, val
34
34
  case 'load-snapshot':
35
35
  return `Usage:
36
36
  dotdotgod load-snapshot <root> [--json]`;
37
+ case 'plan':
38
+ return `Usage:
39
+ dotdotgod plan validate docs/plan/<task-slug>/README.md [--stage stage] [--json]
40
+ dotdotgod plan stage create <stage> [docs/plan/<task-slug>/README.md] [--json]`;
41
+ case 'plan validate':
42
+ return `Usage:
43
+ dotdotgod plan validate docs/plan/<task-slug>/README.md [--stage stage] [--json]
44
+
45
+ Validates an active plan artifact before execution. Supports simplified Plan Generator stages and optional .dotdotgod-plan/NN_STAGE_NAME.md workspaces. Use --stage with a stage name or numeric prefix such as 04 or 05 to validate only that stage.`;
46
+ case 'plan stage':
47
+ return `Usage:
48
+ dotdotgod plan stage create <stage> [docs/plan/<task-slug>/README.md] [--json]`;
49
+ case 'plan stage create':
50
+ return `Usage:
51
+ dotdotgod plan stage create <stage> [docs/plan/<task-slug>/README.md] [--json]
52
+
53
+ Creates the matching .dotdotgod-plan/NN_STAGE_NAME.md internal stage checkpoint for a simplified Plan Generator stage. <stage> accepts a numeric prefix such as 02 or a canonical stage name such as 02-context-load. If the plan path is omitted, exactly one active docs/plan/<task-slug>/README.md candidate must exist.`;
37
54
  case 'resolve':
38
55
  return `Usage:
39
56
  dotdotgod resolve <root> <ref> [--max-results n] [--include-archive] [--json]`;
40
57
  case 'expand':
41
58
  return `Usage:
42
59
  dotdotgod expand <root> <prompt> [--max-results n] [--include-archive] [--with-impact] [--fuzzy] [--json]`;
60
+ case 'trello':
61
+ return `Usage:
62
+ dotdotgod trello sync <root> [--dry-run]`;
63
+ case 'trello sync':
64
+ return `Usage:
65
+ dotdotgod trello sync <root> [--dry-run]
66
+
67
+ Options:
68
+ --dry-run Preview planned Trello updates without calling Trello APIs.
69
+
70
+ Write mode:
71
+ Omit --dry-run only in the trusted GitHub Actions default-branch push workflow.
72
+ Requires TRELLO_API_KEY and TRELLO_TOKEN in the trusted workflow environment.
73
+ Local/manual and pull request usage should run --dry-run.`;
43
74
  case 'traceability':
44
75
  return `Usage:
45
76
  dotdotgod traceability links <root> [--check|--write] [--json]`;
@@ -75,6 +106,9 @@ Ranks nodes related to a changed file. <root> is the project root; --changed is
75
106
  dotdotgod resolve <root> <ref> [--max-results n] [--include-archive] [--json]
76
107
  dotdotgod expand <root> <prompt> [--max-results n] [--include-archive] [--with-impact] [--fuzzy] [--json]
77
108
  dotdotgod traceability links <root> [--check|--write] [--json]
109
+ dotdotgod plan validate docs/plan/<task-slug>/README.md [--stage stage] [--json]
110
+ dotdotgod plan stage create <stage> [docs/plan/<task-slug>/README.md] [--json]
111
+ dotdotgod trello sync <root> [--dry-run]
78
112
  dotdotgod graph impact <root> --changed <path> [--compact|--json|--yml|--yaml]
79
113
  dotdotgod graph communities <root> [--json]`;
80
114
  }
@@ -120,7 +154,11 @@ export function printVersion() {
120
154
  export function helpCommandFromArgs(args) {
121
155
  const nonHelp = args.filter((arg) => !isHelpToken(arg));
122
156
  if (nonHelp[0] === 'graph' && nonHelp[1]) return `graph ${nonHelp[1]}`;
157
+ if (nonHelp[0] === 'trello' && nonHelp[1]) return `trello ${nonHelp[1]}`;
123
158
  if (nonHelp[0] === 'traceability' && nonHelp[1]) return `traceability ${nonHelp[1]}`;
159
+ if (nonHelp[0] === 'plan' && nonHelp[1] === 'stage' && nonHelp[2] === 'create') return 'plan stage create';
160
+ if (nonHelp[0] === 'plan' && nonHelp[1] === 'stage') return 'plan stage';
161
+ if (nonHelp[0] === 'plan' && nonHelp[1]) return `plan ${nonHelp[1]}`;
124
162
  if (nonHelp[0] === 'config' && nonHelp[1] === 'init') return 'config init';
125
163
  return nonHelp[0] ?? 'root';
126
164
  }
@@ -0,0 +1,85 @@
1
+ import { existsSync, statSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { usage } from '../cli/usage.mjs';
4
+ import { defaultDotdotgodConfigText, memoryConfigSummary, readMemoryConfig } from '../memory/config.mjs';
5
+
6
+ function parseConfigOptions(argv, allowForce = false, usageKey = 'config') {
7
+ const options = { root: '.', json: false, force: false };
8
+ let rootSet = false;
9
+ for (let i = 0; i < argv.length; i += 1) {
10
+ const arg = argv[i];
11
+ if (arg === '--json') options.json = true;
12
+ else if (allowForce && arg === '--force') options.force = true;
13
+ else if (!arg.startsWith('-') && !rootSet) {
14
+ options.root = arg;
15
+ rootSet = true;
16
+ } else if (!arg.startsWith('-')) usage(`Unexpected argument: ${arg}`, usageKey);
17
+ else usage(`Unknown option: ${arg}`, usageKey);
18
+ }
19
+ options.root = resolve(options.root);
20
+ return options;
21
+ }
22
+
23
+ function configSourcePath(root, source) {
24
+ return source && source !== 'default' ? join(root, source) : null;
25
+ }
26
+
27
+ function configInitError(options, code, message, path = null) {
28
+ const payload = { ok: false, command: 'config init', root: options.root, path, created: false, overwritten: false, error: { code, message } };
29
+ if (options.json) {
30
+ console.log(JSON.stringify(payload, null, 2));
31
+ process.exit(2);
32
+ }
33
+ console.error(message);
34
+ process.exit(2);
35
+ }
36
+
37
+ function formatConfigOutput(payload) {
38
+ const errors = payload.errors ?? [];
39
+ const lines = [`dotdotgod config: ${payload.source}${errors.length > 0 ? ' (invalid; using defaults)' : ''}`];
40
+ lines.push(`- path: ${payload.path ?? 'none'}`);
41
+ lines.push(`- memory areas: ${payload.config.areas.length}`);
42
+ lines.push(`- traceability required: ${(payload.config.traceability.required ?? []).join(', ') || 'none'}`);
43
+ lines.push(`- traceability exclude: ${(payload.config.traceability.exclude ?? []).join(', ') || 'none'}`);
44
+ lines.push(`- validation markdown: maxLines=${payload.config.validation.markdown.maxLines}, maxChars=${payload.config.validation.markdown.maxChars}`);
45
+ lines.push(`- validation markdown exclude: ${(payload.config.validation.markdown.exclude ?? []).join(', ') || 'none'}`);
46
+ lines.push(`- impact ranking preset: ${payload.config.impactRanking.preset}`);
47
+ lines.push(`- trello sync paths: ${(payload.config.integrations?.trello?.syncPaths ?? []).join(', ') || 'none'}`);
48
+ const lowSignal = payload.config.referenceExpansion?.fuzzy?.lowSignal ?? { terms: [], add: [], remove: [] };
49
+ lines.push(`- fuzzy low-signal terms: ${(lowSignal.terms ?? []).join(', ') || 'none'} (add: ${(lowSignal.add ?? []).join(', ') || 'none'}; remove: ${(lowSignal.remove ?? []).join(', ') || 'none'})`);
50
+ if (errors.length > 0) {
51
+ lines.push('- errors:');
52
+ for (const error of errors) lines.push(` - ${error.code} ${error.file}: ${error.message}`);
53
+ }
54
+ return lines.join('\n');
55
+ }
56
+
57
+ export function runConfig(argv) {
58
+ const isInit = argv[0] === 'init';
59
+ const options = parseConfigOptions(isInit ? argv.slice(1) : argv, isInit, isInit ? 'config init' : 'config');
60
+ if (isInit) {
61
+ if (!existsSync(options.root)) configInitError(options, 'ROOT_NOT_FOUND', `Project root not found: ${options.root}`);
62
+ try {
63
+ if (!statSync(options.root).isDirectory()) configInitError(options, 'ROOT_NOT_DIRECTORY', `Project root is not a directory: ${options.root}`);
64
+ } catch {
65
+ configInitError(options, 'ROOT_NOT_FOUND', `Project root not found: ${options.root}`);
66
+ }
67
+ const target = join(options.root, 'dotdotgod.config.json');
68
+ const rcPath = join(options.root, '.dotdotgodrc.json');
69
+ if (existsSync(rcPath)) configInitError(options, 'CONFIG_RC_EXISTS', `.dotdotgodrc.json already exists; remove or migrate it before initializing dotdotgod.config.json.`, rcPath);
70
+ const existed = existsSync(target);
71
+ if (existed && !options.force) configInitError(options, 'CONFIG_EXISTS', `dotdotgod.config.json already exists. Re-run with --force to overwrite it.`, target);
72
+ writeFileSync(target, defaultDotdotgodConfigText());
73
+ const payload = { ok: true, command: 'config init', root: options.root, path: target, created: !existed, overwritten: existed };
74
+ if (options.json) console.log(JSON.stringify(payload, null, 2));
75
+ else console.log(`dotdotgod config init: ${existed ? 'overwrote' : 'created'} ${target}`);
76
+ return;
77
+ }
78
+
79
+ const config = readMemoryConfig(options.root);
80
+ const errors = config.errors ?? [];
81
+ const payload = { ok: errors.length === 0, command: 'config', root: options.root, source: config.source ?? 'default', path: configSourcePath(options.root, config.source), config: memoryConfigSummary(config), errors };
82
+ if (options.json) console.log(JSON.stringify(payload, null, 2));
83
+ else console.log(formatConfigOutput(payload));
84
+ if (errors.length > 0) process.exit(1);
85
+ }
@@ -0,0 +1,66 @@
1
+ import { commandUsage, parseCommon, usage } from '../cli/usage.mjs';
2
+ import { buildCommunities } from '../graph/communities.mjs';
3
+ import { graphSummary, readFreshIndex } from '../index/cache.mjs';
4
+ import { buildCompactImpactReport, buildImpactReport } from '../impact/report.mjs';
5
+ import { formatCompactImpactOutput, formatYmlGraphImpactError, formatYmlImpactOutput } from '../impact/format.mjs';
6
+
7
+ export function parseGraphOptions(argv) {
8
+ const filtered = [];
9
+ let changed;
10
+ let compact = false;
11
+ let yml = false;
12
+ for (let i = 0; i < argv.length; i += 1) {
13
+ if (argv[i] === '--changed') {
14
+ const next = argv[i + 1];
15
+ if (next && !next.startsWith('-')) {
16
+ changed = next;
17
+ i += 1;
18
+ }
19
+ } else if (argv[i] === '--compact') compact = true;
20
+ else if (argv[i] === '--yml' || argv[i] === '--yaml') yml = true;
21
+ else filtered.push(argv[i]);
22
+ }
23
+ const options = parseCommon(filtered);
24
+ options.changed = changed;
25
+ options.compact = compact;
26
+ options.yml = yml;
27
+ return options;
28
+ }
29
+
30
+ export function runGraph(argv) {
31
+ const sub = argv[0];
32
+ const isImpact = sub === 'impact';
33
+ if (!['impact', 'communities'].includes(sub)) usage(sub ? `Unknown graph command: ${sub}` : 'Missing graph command.', 'graph');
34
+ const options = parseGraphOptions(argv.slice(1));
35
+ if (isImpact && [options.json, options.compact, options.yml].filter(Boolean).length > 1) {
36
+ const message = 'Choose only one graph impact output mode: --compact, --json, or --yml/--yaml.';
37
+ if (options.json) console.log(JSON.stringify({ ok: false, command: 'graph impact', compact: options.compact || undefined, yml: options.yml || undefined, root: options.root, error: { code: 'OUTPUT_MODE_CONFLICT', message }, usage: commandUsage('graph impact') }, null, 2));
38
+ else if (options.yml) console.log(formatYmlGraphImpactError(options, message, 'OUTPUT_MODE_CONFLICT'));
39
+ else usage(message, 'graph impact');
40
+ process.exit(2);
41
+ }
42
+ if (isImpact && !options.changed) {
43
+ const message = 'Missing required option: --changed <path>. Run `dotdotgod graph impact <root> --changed <path>`.';
44
+ if (options.json) {
45
+ console.log(JSON.stringify({ ok: false, command: 'graph impact', compact: options.compact || undefined, root: options.root, error: { code: 'MISSING_CHANGED', message }, usage: commandUsage('graph impact') }, null, 2));
46
+ process.exit(2);
47
+ }
48
+ if (options.yml) {
49
+ console.log(formatYmlGraphImpactError(options, message, 'MISSING_CHANGED'));
50
+ process.exit(2);
51
+ }
52
+ usage(message, 'graph impact');
53
+ }
54
+ const { status, index, metadata } = readFreshIndex(options.root);
55
+ const rawImpact = isImpact ? buildImpactReport(index, options.changed) : undefined;
56
+ const impact = isImpact && (options.compact || options.yml) ? buildCompactImpactReport(rawImpact) : rawImpact;
57
+ const payload = isImpact
58
+ ? { ok: status.ok, command: 'graph impact', compact: options.compact || undefined, root: options.root, status, metadata, changed: options.changed, related: impact.related, impact }
59
+ : { ok: status.ok, command: 'graph communities', root: options.root, status, metadata, graph: graphSummary(index), communities: buildCommunities(index) };
60
+ const refreshNote = metadata.cacheRefreshed ? ', refreshed' : '';
61
+ if (options.json) console.log(JSON.stringify(payload, null, 2));
62
+ else if (isImpact && options.yml) console.log(formatYmlImpactOutput(payload, impact));
63
+ else if (isImpact && options.compact) console.log(formatCompactImpactOutput(payload, impact));
64
+ else if (isImpact) console.log(`graph impact: ${payload.related.length} related node(s), ${impact.omittedRelated ?? 0} omitted (${status.status}${refreshNote} index)`);
65
+ else console.log(`graph communities: ${payload.communities.communities.length}/${payload.communities.total} shown, ${payload.communities.omitted} omitted (${status.status}${refreshNote} index)`);
66
+ }
@@ -0,0 +1,40 @@
1
+ import { parseCommon } from '../cli/usage.mjs';
2
+ import { buildCommunities } from '../graph/communities.mjs';
3
+ import { memoryConfigSummary, readMemoryConfig } from '../memory/config.mjs';
4
+ import { buildMemoryAreas, detectCommandGuidance } from '../load-snapshot/summary.mjs';
5
+ import { graphSummary, readFreshIndex } from '../index/cache.mjs';
6
+
7
+ export function runLoadSnapshot(argv) {
8
+ const options = parseCommon(argv);
9
+ const { status, index, metadata } = readFreshIndex(options.root);
10
+ const summary = graphSummary(index);
11
+ const communities = buildCommunities(index, { communities: 5, items: 5 });
12
+ const memoryAreas = buildMemoryAreas(index, { items: 4 });
13
+ const memoryConfig = index.memoryConfig ?? memoryConfigSummary(readMemoryConfig(options.root));
14
+ const memoryPolicy = {
15
+ source: memoryConfig.source ?? 'default',
16
+ sharedAreas: (memoryConfig.areas ?? []).filter((area) => area.scope === 'shared').map((area) => area.id),
17
+ localAreas: (memoryConfig.areas ?? []).filter((area) => area.scope === 'local').map((area) => area.id),
18
+ freshAreas: (memoryConfig.areas ?? []).filter((area) => area.freshness === 'fresh').map((area) => area.id),
19
+ staleAreas: (memoryConfig.areas ?? []).filter((area) => area.freshness === 'stale').map((area) => area.id),
20
+ };
21
+ const bounds = { communities: 5, communityItems: 5, memoryAreaItems: 4, fullGraphIncluded: false, archiveBodiesIncluded: status.archiveBodiesIncluded, archiveMapIncluded: true };
22
+ const quality = {
23
+ indexedFiles: status.indexedFiles,
24
+ currentFiles: status.currentFiles,
25
+ shownCommunities: communities.communities.length,
26
+ totalCommunities: communities.total,
27
+ omittedCommunities: communities.omitted,
28
+ omittedCommunityItems: communities.communities.reduce((sum, community) => sum + (community.omitted ?? 0), 0),
29
+ memoryAreas: memoryAreas.total,
30
+ omittedMemoryAreaItems: memoryAreas.areas.reduce((sum, area) => sum + (area.omitted ?? 0), 0),
31
+ graphNodes: summary.nodes,
32
+ graphEdges: summary.edges,
33
+ };
34
+ const commandGuidance = detectCommandGuidance(options.root);
35
+ let payload = { root: options.root, cache: status, metadata, graph: summary, memoryConfig, memoryPolicy, memoryAreas, communities, bounds, quality, commandGuidance };
36
+ const serialized = JSON.stringify(payload);
37
+ payload = { ...payload, quality: { ...quality, snapshotBytes: Buffer.byteLength(serialized), approxSnapshotTokens: Math.ceil(serialized.length / 4) } };
38
+ if (options.json) console.log(JSON.stringify(payload, null, 2));
39
+ else console.log(`dotdotgod load snapshot\n- cache: ${status.status}${metadata.cacheRefreshed ? ' (refreshed)' : ''}\n- indexed files: ${status.indexedFiles}\n- current files: ${status.currentFiles}\n- archive bodies included: ${status.archiveBodiesIncluded ? 'yes' : 'no'}\n- graph: ${summary.nodes} nodes, ${summary.edges} edges\n- memory areas: ${memoryAreas.areas.length}/${memoryAreas.total} shown\n- communities: ${communities.communities.length}/${communities.total} shown, ${communities.omitted} omitted`);
40
+ }
@@ -0,0 +1,63 @@
1
+ export const PLAN_STAGE_DIRECTORIES = [
2
+ '01-intake',
3
+ '02-context-load',
4
+ '03-discovery',
5
+ '04-plan',
6
+ ];
7
+
8
+ export const PLAN_INTERNAL_WORKSPACE = '.dotdotgod-plan';
9
+
10
+ export const PLAN_INTERNAL_STAGE_DIRECTORIES = [
11
+ ...PLAN_STAGE_DIRECTORIES,
12
+ '05-workstream-handoff',
13
+ ];
14
+
15
+ export const PLAN_REQUIRED_HEADERS = {
16
+ '01-intake': ['Request Summary', 'Goal', 'Scope', 'Non-goals', 'Constraints', 'Assumptions'],
17
+ '02-context-load': ['Memory Reads', 'Impact Candidates', 'Related Files'],
18
+ '03-discovery': ['Findings', 'Risks', 'Open Questions'],
19
+ '04-plan': ['Milestones', 'Atomic Tasks', 'Validation Plan', 'Resume Point'],
20
+ '05-workstream-handoff': ['Workstream Handoff', 'Todo Contract'],
21
+ };
22
+
23
+ export const PLAN_CONSTRUCTION_CHECKLISTS = {
24
+ '01-intake': ['Requirements', 'Constraints', 'Failure paths', 'Data shape', 'Verification', 'Integration impact'],
25
+ '02-context-load': ['Memory reads', 'Impact candidates', 'Related files', 'Boundary risk'],
26
+ '03-discovery': ['Findings', 'Risks', 'Open questions', 'Extension points'],
27
+ '04-plan': ['Milestones', 'Atomic tasks', 'Validation plan', 'Resume point'],
28
+ '05-workstream-handoff': ['Handoffs', 'Do-not rules', 'Focused verification', 'Chat-independent context'],
29
+ };
30
+
31
+ export const PLAN_BLOCKER_LABELS = new Map([
32
+ ['missing-plan', 'Plan file does not exist'],
33
+ ['invalid-plan-path', 'Plan file must be docs/plan/<task-slug>/README.md'],
34
+ ['invalid-task-slug', 'Plan task directory must be kebab-case'],
35
+ ['empty-plan', 'Plan README is empty'],
36
+ ['missing-stage', 'Missing required stage directory'],
37
+ ['missing-internal-stage', 'Missing internal stage file'],
38
+ ['missing-stage-readme', 'Missing stage README.md'],
39
+ ['invalid-markdown-name', 'Invalid Markdown file name'],
40
+ ['missing-section', 'Missing required section'],
41
+ ['empty-section', 'Required section has no content'],
42
+ ['placeholder-section', 'Required section only contains placeholder content'],
43
+ ['pending-workstream', 'Required workstream is pending'],
44
+ ['unresolved-discussion', 'Discussion Queue has unresolved items'],
45
+ ['unresolved-assumption', 'Assumptions contain unresolved items'],
46
+ ['missing-atomic-acceptance', 'Atomic Tasks must define acceptance criteria'],
47
+ ['missing-atomic-verification', 'Atomic Tasks must define verification'],
48
+ ['missing-construction-checklist', 'Missing construction checklist'],
49
+ ['missing-checklist-item', 'Missing construction checklist item'],
50
+ ['malformed-checklist-item', 'Malformed construction checklist item'],
51
+ ['open-checklist-item', 'Construction checklist item is open'],
52
+ ['placeholder-checklist-item', 'Construction checklist item only contains placeholder content'],
53
+ ]);
54
+
55
+ export function buildPlanValidationNextStagePrompt(planPath, stage, stagePath) {
56
+ return [
57
+ `Continue Plan Mode stage advancement for ${planPath}.`,
58
+ `Current stage: ${stage}.`,
59
+ `Make ${planPath} satisfy the current stage's durable README requirements first. Use ${stagePath} only as an optional checkpoint/workflow note for this stage; do not make numbered checkpoint creation the goal and do not create later checkpoint files yet.`,
60
+ stage === '05-workstream-handoff' ? 'For this stage, ensure the durable README or split UPPER_SNAKE_CASE workstream plan files expose numbered `Plan:` sections so Pi can extract todos during execution. For small plans, explicitly state that no split is needed.' : undefined,
61
+ `After completing this stage, stop and rerun \`dotdotgod plan validate ${planPath} --stage ${stage} --json\`.`,
62
+ ].filter(Boolean).join('\n\n');
63
+ }