gsd-opencode 1.22.1 → 1.33.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.
Files changed (188) hide show
  1. package/agents/gsd-advisor-researcher.md +112 -0
  2. package/agents/gsd-assumptions-analyzer.md +110 -0
  3. package/agents/gsd-codebase-mapper.md +0 -2
  4. package/agents/gsd-debugger.md +117 -2
  5. package/agents/gsd-doc-verifier.md +207 -0
  6. package/agents/gsd-doc-writer.md +608 -0
  7. package/agents/gsd-executor.md +45 -4
  8. package/agents/gsd-integration-checker.md +0 -2
  9. package/agents/gsd-nyquist-auditor.md +0 -2
  10. package/agents/gsd-phase-researcher.md +191 -5
  11. package/agents/gsd-plan-checker.md +152 -5
  12. package/agents/gsd-planner.md +131 -157
  13. package/agents/gsd-project-researcher.md +28 -3
  14. package/agents/gsd-research-synthesizer.md +0 -2
  15. package/agents/gsd-roadmapper.md +29 -2
  16. package/agents/gsd-security-auditor.md +129 -0
  17. package/agents/gsd-ui-auditor.md +485 -0
  18. package/agents/gsd-ui-checker.md +305 -0
  19. package/agents/gsd-ui-researcher.md +368 -0
  20. package/agents/gsd-user-profiler.md +173 -0
  21. package/agents/gsd-verifier.md +207 -22
  22. package/commands/gsd/gsd-add-backlog.md +76 -0
  23. package/commands/gsd/gsd-analyze-dependencies.md +34 -0
  24. package/commands/gsd/gsd-audit-uat.md +24 -0
  25. package/commands/gsd/gsd-autonomous.md +45 -0
  26. package/commands/gsd/gsd-cleanup.md +5 -0
  27. package/commands/gsd/gsd-debug.md +29 -21
  28. package/commands/gsd/gsd-discuss-phase.md +15 -36
  29. package/commands/gsd/gsd-do.md +30 -0
  30. package/commands/gsd/gsd-docs-update.md +48 -0
  31. package/commands/gsd/gsd-execute-phase.md +24 -2
  32. package/commands/gsd/gsd-fast.md +30 -0
  33. package/commands/gsd/gsd-forensics.md +56 -0
  34. package/commands/gsd/gsd-help.md +2 -0
  35. package/commands/gsd/gsd-join-discord.md +2 -1
  36. package/commands/gsd/gsd-list-workspaces.md +19 -0
  37. package/commands/gsd/gsd-manager.md +40 -0
  38. package/commands/gsd/gsd-milestone-summary.md +51 -0
  39. package/commands/gsd/gsd-new-project.md +4 -0
  40. package/commands/gsd/gsd-new-workspace.md +44 -0
  41. package/commands/gsd/gsd-next.md +24 -0
  42. package/commands/gsd/gsd-note.md +34 -0
  43. package/commands/gsd/gsd-plan-phase.md +8 -1
  44. package/commands/gsd/gsd-plant-seed.md +28 -0
  45. package/commands/gsd/gsd-pr-branch.md +25 -0
  46. package/commands/gsd/gsd-profile-user.md +46 -0
  47. package/commands/gsd/gsd-quick.md +7 -3
  48. package/commands/gsd/gsd-reapply-patches.md +178 -45
  49. package/commands/gsd/gsd-remove-workspace.md +26 -0
  50. package/commands/gsd/gsd-research-phase.md +7 -12
  51. package/commands/gsd/gsd-review-backlog.md +62 -0
  52. package/commands/gsd/gsd-review.md +38 -0
  53. package/commands/gsd/gsd-secure-phase.md +35 -0
  54. package/commands/gsd/gsd-session-report.md +19 -0
  55. package/commands/gsd/gsd-set-profile.md +24 -23
  56. package/commands/gsd/gsd-ship.md +23 -0
  57. package/commands/gsd/gsd-stats.md +18 -0
  58. package/commands/gsd/gsd-thread.md +127 -0
  59. package/commands/gsd/gsd-ui-phase.md +34 -0
  60. package/commands/gsd/gsd-ui-review.md +32 -0
  61. package/commands/gsd/gsd-workstreams.md +71 -0
  62. package/get-shit-done/bin/gsd-tools.cjs +450 -90
  63. package/get-shit-done/bin/lib/commands.cjs +489 -24
  64. package/get-shit-done/bin/lib/config.cjs +329 -48
  65. package/get-shit-done/bin/lib/core.cjs +1143 -102
  66. package/get-shit-done/bin/lib/docs.cjs +267 -0
  67. package/get-shit-done/bin/lib/frontmatter.cjs +125 -43
  68. package/get-shit-done/bin/lib/init.cjs +918 -106
  69. package/get-shit-done/bin/lib/milestone.cjs +65 -33
  70. package/get-shit-done/bin/lib/model-profiles.cjs +70 -0
  71. package/get-shit-done/bin/lib/phase.cjs +434 -404
  72. package/get-shit-done/bin/lib/profile-output.cjs +1048 -0
  73. package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
  74. package/get-shit-done/bin/lib/roadmap.cjs +156 -101
  75. package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
  76. package/get-shit-done/bin/lib/security.cjs +384 -0
  77. package/get-shit-done/bin/lib/state.cjs +711 -79
  78. package/get-shit-done/bin/lib/template.cjs +2 -2
  79. package/get-shit-done/bin/lib/uat.cjs +282 -0
  80. package/get-shit-done/bin/lib/verify.cjs +254 -42
  81. package/get-shit-done/bin/lib/workstream.cjs +495 -0
  82. package/get-shit-done/references/agent-contracts.md +79 -0
  83. package/get-shit-done/references/artifact-types.md +113 -0
  84. package/get-shit-done/references/checkpoints.md +12 -10
  85. package/get-shit-done/references/context-budget.md +49 -0
  86. package/get-shit-done/references/continuation-format.md +15 -15
  87. package/get-shit-done/references/decimal-phase-calculation.md +2 -3
  88. package/get-shit-done/references/domain-probes.md +125 -0
  89. package/get-shit-done/references/gate-prompts.md +100 -0
  90. package/get-shit-done/references/git-integration.md +47 -0
  91. package/get-shit-done/references/model-profile-resolution.md +2 -0
  92. package/get-shit-done/references/model-profiles.md +62 -16
  93. package/get-shit-done/references/phase-argument-parsing.md +2 -2
  94. package/get-shit-done/references/planner-gap-closure.md +62 -0
  95. package/get-shit-done/references/planner-reviews.md +39 -0
  96. package/get-shit-done/references/planner-revision.md +87 -0
  97. package/get-shit-done/references/planning-config.md +18 -1
  98. package/get-shit-done/references/revision-loop.md +97 -0
  99. package/get-shit-done/references/ui-brand.md +2 -2
  100. package/get-shit-done/references/universal-anti-patterns.md +58 -0
  101. package/get-shit-done/references/user-profiling.md +681 -0
  102. package/get-shit-done/references/workstream-flag.md +111 -0
  103. package/get-shit-done/templates/SECURITY.md +61 -0
  104. package/get-shit-done/templates/UAT.md +21 -3
  105. package/get-shit-done/templates/UI-SPEC.md +100 -0
  106. package/get-shit-done/templates/VALIDATION.md +3 -3
  107. package/get-shit-done/templates/claude-md.md +145 -0
  108. package/get-shit-done/templates/config.json +14 -3
  109. package/get-shit-done/templates/context.md +61 -6
  110. package/get-shit-done/templates/debug-subagent-prompt.md +2 -6
  111. package/get-shit-done/templates/dev-preferences.md +21 -0
  112. package/get-shit-done/templates/discussion-log.md +63 -0
  113. package/get-shit-done/templates/phase-prompt.md +46 -5
  114. package/get-shit-done/templates/planner-subagent-prompt.md +2 -10
  115. package/get-shit-done/templates/project.md +2 -0
  116. package/get-shit-done/templates/state.md +2 -2
  117. package/get-shit-done/templates/user-profile.md +146 -0
  118. package/get-shit-done/workflows/add-phase.md +4 -4
  119. package/get-shit-done/workflows/add-tests.md +4 -4
  120. package/get-shit-done/workflows/add-todo.md +4 -4
  121. package/get-shit-done/workflows/analyze-dependencies.md +96 -0
  122. package/get-shit-done/workflows/audit-milestone.md +20 -16
  123. package/get-shit-done/workflows/audit-uat.md +109 -0
  124. package/get-shit-done/workflows/autonomous.md +1036 -0
  125. package/get-shit-done/workflows/check-todos.md +4 -4
  126. package/get-shit-done/workflows/cleanup.md +4 -4
  127. package/get-shit-done/workflows/complete-milestone.md +22 -10
  128. package/get-shit-done/workflows/diagnose-issues.md +21 -7
  129. package/get-shit-done/workflows/discovery-phase.md +2 -2
  130. package/get-shit-done/workflows/discuss-phase-assumptions.md +671 -0
  131. package/get-shit-done/workflows/discuss-phase-power.md +291 -0
  132. package/get-shit-done/workflows/discuss-phase.md +558 -47
  133. package/get-shit-done/workflows/do.md +104 -0
  134. package/get-shit-done/workflows/docs-update.md +1093 -0
  135. package/get-shit-done/workflows/execute-phase.md +741 -58
  136. package/get-shit-done/workflows/execute-plan.md +77 -12
  137. package/get-shit-done/workflows/fast.md +105 -0
  138. package/get-shit-done/workflows/forensics.md +265 -0
  139. package/get-shit-done/workflows/health.md +28 -6
  140. package/get-shit-done/workflows/help.md +127 -7
  141. package/get-shit-done/workflows/insert-phase.md +4 -4
  142. package/get-shit-done/workflows/list-phase-assumptions.md +2 -2
  143. package/get-shit-done/workflows/list-workspaces.md +56 -0
  144. package/get-shit-done/workflows/manager.md +363 -0
  145. package/get-shit-done/workflows/map-codebase.md +83 -44
  146. package/get-shit-done/workflows/milestone-summary.md +223 -0
  147. package/get-shit-done/workflows/new-milestone.md +133 -25
  148. package/get-shit-done/workflows/new-project.md +216 -54
  149. package/get-shit-done/workflows/new-workspace.md +237 -0
  150. package/get-shit-done/workflows/next.md +97 -0
  151. package/get-shit-done/workflows/node-repair.md +92 -0
  152. package/get-shit-done/workflows/note.md +156 -0
  153. package/get-shit-done/workflows/pause-work.md +132 -15
  154. package/get-shit-done/workflows/plan-milestone-gaps.md +6 -7
  155. package/get-shit-done/workflows/plan-phase.md +513 -62
  156. package/get-shit-done/workflows/plant-seed.md +169 -0
  157. package/get-shit-done/workflows/pr-branch.md +129 -0
  158. package/get-shit-done/workflows/profile-user.md +450 -0
  159. package/get-shit-done/workflows/progress.md +154 -29
  160. package/get-shit-done/workflows/quick.md +285 -111
  161. package/get-shit-done/workflows/remove-phase.md +2 -2
  162. package/get-shit-done/workflows/remove-workspace.md +90 -0
  163. package/get-shit-done/workflows/research-phase.md +13 -9
  164. package/get-shit-done/workflows/resume-project.md +37 -18
  165. package/get-shit-done/workflows/review.md +281 -0
  166. package/get-shit-done/workflows/secure-phase.md +154 -0
  167. package/get-shit-done/workflows/session-report.md +146 -0
  168. package/get-shit-done/workflows/set-profile.md +2 -2
  169. package/get-shit-done/workflows/settings.md +91 -11
  170. package/get-shit-done/workflows/ship.md +237 -0
  171. package/get-shit-done/workflows/stats.md +60 -0
  172. package/get-shit-done/workflows/transition.md +150 -23
  173. package/get-shit-done/workflows/ui-phase.md +292 -0
  174. package/get-shit-done/workflows/ui-review.md +183 -0
  175. package/get-shit-done/workflows/update.md +262 -30
  176. package/get-shit-done/workflows/validate-phase.md +14 -17
  177. package/get-shit-done/workflows/verify-phase.md +143 -11
  178. package/get-shit-done/workflows/verify-work.md +141 -39
  179. package/package.json +1 -1
  180. package/skills/gsd-audit-milestone/SKILL.md +29 -0
  181. package/skills/gsd-cleanup/SKILL.md +19 -0
  182. package/skills/gsd-complete-milestone/SKILL.md +131 -0
  183. package/skills/gsd-discuss-phase/SKILL.md +54 -0
  184. package/skills/gsd-execute-phase/SKILL.md +49 -0
  185. package/skills/gsd-plan-phase/SKILL.md +37 -0
  186. package/skills/gsd-ui-phase/SKILL.md +24 -0
  187. package/skills/gsd-ui-review/SKILL.md +24 -0
  188. package/skills/gsd-verify-work/SKILL.md +30 -0
