@dotdotgod/cli 0.1.43 → 0.1.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/cli",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "CLI for dotdotgod validation, project memory snapshots, and graph indexing.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -41,8 +41,9 @@ export const PLAN_BLOCKER_LABELS = new Map([
41
41
  ['invalid-task-slug', 'Plan task directory must be kebab-case'],
42
42
  ['empty-plan', 'Plan README is empty'],
43
43
  ['missing-stage', 'Missing required stage directory'],
44
- ['missing-internal-stage', 'Missing internal stage file'],
45
44
  ['missing-stage-readme', 'Missing stage README.md'],
45
+ ['missing-internal-stage', 'Missing internal stage checkpoint'],
46
+ ['open-internal-stage', 'Internal stage checkpoint is not completed'],
46
47
  ['invalid-markdown-name', 'Invalid Markdown file name'],
47
48
  ['missing-section', 'Missing required section'],
48
49
  ['empty-section', 'Required section has no content'],
@@ -66,8 +67,9 @@ export function buildPlanValidationNextStagePrompt(planPath, stage, stagePath) {
66
67
  return [
67
68
  `Continue Plan Mode stage advancement for ${planPath}.`,
68
69
  `Current stage: ${stage}.`,
69
- `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.`,
70
- stage === '05-workstream-handoff' ? 'For this stage, record `Split decision: yes` or `Split decision: no` in `## Workstream Handoff`. If splitting, make `## Workstream Map`, `## Workstreams`, and `## Integration Sequence` describe multiple actionable workstreams with required context, allowed/forbidden edits, tasks, validation, dependencies, and handoff output. If not splitting, record the no-split rationale and keep `## Todo Contract` actionable so Pi can extract execution todos.' : undefined,
70
+ `Update the durable plan for human readers, then make ${stagePath} the machine-readable checkpoint that satisfies the current stage validation contract. Do not create later checkpoint files yet.`,
71
+ `In ${stagePath}, set Status: completed and include the current stage required sections plus \`## Stage ${stage.slice(0, 2)} Construction Checklist\` rows in the canonical \`- [x] Category: evidence\` form.`,
72
+ stage === '05-workstream-handoff' ? 'For this stage checkpoint, record `Split decision: yes` or `Split decision: no` in `## Workstream Handoff`. If splitting, make `## Workstream Map`, `## Workstreams`, and `## Integration Sequence` describe multiple actionable workstreams with required context, allowed/forbidden edits, tasks, validation, dependencies, and handoff output. If not splitting, record the no-split rationale and keep `## Todo Contract` actionable so Pi can extract execution todos.' : undefined,
71
73
  `After completing this stage, stop and rerun \`dotdotgod plan validate ${planPath} --stage ${stage} --json\`.`,
72
74
  ].filter(Boolean).join('\n\n');
73
75
  }
@@ -201,17 +201,37 @@ function readInternalStageState(taskDir, stageName) {
201
201
 
202
202
  function validateInternalStageState(root, taskDir, stageName, blockers) {
203
203
  const state = readInternalStageState(taskDir, stageName);
204
- if (!state) return;
204
+ const expectedPath = internalStageFile(taskDir, stageName);
205
+ const rel = projectRelative(root, state?.file ?? expectedPath);
206
+ if (!state) {
207
+ addBlocker(blockers, 'missing-internal-stage', `Missing internal stage checkpoint: ${rel}`, { path: rel, stage: stageName });
208
+ return { state: undefined, valid: false };
209
+ }
210
+
211
+ let valid = true;
205
212
  const stage = contentMatch(state.content, /^Stage:\s*(\S+)\s*$/m);
206
213
  const status = contentMatch(state.content, /^Status:\s*(created|blocked|completed)\s*$/m);
207
214
  const updated = contentMatch(state.content, /^Updated:\s*(\S+)\s*$/m);
208
- const rel = projectRelative(root, state.file);
209
- const looksLikeGeneratorState = /^Stage:\s*\S+\s*$/m.test(state.content) || /^Status:\s*\S+\s*$/m.test(state.content) || /^Updated:\s*\S+\s*$/m.test(state.content);
210
- if (!looksLikeGeneratorState) return;
211
- if (stage && stage !== stageName) addBlocker(blockers, 'missing-internal-stage', `Internal stage file has wrong Stage field: ${rel}`, { path: rel, stage: stageName });
212
- if (!stage) addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing Stage: ${rel}`, { path: rel, stage: stageName });
213
- if (!status) addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing valid Status: ${rel}`, { path: rel, stage: stageName });
214
- if (!updated) addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing Updated: ${rel}`, { path: rel, stage: stageName });
215
+ if (stage && stage !== stageName) {
216
+ valid = false;
217
+ addBlocker(blockers, 'missing-internal-stage', `Internal stage file has wrong Stage field: ${rel}`, { path: rel, stage: stageName });
218
+ }
219
+ if (!stage) {
220
+ valid = false;
221
+ addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing Stage: ${rel}`, { path: rel, stage: stageName });
222
+ }
223
+ if (!status) {
224
+ valid = false;
225
+ addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing valid Status: ${rel}`, { path: rel, stage: stageName });
226
+ } else if (status !== 'completed') {
227
+ valid = false;
228
+ addBlocker(blockers, 'open-internal-stage', `Internal stage checkpoint is not completed: ${rel}`, { path: rel, stage: stageName });
229
+ }
230
+ if (!updated) {
231
+ valid = false;
232
+ addBlocker(blockers, 'missing-internal-stage', `Internal stage file is missing Updated: ${rel}`, { path: rel, stage: stageName });
233
+ }
234
+ return { state, valid };
215
235
  }
216
236
 
217
237
  function contentMatch(content, pattern) {
@@ -492,28 +512,28 @@ export function validatePlanArtifact(planPath, options = {}) {
492
512
  const durableFiles = discoverDurablePlanMarkdownFiles(taskDir);
493
513
  if (durableFiles.length > 1) summary.splitFiles.detected = true;
494
514
  for (const stageName of stagesToValidate) {
495
- validateInternalStageState(root, taskDir, stageName, blockers);
496
- const files = durableFiles.length > 0 ? durableFiles : [absolutePlanPath];
497
- const stageReadme = absolutePlanPath;
498
- summary.stages.present += 1;
499
- let stageValid = true;
515
+ const internal = validateInternalStageState(root, taskDir, stageName, blockers);
516
+ summary.stages.present += internal.state ? 1 : 0;
517
+ let stageValid = internal.valid;
518
+ const files = internal.state ? [internal.state.file] : [];
519
+ const fallbackPath = projectRelative(root, internalStageFile(taskDir, stageName));
500
520
  for (const header of PLAN_REQUIRED_HEADERS[stageName]) {
501
- const found = findSection(files, header);
521
+ const found = files.length > 0 ? findSection(files, header) : undefined;
502
522
  if (!found) {
503
523
  stageValid = false;
504
- addBlocker(blockers, 'missing-section', `Missing required section: ## ${header}`, { path: projectRelative(root, stageReadme), stage: stageName, section: header });
524
+ addBlocker(blockers, 'missing-section', `Missing required section in internal checkpoint: ## ${header}`, { path: fallbackPath, stage: stageName, section: header });
505
525
  continue;
506
526
  }
507
527
  summary.requiredSections.present += 1;
508
528
  const relFound = projectRelative(root, found.file);
509
529
  if (!contentHasValue(found.content)) {
510
530
  stageValid = false;
511
- addBlocker(blockers, 'empty-section', `Required section has no content: ## ${header}`, { path: relFound, stage: stageName, section: header });
531
+ addBlocker(blockers, 'empty-section', `Required checkpoint section has no content: ## ${header}`, { path: relFound, stage: stageName, section: header });
512
532
  continue;
513
533
  }
514
534
  if (isPlaceholderContent(found.content)) {
515
535
  stageValid = false;
516
- addBlocker(blockers, 'placeholder-section', `Required section only contains placeholder content: ## ${header}`, { path: relFound, stage: stageName, section: header });
536
+ addBlocker(blockers, 'placeholder-section', `Required checkpoint section only contains placeholder content: ## ${header}`, { path: relFound, stage: stageName, section: header });
517
537
  continue;
518
538
  }
519
539
  summary.requiredSections.valid += 1;
@@ -541,8 +561,8 @@ export function validatePlanArtifact(planPath, options = {}) {
541
561
  addBlocker(blockers, 'unresolved-assumption', 'Assumptions contain unresolved items.', { path: relFound, stage: stageName, section: header });
542
562
  }
543
563
  }
