@bhargavvc/sdd-cc 1.30.0 → 1.35.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 (242) hide show
  1. package/README.ja-JP.md +144 -110
  2. package/README.ko-KR.md +143 -107
  3. package/README.md +183 -112
  4. package/README.pt-BR.md +90 -52
  5. package/README.zh-CN.md +141 -101
  6. package/agents/sdd-advisor-researcher.md +23 -0
  7. package/agents/sdd-ai-researcher.md +133 -0
  8. package/agents/sdd-code-fixer.md +516 -0
  9. package/agents/sdd-code-reviewer.md +355 -0
  10. package/agents/sdd-codebase-mapper.md +3 -3
  11. package/agents/sdd-debugger.md +17 -5
  12. package/agents/sdd-doc-verifier.md +201 -0
  13. package/agents/sdd-doc-writer.md +602 -0
  14. package/agents/sdd-domain-researcher.md +153 -0
  15. package/agents/sdd-eval-auditor.md +164 -0
  16. package/agents/sdd-eval-planner.md +154 -0
  17. package/agents/sdd-executor.md +87 -4
  18. package/agents/sdd-framework-selector.md +160 -0
  19. package/agents/sdd-intel-updater.md +314 -0
  20. package/agents/sdd-nyquist-auditor.md +1 -1
  21. package/agents/sdd-phase-researcher.md +71 -4
  22. package/agents/sdd-plan-checker.md +100 -6
  23. package/agents/sdd-planner.md +145 -206
  24. package/agents/sdd-project-researcher.md +25 -2
  25. package/agents/sdd-research-synthesizer.md +3 -3
  26. package/agents/sdd-roadmapper.md +6 -6
  27. package/agents/sdd-security-auditor.md +128 -0
  28. package/agents/sdd-ui-auditor.md +43 -3
  29. package/agents/sdd-ui-checker.md +5 -5
  30. package/agents/sdd-ui-researcher.md +27 -4
  31. package/agents/sdd-user-profiler.md +2 -2
  32. package/agents/sdd-verifier.md +142 -22
  33. package/bin/install.js +2151 -551
  34. package/commands/sdd/add-backlog.md +5 -5
  35. package/commands/sdd/add-tests.md +2 -2
  36. package/commands/sdd/ai-integration-phase.md +36 -0
  37. package/commands/sdd/analyze-dependencies.md +34 -0
  38. package/commands/sdd/audit-fix.md +33 -0
  39. package/commands/sdd/autonomous.md +7 -2
  40. package/commands/sdd/cleanup.md +5 -0
  41. package/commands/sdd/code-review-fix.md +52 -0
  42. package/commands/sdd/code-review.md +55 -0
  43. package/commands/sdd/complete-milestone.md +6 -6
  44. package/commands/sdd/debug.md +22 -9
  45. package/commands/sdd/discuss-phase.md +7 -2
  46. package/commands/sdd/do.md +1 -1
  47. package/commands/sdd/docs-update.md +48 -0
  48. package/commands/sdd/eval-review.md +32 -0
  49. package/commands/sdd/execute-phase.md +4 -0
  50. package/commands/sdd/explore.md +27 -0
  51. package/commands/sdd/fast.md +2 -2
  52. package/commands/sdd/from-sdd2.md +45 -0
  53. package/commands/sdd/help.md +2 -0
  54. package/commands/sdd/import.md +36 -0
  55. package/commands/sdd/intel.md +179 -0
  56. package/commands/sdd/join-discord.md +2 -1
  57. package/commands/sdd/manager.md +1 -0
  58. package/commands/sdd/map-codebase.md +3 -3
  59. package/commands/sdd/new-milestone.md +1 -1
  60. package/commands/sdd/new-project.md +5 -1
  61. package/commands/sdd/new-workspace.md +1 -1
  62. package/commands/sdd/next.md +2 -0
  63. package/commands/sdd/plan-milestone-gaps.md +2 -2
  64. package/commands/sdd/plan-phase.md +6 -1
  65. package/commands/sdd/plant-seed.md +1 -1
  66. package/commands/sdd/profile-user.md +1 -1
  67. package/commands/sdd/quick.md +5 -3
  68. package/commands/sdd/reapply-patches.md +230 -42
  69. package/commands/sdd/research-phase.md +3 -3
  70. package/commands/sdd/review-backlog.md +1 -0
  71. package/commands/sdd/review.md +6 -3
  72. package/commands/sdd/scan.md +26 -0
  73. package/commands/sdd/secure-phase.md +35 -0
  74. package/commands/sdd/ship.md +1 -1
  75. package/commands/sdd/thread.md +5 -5
  76. package/commands/sdd/undo.md +34 -0
  77. package/commands/sdd/verify-work.md +1 -1
  78. package/commands/sdd/workstreams.md +17 -11
  79. package/hooks/dist/sdd-check-update.js +33 -8
  80. package/hooks/dist/sdd-context-monitor.js +17 -8
  81. package/hooks/dist/sdd-phase-boundary.sh +27 -0
  82. package/hooks/dist/sdd-prompt-guard.js +1 -0
  83. package/hooks/dist/sdd-read-guard.js +82 -0
  84. package/hooks/dist/sdd-session-state.sh +33 -0
  85. package/hooks/dist/sdd-statusline.js +137 -15
  86. package/hooks/dist/sdd-validate-commit.sh +47 -0
  87. package/hooks/dist/sdd-workflow-guard.js +4 -4
  88. package/hooks/sdd-check-update.js +139 -0
  89. package/hooks/sdd-context-monitor.js +165 -0
  90. package/hooks/sdd-phase-boundary.sh +27 -0
  91. package/hooks/sdd-prompt-guard.js +97 -0
  92. package/hooks/sdd-read-guard.js +82 -0
  93. package/hooks/sdd-session-state.sh +33 -0
  94. package/hooks/sdd-statusline.js +241 -0
  95. package/hooks/sdd-validate-commit.sh +47 -0
  96. package/hooks/sdd-workflow-guard.js +94 -0
  97. package/package.json +3 -3
  98. package/scripts/build-hooks.js +18 -7
  99. package/scripts/prompt-injection-scan.sh +1 -0
  100. package/scripts/rebrand-gsd-to-sdd.sh +221 -220
  101. package/scripts/run-tests.cjs +5 -1
  102. package/scripts/sync-upstream.sh +1 -1
  103. package/sdd/bin/lib/commands.cjs +79 -17
  104. package/sdd/bin/lib/config.cjs +90 -48
  105. package/sdd/bin/lib/core.cjs +452 -87
  106. package/sdd/bin/lib/docs.cjs +267 -0
  107. package/sdd/bin/lib/frontmatter.cjs +381 -336
  108. package/sdd/bin/lib/init.cjs +110 -16
  109. package/sdd/bin/lib/intel.cjs +660 -0
  110. package/sdd/bin/lib/learnings.cjs +378 -0
  111. package/sdd/bin/lib/milestone.cjs +42 -11
  112. package/sdd/bin/lib/model-profiles.cjs +17 -15
  113. package/sdd/bin/lib/phase.cjs +367 -288
  114. package/sdd/bin/lib/profile-output.cjs +106 -10
  115. package/sdd/bin/lib/roadmap.cjs +146 -115
  116. package/sdd/bin/lib/schema-detect.cjs +238 -0
  117. package/sdd/bin/lib/sdd2-import.cjs +511 -0
  118. package/sdd/bin/lib/security.cjs +124 -3
  119. package/sdd/bin/lib/state.cjs +648 -264
  120. package/sdd/bin/lib/template.cjs +8 -4
  121. package/sdd/bin/lib/verify.cjs +209 -28
  122. package/sdd/bin/lib/workstream.cjs +7 -3
  123. package/sdd/bin/sdd-tools.cjs +184 -12
  124. package/sdd/contexts/dev.md +21 -0
  125. package/sdd/contexts/research.md +22 -0
  126. package/sdd/contexts/review.md +22 -0
  127. package/sdd/references/agent-contracts.md +79 -0
  128. package/sdd/references/ai-evals.md +156 -0
  129. package/sdd/references/ai-frameworks.md +186 -0
  130. package/sdd/references/artifact-types.md +113 -0
  131. package/sdd/references/common-bug-patterns.md +114 -0
  132. package/sdd/references/context-budget.md +49 -0
  133. package/sdd/references/continuation-format.md +25 -25
  134. package/sdd/references/domain-probes.md +125 -0
  135. package/sdd/references/few-shot-examples/plan-checker.md +73 -0
  136. package/sdd/references/few-shot-examples/verifier.md +109 -0
  137. package/sdd/references/gate-prompts.md +100 -0
  138. package/sdd/references/gates.md +70 -0
  139. package/sdd/references/git-integration.md +1 -1
  140. package/sdd/references/ios-scaffold.md +123 -0
  141. package/sdd/references/model-profile-resolution.md +2 -0
  142. package/sdd/references/model-profiles.md +24 -18
  143. package/sdd/references/planner-gap-closure.md +62 -0
  144. package/sdd/references/planner-reviews.md +39 -0
  145. package/sdd/references/planner-revision.md +87 -0
  146. package/sdd/references/planning-config.md +252 -0
  147. package/sdd/references/revision-loop.md +97 -0
  148. package/sdd/references/thinking-models-debug.md +44 -0
  149. package/sdd/references/thinking-models-execution.md +50 -0
  150. package/sdd/references/thinking-models-planning.md +62 -0
  151. package/sdd/references/thinking-models-research.md +50 -0
  152. package/sdd/references/thinking-models-verification.md +55 -0
  153. package/sdd/references/thinking-partner.md +96 -0
  154. package/sdd/references/ui-brand.md +4 -4
  155. package/sdd/references/universal-anti-patterns.md +63 -0
  156. package/sdd/references/verification-overrides.md +227 -0
  157. package/sdd/references/workstream-flag.md +56 -3
  158. package/sdd/templates/AI-SPEC.md +246 -0
  159. package/sdd/templates/DEBUG.md +1 -1
  160. package/sdd/templates/SECURITY.md +61 -0
  161. package/sdd/templates/UAT.md +4 -4
  162. package/sdd/templates/VALIDATION.md +4 -4
  163. package/sdd/templates/claude-md.md +32 -9
  164. package/sdd/templates/config.json +4 -0
  165. package/sdd/templates/debug-subagent-prompt.md +1 -1
  166. package/sdd/templates/dev-preferences.md +1 -1
  167. package/sdd/templates/discovery.md +2 -2
  168. package/sdd/templates/phase-prompt.md +1 -1
  169. package/sdd/templates/planner-subagent-prompt.md +3 -3
  170. package/sdd/templates/project.md +1 -1
  171. package/sdd/templates/research.md +1 -1
  172. package/sdd/templates/state.md +2 -2
  173. package/sdd/workflows/add-phase.md +8 -8
  174. package/sdd/workflows/add-tests.md +12 -9
  175. package/sdd/workflows/add-todo.md +5 -3
  176. package/sdd/workflows/ai-integration-phase.md +284 -0
  177. package/sdd/workflows/analyze-dependencies.md +96 -0
  178. package/sdd/workflows/audit-fix.md +157 -0
  179. package/sdd/workflows/audit-milestone.md +11 -11
  180. package/sdd/workflows/audit-uat.md +2 -2
  181. package/sdd/workflows/autonomous.md +195 -27
  182. package/sdd/workflows/check-todos.md +12 -10
  183. package/sdd/workflows/cleanup.md +2 -0
  184. package/sdd/workflows/code-review-fix.md +497 -0
  185. package/sdd/workflows/code-review.md +515 -0
  186. package/sdd/workflows/complete-milestone.md +56 -22
  187. package/sdd/workflows/diagnose-issues.md +10 -3
  188. package/sdd/workflows/discovery-phase.md +5 -3
  189. package/sdd/workflows/discuss-phase-assumptions.md +24 -6
  190. package/sdd/workflows/discuss-phase-power.md +291 -0
  191. package/sdd/workflows/discuss-phase.md +173 -21
  192. package/sdd/workflows/do.md +23 -21
  193. package/sdd/workflows/docs-update.md +1155 -0
  194. package/sdd/workflows/eval-review.md +155 -0
  195. package/sdd/workflows/execute-phase.md +594 -38
  196. package/sdd/workflows/execute-plan.md +67 -96
  197. package/sdd/workflows/explore.md +139 -0
  198. package/sdd/workflows/fast.md +5 -5
  199. package/sdd/workflows/forensics.md +2 -2
  200. package/sdd/workflows/health.md +4 -4
  201. package/sdd/workflows/help.md +122 -119
  202. package/sdd/workflows/import.md +276 -0
  203. package/sdd/workflows/inbox.md +387 -0
  204. package/sdd/workflows/insert-phase.md +7 -7
  205. package/sdd/workflows/list-phase-assumptions.md +4 -4
  206. package/sdd/workflows/list-workspaces.md +2 -2
  207. package/sdd/workflows/manager.md +35 -32
  208. package/sdd/workflows/map-codebase.md +7 -5
  209. package/sdd/workflows/milestone-summary.md +2 -2
  210. package/sdd/workflows/new-milestone.md +17 -9
  211. package/sdd/workflows/new-project.md +50 -25
  212. package/sdd/workflows/new-workspace.md +7 -5
  213. package/sdd/workflows/next.md +67 -11
  214. package/sdd/workflows/note.md +9 -7
  215. package/sdd/workflows/pause-work.md +75 -12
  216. package/sdd/workflows/plan-milestone-gaps.md +8 -8
  217. package/sdd/workflows/plan-phase.md +294 -42
  218. package/sdd/workflows/plant-seed.md +6 -3
  219. package/sdd/workflows/pr-branch.md +42 -14
  220. package/sdd/workflows/profile-user.md +9 -7
  221. package/sdd/workflows/progress.md +45 -45
  222. package/sdd/workflows/quick.md +195 -47
  223. package/sdd/workflows/remove-phase.md +6 -6
  224. package/sdd/workflows/remove-workspace.md +3 -1
  225. package/sdd/workflows/research-phase.md +2 -2
  226. package/sdd/workflows/resume-project.md +12 -12
  227. package/sdd/workflows/review.md +109 -9
  228. package/sdd/workflows/scan.md +102 -0
  229. package/sdd/workflows/secure-phase.md +166 -0
  230. package/sdd/workflows/session-report.md +2 -2
  231. package/sdd/workflows/settings.md +38 -12
  232. package/sdd/workflows/ship.md +21 -9
  233. package/sdd/workflows/stats.md +1 -1
  234. package/sdd/workflows/transition.md +23 -23
  235. package/sdd/workflows/ui-phase.md +15 -7
  236. package/sdd/workflows/ui-review.md +29 -4
  237. package/sdd/workflows/undo.md +314 -0
  238. package/sdd/workflows/update.md +171 -20
  239. package/sdd/workflows/validate-phase.md +6 -4
  240. package/sdd/workflows/verify-phase.md +210 -6
  241. package/sdd/workflows/verify-work.md +83 -9
  242. package/sdd/commands/sdd/workstreams.md +0 -63