@@ -4,7 +4,8 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { safeReadFile, normalizePhaseName, execGit, findPhaseInternal, getMilestoneInfo, output, error } = require('./core.cjs');
7
+ const os = require('os');
8
+ const { safeReadFile, loadConfig, normalizePhaseName, escapeRegex, execGit, findPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, planningDir, planningRoot, output, error, checkAgentsInstalled, CONFIG_DEFAULTS } = require('./core.cjs');
8
9
  const { extractFrontmatter, parseMustHavesBlock } = require('./frontmatter.cjs');
9
10
  const { writeStateMd } = require('./state.cjs');
10
11
 
@@ -395,8 +396,8 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
395
396
  }
396
397
 
397
398
  function cmdValidateConsistency(cwd, raw) {
398
- const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
399
- const phasesDir = path.join(cwd, '.planning', 'phases');
399
+ const roadmapPath = path.join(planningDir(cwd), 'ROADMAP.md');
400
+ const phasesDir = path.join(planningDir(cwd), 'phases');
400
401
  const errors = [];
401
402
  const warnings = [];
402
403
 
@@ -407,9 +408,10 @@ function cmdValidateConsistency(cwd, raw) {
407
408
  return;
408
409
  }
409
410
 
410
- const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
411
+ const roadmapContentRaw = fs.readFileSync(roadmapPath, 'utf-8');
412
+ const roadmapContent = extractCurrentMilestone(roadmapContentRaw, cwd);
411
413
 
412
- // Extract phases from ROADMAP
414
+ // Extract phases from ROADMAP (archived milestones already stripped)
413
415
  const roadmapPhases = new Set();
414
416
  const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
415
417
  let m;
@@ -426,7 +428,7 @@ function cmdValidateConsistency(cwd, raw) {
426
428
  const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
427
429
  if (dm) diskPhases.add(dm[1]);
428
430
  }
429
- } catch {}
431
+ } catch { /* intentionally empty */ }
430
432
 