544
- if (!validateWorkstreamHandoffContract(root, files, stageName, blockers)) stageValid = false;
545
- if (!validateConstructionChecklist(root, files, stageName, blockers)) stageValid = false;
564
+ if (files.length > 0 && !validateWorkstreamHandoffContract(root, files, stageName, blockers)) stageValid = false;
565
+ if (files.length > 0 && !validateConstructionChecklist(root, files, stageName, blockers)) stageValid = false;
546
566
  if (stageValid) summary.stages.valid += 1;
547
567
  }
548
568
 
@@ -35,6 +35,7 @@ export function runValidate(argv) {
35
35
  if (path.includes('/node_modules') || path === 'node_modules') return true;
36
36
  if (path.includes('/.git') || path === '.git') return true;
37
37
  if (path === CACHE_DIR || path.startsWith(`${CACHE_DIR}/`)) return true;
38
+ if (/^docs\/(?:plan|archive\/plan)\/[a-z0-9]+(?:-[a-z0-9]+)*\/\.dotdotgod-plan(?:\/|$)/.test(path)) return true;
38
39
  if (!options.includeLocalMemory && (path === 'docs/plan' || path.startsWith('docs/plan/') || path === 'docs/archive' || path.startsWith('docs/archive/'))) return true;
39
40
  return false;
40
41
  };