@@ -173,19 +173,23 @@ const CLAUDE_INSTRUCTIONS = {
173
173
  };
174
174
 
175
175
  const CLAUDE_MD_FALLBACKS = {
176
- project: 'Project not yet initialized. Run /sdd:new-project to set up.',
176
+ project: 'Project not yet initialized. Run /sdd-new-project to set up.',
177
177
  stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.',
178
178
  conventions: 'Conventions not yet established. Will populate as patterns emerge during development.',
179
179
  architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.',
180
+ skills: 'No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, or `.github/skills/` with a `SKILL.md` index file.',
180
181
  };
181
182
 
183
+ // Directories where project skills may live (checked in order)
184
+ const SKILL_SEARCH_DIRS = ['.claude/skills', '.agents/skills', '.cursor/skills', '.github/skills'];
185
+
182
186
  const CLAUDE_MD_WORKFLOW_ENFORCEMENT = [
183
187
  'Before using Edit, Write, or other file-changing tools, start work through a SDD command so planning artifacts and execution context stay in sync.',
184
188
  '',
185
189
  'Use these entry points:',
186
- '- `/sdd:quick` for small fixes, doc updates, and ad-hoc tasks',
187
- '- `/sdd:debug` for investigation and bug fixing',
188
- '- `/sdd:execute-phase` for planned phase work',
190
+ '- `/sdd-quick` for small fixes, doc updates, and ad-hoc tasks',
191
+ '- `/sdd-debug` for investigation and bug fixing',
192
+ '- `/sdd-execute-phase` for planned phase work',
189
193
  '',
190
194
  'Do not make direct repo edits outside a SDD workflow unless the user explicitly asks to bypass it.',
191
195
  ].join('\n');