431
433
  // Check: phases in ROADMAP but not on disk
432
434
  for (const p of roadmapPhases) {
@@ -443,15 +445,18 @@ function cmdValidateConsistency(cwd, raw) {
443
445
  }
444
446
  }
445
447
 
446
- // Check: sequential phase numbers (integers only)
447
- const integerPhases = [...diskPhases]
448
- .filter(p => !p.includes('.'))
449
- .map(p => parseInt(p, 10))
450
- .sort((a, b) => a - b);
451
-
452
- for (let i = 1; i < integerPhases.length; i++) {
453
- if (integerPhases[i] !== integerPhases[i - 1] + 1) {
454
- warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} ${integerPhases[i]}`);
448
+ // Check: sequential phase numbers (integers only, skip in custom naming mode)
449
+ const config = loadConfig(cwd);
450
+ if (config.phase_naming !== 'custom') {
451
+ const integerPhases = [...diskPhases]
452
+ .filter(p => !p.includes('.'))
453
+ .map(p => parseInt(p, 10))
454
+ .sort((a, b) => a - b);
455
+
456
+ for (let i = 1; i < integerPhases.length; i++) {
457
+ if (integerPhases[i] !== integerPhases[i - 1] + 1) {
458
+ warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} → ${integerPhases[i]}`);
459
+ }
455
460
  }
456
461
  }
457
462
 
@@ -488,7 +493,7 @@ function cmdValidateConsistency(cwd, raw) {
488
493
  }
489
494
  }
490
495
  }
491
- } catch {}
496
+ } catch { /* intentionally empty */ }
492
497
 
493
498
  // Check: frontmatter in plans has required fields
494
499
  try {
@@ -508,19 +513,33 @@ function cmdValidateConsistency(cwd, raw) {
508
513
  }
509
514
  }
510
515
  }
511
- } catch {}
516
+ } catch { /* intentionally empty */ }
512
517
 
513
518
  const passed = errors.length === 0;
514
519
  output({ passed, errors, warnings, warning_count: warnings.length }, raw, passed ? 'passed' : 'failed');
515
520
  }
516
521
 
517
522
  function cmdValidateHealth(cwd, options, raw) {
518
- const planningDir = path.join(cwd, '.planning');
519
- const projectPath = path.join(planningDir, 'PROJECT.md');
520
- const roadmapPath = path.join(planningDir, 'ROADMAP.md');
521
- const statePath = path.join(planningDir, 'STATE.md');
522
- const configPath = path.join(planningDir, 'config.json');
523
- const phasesDir = path.join(planningDir, 'phases');
523
+ // Guard: detect if CWD is the home directory (likely accidental)
524
+ const resolved = path.resolve(cwd);
525
+ if (resolved === os.homedir()) {
526
+ output({
527
+ status: 'error',
528
+ errors: [{ code: 'E010', message: `CWD is home directory (${resolved}) — health check would read the wrong .planning/ directory. Run from your project root instead.`, fix: 'cd into your project directory and retry' }],
529
+ warnings: [],
530
+ info: [{ code: 'I010', message: `Resolved CWD: ${resolved}` }],
531
+ repairable_count: 0,
532
+ }, raw);
533
+ return;
534
+ }
535
+
536
+ const planBase = planningDir(cwd);
537
+ const planRoot = planningRoot(cwd);
538
+ const projectPath = path.join(planRoot, 'PROJECT.md');
539
+ const roadmapPath = path.join(planBase, 'ROADMAP.md');
540
+ const statePath = path.join(planBase, 'STATE.md');
541
+ const configPath = path.join(planRoot, 'config.json');
542
+ const phasesDir = path.join(planBase, 'phases');
524
543
 
525
544
  const errors = [];
526
545
  const warnings = [];
@@ -536,7 +555,7 @@ function cmdValidateHealth(cwd, options, raw) {
536
555
  };
537
556
 
538
557
  // ─── Check 1: .planning/ exists ───────────────────────────────────────────
539
- if (!fs.existsSync(planningDir)) {
558
+ if (!fs.existsSync(planBase)) {
540
559
  addIssue('error', 'E001', '.planning/ directory not found', 'Run /gsd-new-project to initialize');
541
560
  output({
542
561
  status: 'broken',
@@ -584,15 +603,19 @@ function cmdValidateHealth(cwd, options, raw) {
584
603
  if (m) diskPhases.add(m[1]);
585
604
  }
586
605
  }
587
- } catch {}
606
+ } catch { /* intentionally empty */ }
588
607
  // Check for invalid references
589
608
  for (const ref of phaseRefs) {
590
609
  const normalizedRef = String(parseInt(ref, 10)).padStart(2, '0');
591
610
  if (!diskPhases.has(ref) && !diskPhases.has(normalizedRef) && !diskPhases.has(String(parseInt(ref, 10)))) {
592
611
  // Only warn if phases dir has any content (not just an empty project)
593
612
  if (diskPhases.size > 0) {
594
- addIssue('warning', 'W002', `STATE.md references phase ${ref}, but only phases ${[...diskPhases].sort().join(', ')} exist`, 'Run /gsd-health --repair to regenerate STATE.md', true);
595
- if (!repairs.includes('regenerateState')) repairs.push('regenerateState');
613
+ addIssue(
614
+ 'warning',
615
+ 'W002',
616
+ `STATE.md references phase ${ref}, but only phases ${[...diskPhases].sort().join(', ')} exist`,
617
+ 'Review STATE.md manually before changing it; /gsd-health --repair will not overwrite an existing STATE.md for phase mismatches'
618
+ );
596
619
  }
597
620
  }
598
621
  }
@@ -607,7 +630,7 @@ function cmdValidateHealth(cwd, options, raw) {
607
630
  const raw = fs.readFileSync(configPath, 'utf-8');
608
631
  const parsed = JSON.parse(raw);
609
632
  // Validate known fields
610
- const validProfiles = ['quality', 'balanced', 'budget'];
633
+ const validProfiles = ['quality', 'balanced', 'budget', 'inherit'];
611
634
  if (parsed.model_profile && !validProfiles.includes(parsed.model_profile)) {
612
635
  addIssue('warning', 'W004', `config.json: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`);
613
636
  }
@@ -626,7 +649,7 @@ function cmdValidateHealth(cwd, options, raw) {
626
649
  addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd-health --repair to add key', true);
627
650
  if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey');
628
651
  }
629
- } catch {}
652
+ } catch { /* intentionally empty */ }
630
653
  }