@@ -194,7 +198,7 @@ const CLAUDE_MD_PROFILE_PLACEHOLDER = [
194
198
  '<!-- SDD:profile-start -->',
195
199
  '## Developer Profile',
196
200
  '',
197
- '> Profile not yet configured. Run `/sdd:profile-user` to generate your developer profile.',
201
+ '> Profile not yet configured. Run `/sdd-profile-user` to generate your developer profile.',
198
202
  '> This section is managed by `generate-claude-profile` -- do not edit manually.',
199
203
  '<!-- SDD:profile-end -->',
200
204
  ].join('\n');
@@ -375,6 +379,96 @@ function generateWorkflowSection() {
375
379
  };
376
380
  }
377
381
 
382
+ /**
383
+ * Discover project skills from standard directories and extract frontmatter
384
+ * (name + description) for each. Returns a table summary for CLAUDE.md so
385
+ * agents know which skills are available at session startup (Layer 1 discovery).
386
+ */
387
+ function generateSkillsSection(cwd) {
388
+ const discovered = [];
389
+
390
+ for (const dir of SKILL_SEARCH_DIRS) {
391
+ const absDir = path.join(cwd, dir);
392
+ if (!fs.existsSync(absDir)) continue;
393
+
394
+ let entries;
395
+ try {
396
+ entries = fs.readdirSync(absDir, { withFileTypes: true });
397
+ } catch {
398
+ continue;
399
+ }
400
+
401
+ for (const entry of entries) {
402
+ if (!entry.isDirectory()) continue;
403
+ // Skip SDD's own installed skills — only surface project-specific skills
404
+ if (entry.name.startsWith('sdd-')) continue;
405
+
406
+ const skillMdPath = path.join(absDir, entry.name, 'SKILL.md');
407
+ if (!fs.existsSync(skillMdPath)) continue;
408
+
409
+ const content = safeReadFile(skillMdPath);
410
+ if (!content) continue;
411
+
412
+ const frontmatter = extractSkillFrontmatter(content);
413
+ const name = frontmatter.name || entry.name;
414
+ const description = frontmatter.description || '';
415
+
416
+ // Avoid duplicates when same skill dir is symlinked from multiple locations
417
+ if (discovered.some(s => s.name === name)) continue;
418
+
419
+ discovered.push({ name, description, path: `${dir}/${entry.name}` });
420
+ }
421
+ }
422
+
423
+ if (discovered.length === 0) {
424
+ return { content: CLAUDE_MD_FALLBACKS.skills, source: 'skills/', hasFallback: true };
425
+ }
426
+
427
+ const lines = ['| Skill | Description | Path |', '|-------|-------------|------|'];
428
+ for (const skill of discovered) {
429
+ // Sanitize table cell content (escape pipes)
430
+ const desc = skill.description.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim();
431
+ const safeName = skill.name.replace(/\|/g, '\\|');
432
+ lines.push(`| ${safeName} | ${desc} | \`${skill.path}/SKILL.md\` |`);
433
+ }
434
+
435
+ return { content: lines.join('\n'), source: 'skills/', hasFallback: false };
436
+ }
437
+
438
+ /**
439
+ * Extract name and description from YAML-like frontmatter in a SKILL.md file.
440
+ * Handles multi-line description values (continuation lines indented with spaces).
441
+ */
442
+ function extractSkillFrontmatter(content) {
443
+ const result = { name: '', description: '' };
444
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
445
+ if (!fmMatch) return result;
446
+
447
+ const fmBlock = fmMatch[1];
448
+ const lines = fmBlock.split('\n');
449
+
450
+ let currentKey = '';
451
+ for (const line of lines) {
452
+ // Top-level key: value
453
+ const kvMatch = line.match(/^(\w[\w-]*):\s*(.*)/);
454
+ if (kvMatch) {
455
+ currentKey = kvMatch[1];
456
+ const value = kvMatch[2].trim();
457
+ if (currentKey === 'name') result.name = value;
458
+ if (currentKey === 'description') result.description = value;
459
+ continue;
460
+ }
461
+ // Continuation line (indented) for multi-line values
462
+ if (currentKey === 'description' && /^\s+/.test(line)) {
463
+ result.description += ' ' + line.trim();
464
+ } else {
465
+ currentKey = '';
466
+ }
467
+ }
468
+
469
+ return result;
470
+ }
471
+
378
472
  // ─── Commands ─────────────────────────────────────────────────────────────────