631
654
 
632
655
  // ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
@@ -637,7 +660,7 @@ function cmdValidateHealth(cwd, options, raw) {
637
660
  addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
638
661
  }
639
662
  }
640
- } catch {}
663
+ } catch { /* intentionally empty */ }
641
664
 
642
665
  // ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
643
666
  try {
@@ -656,7 +679,7 @@ function cmdValidateHealth(cwd, options, raw) {
656
679
  }
657
680
  }
658
681
  }
659
- } catch {}
682
+ } catch { /* intentionally empty */ }
660
683
 
661
684
  // ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
662
685
  try {
@@ -674,12 +697,31 @@ function cmdValidateHealth(cwd, options, raw) {
674
697
  }
675
698
  }
676
699
  }
677
- } catch {}
700
+ } catch { /* intentionally empty */ }
701
+
702
+ // ─── Check 7c: Agent installation (#1371) ──────────────────────────────────
703
+ // Verify GSD agents are installed. Missing agents cause task(subagent_type=...)
704
+ // to silently fall back to general-purpose, losing specialized instructions.
705
+ try {
706
+ const agentStatus = checkAgentsInstalled();
707
+ if (!agentStatus.agents_installed) {
708
+ if (agentStatus.installed_agents.length === 0) {
709
+ addIssue('warning', 'W010',
710
+ `No GSD agents found in ${agentStatus.agents_dir} — task(subagent_type="gsd-*") will fall back to general-purpose`,
711
+ 'Run the GSD installer: npx gsd-opencode@latest');
712
+ } else {
713
+ addIssue('warning', 'W010',
714
+ `Missing ${agentStatus.missing_agents.length} GSD agents: ${agentStatus.missing_agents.join(', ')} — affected workflows will fall back to general-purpose`,
715
+ 'Run the GSD installer: npx gsd-opencode@latest');
716
+ }
717
+ }
718
+ } catch { /* intentionally empty — agent check is non-blocking */ }
678
719
 
679
720
  // ─── Check 8: Run existing consistency checks ─────────────────────────────
680
721
  // Inline subset of cmdValidateConsistency
681
722
  if (fs.existsSync(roadmapPath)) {
682
- const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
723
+ const roadmapContentRaw = fs.readFileSync(roadmapPath, 'utf-8');
724
+ const roadmapContent = extractCurrentMilestone(roadmapContentRaw, cwd);
683
725
  const roadmapPhases = new Set();
684
726
  const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
685
727
  let m;
@@ -696,7 +738,7 @@ function cmdValidateHealth(cwd, options, raw) {
696
738
  if (dm) diskPhases.add(dm[1]);
697
739
  }
698
740
  }
699
- } catch {}
741
+ } catch { /* intentionally empty */ }
700
742
 
701
743
  // Phases in ROADMAP but not on disk
702
744
  for (const p of roadmapPhases) {
@@ -715,6 +757,71 @@ function cmdValidateHealth(cwd, options, raw) {
715
757
  }
716
758
  }
717
759
 
760
+ // ─── Check 9: STATE.md / ROADMAP.md cross-validation ─────────────────────
761
+ if (fs.existsSync(statePath) && fs.existsSync(roadmapPath)) {
762
+ try {
763
+ const stateContent = fs.readFileSync(statePath, 'utf-8');
764
+ const roadmapContentFull = fs.readFileSync(roadmapPath, 'utf-8');
765
+
766
+ // Extract current phase from STATE.md
767
+ const currentPhaseMatch = stateContent.match(/\*\*Current Phase:\*\*\s*(\S+)/i) ||
768
+ stateContent.match(/Current Phase:\s*(\S+)/i);
769
+ if (currentPhaseMatch) {
770
+ const statePhase = currentPhaseMatch[1].replace(/^0+/, '');
771
+ // Check if ROADMAP shows this phase as already complete
772
+ const phaseCheckboxRe = new RegExp(`-\\s*\\[x\\].*Phase\\s+0*${escapeRegex(statePhase)}[:\\s]`, 'i');
773
+ if (phaseCheckboxRe.test(roadmapContentFull)) {
774
+ // STATE says "current" but ROADMAP says "complete" — divergence
775
+ const stateStatus = stateContent.match(/\*\*Status:\*\*\s*(.+)/i);
776
+ const statusVal = stateStatus ? stateStatus[1].trim().toLowerCase() : '';
777
+ if (statusVal !== 'complete' && statusVal !== 'done') {
778
+ addIssue('warning', 'W011',
779
+ `STATE.md says current phase is ${statePhase} (status: ${statusVal || 'unknown'}) but ROADMAP.md shows it as [x] complete — state files may be out of sync`,
780
+ 'Run /gsd-progress to re-derive current position, or manually update STATE.md');
781
+ }
782
+ }
783
+ }
784
+ } catch { /* intentionally empty — cross-validation is advisory */ }
785
+ }
786
+
787
+ // ─── Check 10: Config field validation ────────────────────────────────────
788
+ if (fs.existsSync(configPath)) {
789
+ try {
790
+ const configRaw = fs.readFileSync(configPath, 'utf-8');
791
+ const configParsed = JSON.parse(configRaw);
792
+
793
+ // Validate branching_strategy
794
+ const validStrategies = ['none', 'phase', 'milestone'];
795
+ if (configParsed.branching_strategy && !validStrategies.includes(configParsed.branching_strategy)) {
796
+ addIssue('warning', 'W012',
797
+ `config.json: invalid branching_strategy "${configParsed.branching_strategy}"`,
798
+ `Valid values: ${validStrategies.join(', ')}`);
799
+ }
800
+
801
+ // Validate context_window is a positive integer
802
+ if (configParsed.context_window !== undefined) {
803
+ const cw = configParsed.context_window;
804
+ if (typeof cw !== 'number' || cw <= 0 || !Number.isInteger(cw)) {
805
+ addIssue('warning', 'W013',
806
+ `config.json: context_window should be a positive integer, got "${cw}"`,
807
+ 'Set to 200000 (default) or 1000000 (for 1M models)');
808
+ }
809
+ }
810
+
811
+ // Validate branch templates have required placeholders
812
+ if (configParsed.phase_branch_template && !configParsed.phase_branch_template.includes('{phase}')) {
813
+ addIssue('warning', 'W014',
814
+ 'config.json: phase_branch_template missing {phase} placeholder',
815
+ 'Template must include {phase} for phase number substitution');
816
+ }
817
+ if (configParsed.milestone_branch_template && !configParsed.milestone_branch_template.includes('{milestone}')) {
818
+ addIssue('warning', 'W015',
819
+ 'config.json: milestone_branch_template missing {milestone} placeholder',
820
+ 'Template must include {milestone} for version substitution');
821
+ }
822
+ } catch { /* parse error already caught in Check 5 */ }
823
+ }
824
+
718
825
  // ─── Perform repairs if requested ─────────────────────────────────────────