379
473
 
380
474
  function cmdWriteProfile(cwd, options, raw) {
@@ -671,7 +765,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
671
765
 
672
766
  let stackBlock;
673
767
  if (analysis.data_source === 'questionnaire') {
674
- stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/sdd:profile-user --refresh` with session data to populate.';
768
+ stackBlock = 'Stack preferences not available (questionnaire-only profile). Run `/sdd-profile-user --refresh` with session data to populate.';
675
769
  } else if (options.stack) {
676
770
  stackBlock = options.stack;
677
771
  } else {
@@ -691,7 +785,7 @@ function cmdGenerateDevPreferences(cwd, options, raw) {
691
785
 
692
786
  const result = {
693
787
  command_path: outputPath,
694
- command_name: '/sdd:dev-preferences',
788
+ command_name: '/sdd-dev-preferences',
695
789
  dimensions_included: dimensionsIncluded,
696
790
  source: analysis.data_source || 'session_analysis',
697
791
  };
@@ -757,7 +851,7 @@ function cmdGenerateClaudeProfile(cwd, options, raw) {
757
851
  '<!-- SDD:profile-start -->',
758
852
  '## Developer Profile',
759
853
  '',
760
- `> Generated by SDD from ${dataSource}. Run \`/sdd:profile-user --refresh\` to update.`,
854
+ `> Generated by SDD from ${dataSource}. Run \`/sdd-profile-user --refresh\` to update.`,
761
855
  '',
762
856
  '| Dimension | Rating | Confidence |',
763
857
  '|-----------|--------|------------|',
@@ -815,12 +909,13 @@ function cmdGenerateClaudeProfile(cwd, options, raw) {
815
909
  }
816
910
 
817
911
  function cmdGenerateClaudeMd(cwd, options, raw) {
818
- const MANAGED_SECTIONS = ['project', 'stack', 'conventions', 'architecture', 'workflow'];
912
+ const MANAGED_SECTIONS = ['project', 'stack', 'conventions', 'architecture', 'skills', 'workflow'];
819
913
  const generators = {
820
914
  project: generateProjectSection,
821
915
  stack: generateStackSection,
822
916
  conventions: generateConventionsSection,
823
917
  architecture: generateArchitectureSection,
918
+ skills: generateSkillsSection,
824
919
  workflow: generateWorkflowSection,
825
920
  };
826
921
  const sectionHeadings = {
@@ -828,6 +923,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
828
923
  stack: '## Technology Stack',
829
924
  conventions: '## Conventions',
830
925
  architecture: '## Architecture',
926
+ skills: '## Project Skills',
831
927
  workflow: '## SDD Workflow Enforcement',
832
928
  };
833
929
 
@@ -925,7 +1021,7 @@ function cmdGenerateClaudeMd(cwd, options, raw) {
925
1021
  let message = `Generated ${genCount}/${totalManaged} sections.`;
926
1022
  if (sectionsFallback.length > 0) message += ` Fallback: ${sectionsFallback.join(', ')}.`;
927
1023
  if (sectionsSkipped.length > 0) message += ` Skipped (manually edited): ${sectionsSkipped.join(', ')}.`;
928
- if (profileStatus === 'placeholder_added') message += ' Run /sdd:profile-user to unlock Developer Profile.';
1024
+ if (profileStatus === 'placeholder_added') message += ' Run /sdd-profile-user to unlock Developer Profile.';
929
1025
 
930
1026
  const result = {
931
1027
  claude_md_path: outputPath,
@@ -4,7 +4,73 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { escapeRegex, normalizePhaseName, planningPaths, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = require('./core.cjs');
7
+ const { escapeRegex, normalizePhaseName, planningPaths, withPlanningLock, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone, phaseTokenMatches, atomicWriteFileSync } = require('./core.cjs');
8
+
9
+ /**
10
+ * Search for a phase header (and its section) within the given content string.
11
+ * Returns a result object if found (either a full match or a malformed_roadmap
12
+ * checklist-only match), or null if the phase is not present at all.
13
+ */
14
+ function searchPhaseInContent(content, escapedPhase, phaseNum) {
15
+ // Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
16
+ const phasePattern = new RegExp(
17
+ `#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
18
+ 'i'
19
+ );
20
+ const headerMatch = content.match(phasePattern);
21
+
22
+ if (!headerMatch) {
23
+ // Fallback: check if phase exists in summary list but missing detail section
24
+ const checklistPattern = new RegExp(
25
+ `-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
26
+ 'i'
27
+ );
28
+ const checklistMatch = content.match(checklistPattern);
29
+
30
+ if (checklistMatch) {
31
+ return {
32
+ found: false,
33
+ phase_number: phaseNum,
34
+ phase_name: checklistMatch[1].trim(),
35
+ error: 'malformed_roadmap',
36
+ message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
37
+ };
38
+ }
39
+
40
+ return null;
41
+ }
42
+
43
+ const phaseName = headerMatch[1].trim();
44
+ const headerIndex = headerMatch.index;
45
+
46
+ // Find the end of this section (next ## or ### phase header, or end of file)
47
+ const restOfContent = content.slice(headerIndex);
48
+ const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
49
+ const sectionEnd = nextHeaderMatch
50
+ ? headerIndex + nextHeaderMatch.index
51
+ : content.length;
52
+
53
+ const section = content.slice(headerIndex, sectionEnd).trim();
54
+
55
+ // Extract goal if present (supports both **Goal:** and **Goal**: formats)
56
+ const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
57
+ const goal = goalMatch ? goalMatch[1].trim() : null;
58
+
59
+ // Extract success criteria as structured array
60
+ const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
61
+ const success_criteria = criteriaMatch
62
+ ? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
63
+ : [];
64
+
65
+ return {
66
+ found: true,
67
+ phase_number: phaseNum,
68
+ phase_name: phaseName,
69
+ goal,
70
+ success_criteria,
71
+ section,
72
+ };
73
+ }
8
74
 
9
75
  function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
10
76
  const roadmapPath = planningPaths(cwd).roadmap;
@@ -15,76 +81,32 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
15
81
  }
16
82
 
17
83
  try {
18
- const content = extractCurrentMilestone(fs.readFileSync(roadmapPath, 'utf-8'), cwd);
84
+ const rawContent = fs.readFileSync(roadmapPath, 'utf-8');
85
+ const milestoneContent = extractCurrentMilestone(rawContent, cwd);
19
86
 
20
87
  // Escape special regex chars in phase number, handle decimal
21
88
  const escapedPhase = escapeRegex(phaseNum);
22
89
 
23
- // Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
24
- const phasePattern = new RegExp(
25
- `#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
26
- 'i'
27
- );
28
- const headerMatch = content.match(phasePattern);
29
-
30
- if (!headerMatch) {
31
- // Fallback: check if phase exists in summary list but missing detail section
32
- const checklistPattern = new RegExp(
33
- `-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
34
- 'i'
35
- );
36
- const checklistMatch = content.match(checklistPattern);
37
-
38
- if (checklistMatch) {
39
- // Phase exists in summary but missing detail section - malformed ROADMAP
40
- output({
41
- found: false,
42
- phase_number: phaseNum,
43
- phase_name: checklistMatch[1].trim(),
44
- error: 'malformed_roadmap',
45
- message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
46
- }, raw, '');
47
- return;
48
- }
90
+ // Search the current milestone slice first, then fall back to full roadmap.
91
+ // A malformed_roadmap result (checklist-only) from the milestone should not
92
+ // block finding a full header match in the wider roadmap content.
93
+ const fullContent = stripShippedMilestones(rawContent);
94
+ const milestoneResult = searchPhaseInContent(milestoneContent, escapedPhase, phaseNum);
95
+ const result = (milestoneResult && !milestoneResult.error)
96
+ ? milestoneResult
97
+ : searchPhaseInContent(fullContent, escapedPhase, phaseNum) || milestoneResult;
49
98
 
99
+ if (!result) {
50
100
  output({ found: false, phase_number: phaseNum }, raw, '');
51
101
  return;
52
102
  }
53
103
 
54
- const phaseName = headerMatch[1].trim();
55
- const headerIndex = headerMatch.index;
56
-
57
- // Find the end of this section (next ## or ### phase header, or end of file)
58
- const restOfContent = content.slice(headerIndex);
59
- const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
60
- const sectionEnd = nextHeaderMatch
61
- ? headerIndex + nextHeaderMatch.index
62
- : content.length;
63
-
64
- const section = content.slice(headerIndex, sectionEnd).trim();
65
-
66
- // Extract goal if present (supports both **Goal:** and **Goal**: formats)
67
- const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
68
- const goal = goalMatch ? goalMatch[1].trim() : null;
69
-
70
- // Extract success criteria as structured array
71
- const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
72
- const success_criteria = criteriaMatch
73
- ? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
74
- : [];
104
+ if (result.error) {
105
+ output(result, raw, '');
106
+ return;
107
+ }
75
108
 
76
- output(
77
- {
78
- found: true,
79
- phase_number: phaseNum,
80
- phase_name: phaseName,
81
- goal,
82
- success_criteria,
83
- section,
84
- },
85
- raw,
86
- section
87
- );
109
+ output(result, raw, result.section);
88
110
  } catch (e) {
89
111
  error('Failed to read ROADMAP.md: ' + e.message);
90
112
  }
@@ -107,6 +129,15 @@ function cmdRoadmapAnalyze(cwd, raw) {
107
129
  const phases = [];
108
130
  let match;
109
131
 
132
+ // Build phase directory lookup once (O(1) readdir instead of O(N) per phase)
133
+ const _phaseDirNames = (() => {
134
+ try {
135
+ return fs.readdirSync(phasesDir, { withFileTypes: true })
136
+ .filter(e => e.isDirectory())
137
+ .map(e => e.name);
138
+ } catch { return []; }
139
+ })();
140
+
110
141
  while ((match = phasePattern.exec(content)) !== null) {
111
142
  const phaseNum = match[1];
112
143
  const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
@@ -133,9 +164,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
133
164
  let hasResearch = false;
134
165
 
135
166
  try {
136
- const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
137
- const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
138
- const dirMatch = dirs.find(d => d.startsWith(normalized + '-') || d === normalized);
167
+ const dirMatch = _phaseDirNames.find(d => phaseTokenMatches(d, normalized));
139
168
 
140
169
  if (dirMatch) {
141
170
  const phaseFiles = fs.readdirSync(path.join(phasesDir, dirMatch));
@@ -254,64 +283,66 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
254
283
  return;
255
284
  }
256
285
 
257
- let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
258
- const phaseEscaped = escapeRegex(phaseNum);
259
-
260
- // Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
261
- const tableRowPattern = new RegExp(
262
- `^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
263
- 'im'
264
- );
265
- const dateField = isComplete ? ` ${today} ` : ' ';
266
- roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
267
- const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
268
- if (cells.length === 5) {
269
- // 5-col: Phase | Milestone | Plans | Status | Completed
270
- cells[2] = ` ${summaryCount}/${planCount} `;
271
- cells[3] = ` ${status.padEnd(11)}`;
272
- cells[4] = dateField;
273
- } else if (cells.length === 4) {
274
- // 4-col: Phase | Plans | Status | Completed
275
- cells[1] = ` ${summaryCount}/${planCount} `;
276
- cells[2] = ` ${status.padEnd(11)}`;
277
- cells[3] = dateField;
278
- }
279
- return '|' + cells.join('|') + '|';
280
- });
286
+ // Wrap entire read-modify-write in lock to prevent concurrent corruption
287
+ withPlanningLock(cwd, () => {
288
+ let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
289
+ const phaseEscaped = escapeRegex(phaseNum);
281
290
 
282
- // Update plan count in phase detail section
283
- const planCountPattern = new RegExp(
284
- `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
285
- 'i'
286
- );
287
- const planCountText = isComplete
288
- ? `${summaryCount}/${planCount} plans complete`
289
- : `${summaryCount}/${planCount} plans executed`;
290
- roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
291
-
292
- // If complete: check checkbox
293
- if (isComplete) {
294
- const checkboxPattern = new RegExp(
295
- `(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
296
- 'i'
291
+ // Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
292
+ const tableRowPattern = new RegExp(
293
+ `^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
294
+ 'im'
297
295
  );
298
- roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
299
- }
296
+ const dateField = isComplete ? ` ${today} ` : ' ';
297
+ roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
298
+ const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
299
+ if (cells.length === 5) {
300
+ // 5-col: Phase | Milestone | Plans | Status | Completed
301
+ cells[2] = ` ${summaryCount}/${planCount} `;
302
+ cells[3] = ` ${status.padEnd(11)}`;
303
+ cells[4] = dateField;
304
+ } else if (cells.length === 4) {
305
+ // 4-col: Phase | Plans | Status | Completed
306
+ cells[1] = ` ${summaryCount}/${planCount} `;
307
+ cells[2] = ` ${status.padEnd(11)}`;
308
+ cells[3] = dateField;
309
+ }
310
+ return '|' + cells.join('|') + '|';
311
+ });
300
312
 
301
- // Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md" or "- [ ] 50-01:")
302
- for (const summaryFile of phaseInfo.summaries) {
303
- const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
304
- if (!planId) continue;
305
- const planEscaped = escapeRegex(planId);
306
- const planCheckboxPattern = new RegExp(
307
- `(-\\s*\\[) (\\]\\s*${planEscaped})`,
313
+ // Update plan count in phase detail section
314
+ const planCountPattern = new RegExp(
315
+ `(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
308
316
  'i'
309
317
  );
310
- roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
311
- }
318
+ const planCountText = isComplete
319
+ ? `${summaryCount}/${planCount} plans complete`
320
+ : `${summaryCount}/${planCount} plans executed`;
321
+ roadmapContent = replaceInCurrentMilestone(roadmapContent, planCountPattern, `$1${planCountText}`);
322
+
323
+ // If complete: check checkbox
324
+ if (isComplete) {
325
+ const checkboxPattern = new RegExp(
326
+ `(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
327
+ 'i'
328
+ );
329
+ roadmapContent = replaceInCurrentMilestone(roadmapContent, checkboxPattern, `$1x$2 (completed ${today})`);
330
+ }
312
331
 
313
- fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
332
+ // Mark completed plan checkboxes (e.g. "- [ ] 50-01-PLAN.md", "- [ ] 50-01:", or "- [ ] **50-01**")
333
+ for (const summaryFile of phaseInfo.summaries) {
334
+ const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
335
+ if (!planId) continue;
336
+ const planEscaped = escapeRegex(planId);
337
+ const planCheckboxPattern = new RegExp(
338
+ `(-\\s*\\[) (\\]\\s*(?:\\*\\*)?${planEscaped}(?:\\*\\*)?)`,
339
+ 'i'
340
+ );
341
+ roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2');
342
+ }
314
343
 
344
+ atomicWriteFileSync(roadmapPath, roadmapContent, 'utf-8');
345
+ });
315
346
  output({
316
347
  updated: true,
317
348
  phase: phaseNum,