719
826
  const repairActions = [];
720
827
  if (options.repair && repairs.length > 0) {
@@ -724,14 +831,21 @@ function cmdValidateHealth(cwd, options, raw) {
724
831
  case 'createConfig':
725
832
  case 'resetConfig': {
726
833
  const defaults = {
727
- model_profile: 'balanced',
728
- commit_docs: true,
729
- search_gitignored: false,
730
- branching_strategy: 'none',
731
- research: true,
732
- plan_checker: true,
733
- verifier: true,
734
- parallelization: true,
834
+ model_profile: CONFIG_DEFAULTS.model_profile,
835
+ commit_docs: CONFIG_DEFAULTS.commit_docs,
836
+ search_gitignored: CONFIG_DEFAULTS.search_gitignored,
837
+ branching_strategy: CONFIG_DEFAULTS.branching_strategy,
838
+ phase_branch_template: CONFIG_DEFAULTS.phase_branch_template,
839
+ milestone_branch_template: CONFIG_DEFAULTS.milestone_branch_template,
840
+ quick_branch_template: CONFIG_DEFAULTS.quick_branch_template,
841
+ workflow: {
842
+ research: CONFIG_DEFAULTS.research,
843
+ plan_check: CONFIG_DEFAULTS.plan_checker,
844
+ verifier: CONFIG_DEFAULTS.verifier,
845
+ nyquist_validation: CONFIG_DEFAULTS.nyquist_validation,
846
+ },
847
+ parallelization: CONFIG_DEFAULTS.parallelization,
848
+ brave_search: CONFIG_DEFAULTS.brave_search,
735
849
  };
736
850
  fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
737
851
  repairActions.push({ action: repair, success: true, path: 'config.json' });
@@ -807,6 +921,102 @@ function cmdValidateHealth(cwd, options, raw) {
807
921
  }, raw);
808
922
  }
809
923
 
924
+ /**
925
+ * Validate agent installation status (#1371).
926
+ * Returns detailed information about which agents are installed and which are missing.
927
+ */
928
+ function cmdValidateAgents(cwd, raw) {
929
+ const { MODEL_PROFILES } = require('./model-profiles.cjs');
930
+ const agentStatus = checkAgentsInstalled();
931
+ const expected = Object.keys(MODEL_PROFILES);
932
+
933
+ output({
934
+ agents_dir: agentStatus.agents_dir,
935
+ agents_found: agentStatus.agents_installed,
936
+ installed: agentStatus.installed_agents,
937
+ missing: agentStatus.missing_agents,
938
+ expected,
939
+ }, raw);
940
+ }
941
+
942
+ // ─── Schema Drift Detection ──────────────────────────────────────────────────
943
+
944
+ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
945
+ const { detectSchemaFiles, checkSchemaDrift } = require('./schema-detect.cjs');
946
+
947
+ if (!phaseArg) {
948
+ error('Usage: verify schema-drift <phase> [--skip]');
949
+ return;
950
+ }
951
+
952
+ // Find phase directory
953
+ const pDir = planningDir(cwd);
954
+ const phasesDir = path.join(pDir, 'phases');
955
+ if (!fs.existsSync(phasesDir)) {
956
+ output({ drift_detected: false, blocking: false, message: 'No phases directory' }, raw);
957
+ return;
958
+ }
959
+
960
+ // Find matching phase directory
961
+ let phaseDir = null;
962
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
963
+ for (const entry of entries) {
964
+ if (entry.isDirectory() && entry.name.includes(phaseArg)) {
965
+ phaseDir = path.join(phasesDir, entry.name);
966
+ break;
967
+ }
968
+ }
969
+
970
+ // Also try exact match
971
+ if (!phaseDir) {
972
+ const exact = path.join(phasesDir, phaseArg);
973
+ if (fs.existsSync(exact)) phaseDir = exact;
974
+ }
975
+
976
+ if (!phaseDir) {
977
+ output({ drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
978
+ return;
979
+ }
980
+
981
+ // Collect files_modified from all PLAN.md files in the phase
982
+ const allFiles = [];
983
+ const planFiles = fs.readdirSync(phaseDir).filter(f => f.endsWith('-PLAN.md'));
984
+ for (const pf of planFiles) {
985
+ const content = fs.readFileSync(path.join(phaseDir, pf), 'utf-8');
986
+ // Extract files_modified from frontmatter
987
+ const fmMatch = content.match(/files_modified:\s*\[([^\]]*)\]/);
988
+ if (fmMatch) {
989
+ const files = fmMatch[1].split(',').map(f => f.trim()).filter(Boolean);
990
+ allFiles.push(...files);
991
+ }
992
+ }
993
+
994
+ // Collect execution log from SUMMARY.md files
995
+ let executionLog = '';
996
+ const summaryFiles = fs.readdirSync(phaseDir).filter(f => f.endsWith('-SUMMARY.md'));
997
+ for (const sf of summaryFiles) {
998
+ executionLog += fs.readFileSync(path.join(phaseDir, sf), 'utf-8') + '\n';
999
+ }
1000
+
1001
+ // Also check git commit messages for push evidence
1002
+ const gitLog = execGit(cwd, ['log', '--oneline', '--all', '-50']);
1003
+ if (gitLog.exitCode === 0) {
1004
+ executionLog += '\n' + gitLog.stdout;
1005
+ }
1006
+
1007
+ const result = checkSchemaDrift(allFiles, executionLog, { skipCheck: !!skipFlag });
1008
+
1009
+ output({
1010
+ drift_detected: result.driftDetected,
1011
+ blocking: result.blocking,
1012
+ schema_files: result.schemaFiles,
1013
+ orms: result.orms,
1014
+ unpushed_orms: result.unpushedOrms,
1015
+ message: result.message,
1016
+ skipped: result.skipped || false,
1017
+ }, raw);
1018
+ }
1019
+
810
1020
  module.exports = {
811
1021
  cmdVerifySummary,
812
1022
  cmdVerifyPlanStructure,
@@ -817,4 +1027,6 @@ module.exports = {
817
1027
  cmdVerifyKeyLinks,
818
1028
  cmdValidateConsistency,
819
1029
  cmdValidateHealth,
1030
+ cmdValidateAgents,
1031
+ cmdVerifySchemaDrift,
820
1032
  };