@ktpartners/dgs-platform 2.6.2

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 (256) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +851 -0
  3. package/agents/dgs-codebase-cross-analyzer.md +183 -0
  4. package/agents/dgs-codebase-mapper.md +782 -0
  5. package/agents/dgs-codebase-synthesizer.md +156 -0
  6. package/agents/dgs-debugger.md +1256 -0
  7. package/agents/dgs-executor.md +550 -0
  8. package/agents/dgs-integration-checker.md +481 -0
  9. package/agents/dgs-nyquist-auditor.md +178 -0
  10. package/agents/dgs-phase-researcher.md +563 -0
  11. package/agents/dgs-phase-verifier.md +450 -0
  12. package/agents/dgs-plan-checker.md +708 -0
  13. package/agents/dgs-planner.md +1324 -0
  14. package/agents/dgs-project-researcher.md +631 -0
  15. package/agents/dgs-research-synthesizer.md +249 -0
  16. package/agents/dgs-roadmapper.md +652 -0
  17. package/agents/dgs-verifier.md +607 -0
  18. package/bin/install.js +2073 -0
  19. package/commands/dgs/add-doc.md +45 -0
  20. package/commands/dgs/add-idea.md +38 -0
  21. package/commands/dgs/add-phase.md +43 -0
  22. package/commands/dgs/add-repo.md +54 -0
  23. package/commands/dgs/add-tests.md +41 -0
  24. package/commands/dgs/add-todo.md +47 -0
  25. package/commands/dgs/approve-spec.md +38 -0
  26. package/commands/dgs/audit-milestone.md +36 -0
  27. package/commands/dgs/audit-phase.md +37 -0
  28. package/commands/dgs/cancel-job.md +23 -0
  29. package/commands/dgs/capture-principle.md +143 -0
  30. package/commands/dgs/check-todos.md +45 -0
  31. package/commands/dgs/cleanup.md +18 -0
  32. package/commands/dgs/complete-milestone.md +136 -0
  33. package/commands/dgs/complete-project.md +70 -0
  34. package/commands/dgs/consolidate-ideas.md +50 -0
  35. package/commands/dgs/create-milestone-job.md +37 -0
  36. package/commands/dgs/debug.md +164 -0
  37. package/commands/dgs/develop-idea.md +53 -0
  38. package/commands/dgs/discuss-idea.md +41 -0
  39. package/commands/dgs/discuss-phase.md +83 -0
  40. package/commands/dgs/execute-phase.md +41 -0
  41. package/commands/dgs/fast.md +38 -0
  42. package/commands/dgs/find-related-ideas.md +43 -0
  43. package/commands/dgs/health.md +28 -0
  44. package/commands/dgs/help.md +22 -0
  45. package/commands/dgs/import-spec.md +36 -0
  46. package/commands/dgs/init-product.md +28 -0
  47. package/commands/dgs/insert-phase.md +32 -0
  48. package/commands/dgs/join-discord.md +18 -0
  49. package/commands/dgs/list-docs.md +40 -0
  50. package/commands/dgs/list-ideas.md +42 -0
  51. package/commands/dgs/list-jobs.md +22 -0
  52. package/commands/dgs/list-phase-assumptions.md +46 -0
  53. package/commands/dgs/list-projects.md +57 -0
  54. package/commands/dgs/list-specs.md +40 -0
  55. package/commands/dgs/map-codebase.md +92 -0
  56. package/commands/dgs/new-milestone.md +44 -0
  57. package/commands/dgs/new-project.md +42 -0
  58. package/commands/dgs/node-repair.md +26 -0
  59. package/commands/dgs/overlap-check.md +20 -0
  60. package/commands/dgs/pause-work.md +38 -0
  61. package/commands/dgs/plan-milestone-gaps.md +34 -0
  62. package/commands/dgs/plan-phase.md +44 -0
  63. package/commands/dgs/progress.md +24 -0
  64. package/commands/dgs/quick.md +41 -0
  65. package/commands/dgs/reactivate-project.md +70 -0
  66. package/commands/dgs/reapply-patches.md +110 -0
  67. package/commands/dgs/refine-spec.md +38 -0
  68. package/commands/dgs/reject-idea.md +43 -0
  69. package/commands/dgs/remove-doc.md +44 -0
  70. package/commands/dgs/remove-phase.md +31 -0
  71. package/commands/dgs/remove-repo.md +69 -0
  72. package/commands/dgs/research-idea.md +43 -0
  73. package/commands/dgs/research-phase.md +189 -0
  74. package/commands/dgs/restore-idea.md +45 -0
  75. package/commands/dgs/resume-work.md +40 -0
  76. package/commands/dgs/rollback-job.md +24 -0
  77. package/commands/dgs/run-job.md +35 -0
  78. package/commands/dgs/search.md +40 -0
  79. package/commands/dgs/set-profile.md +34 -0
  80. package/commands/dgs/settings.md +38 -0
  81. package/commands/dgs/switch-project.md +58 -0
  82. package/commands/dgs/undo-consolidation.md +42 -0
  83. package/commands/dgs/update-idea.md +44 -0
  84. package/commands/dgs/update.md +37 -0
  85. package/commands/dgs/validate-phase.md +35 -0
  86. package/commands/dgs/verify-work.md +39 -0
  87. package/commands/dgs/write-spec.md +49 -0
  88. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-01-SUMMARY.md +84 -0
  89. package/deliver-great-systems/.planning/phases/09-backend-wiring-and-error-handling/09-02-SUMMARY.md +86 -0
  90. package/deliver-great-systems/.planning/phases/10-v1-to-v2-migration-flow/10-01-SUMMARY.md +85 -0
  91. package/deliver-great-systems/bin/dgs-tools.cjs +1444 -0
  92. package/deliver-great-systems/bin/lib/auto-test.cjs +1365 -0
  93. package/deliver-great-systems/bin/lib/commands.cjs +570 -0
  94. package/deliver-great-systems/bin/lib/config.cjs +417 -0
  95. package/deliver-great-systems/bin/lib/conflict-agent.cjs +1063 -0
  96. package/deliver-great-systems/bin/lib/conflict-agent.test.cjs +554 -0
  97. package/deliver-great-systems/bin/lib/context.cjs +929 -0
  98. package/deliver-great-systems/bin/lib/context.test.cjs +693 -0
  99. package/deliver-great-systems/bin/lib/core.cjs +744 -0
  100. package/deliver-great-systems/bin/lib/core.test.cjs +822 -0
  101. package/deliver-great-systems/bin/lib/docs.cjs +919 -0
  102. package/deliver-great-systems/bin/lib/docs.test.cjs +211 -0
  103. package/deliver-great-systems/bin/lib/execution.cjs +705 -0
  104. package/deliver-great-systems/bin/lib/execution.test.cjs +1472 -0
  105. package/deliver-great-systems/bin/lib/frontmatter.cjs +324 -0
  106. package/deliver-great-systems/bin/lib/ideas.cjs +1406 -0
  107. package/deliver-great-systems/bin/lib/ideas.test.cjs +1417 -0
  108. package/deliver-great-systems/bin/lib/identity.cjs +125 -0
  109. package/deliver-great-systems/bin/lib/init.cjs +1114 -0
  110. package/deliver-great-systems/bin/lib/init.test.cjs +1271 -0
  111. package/deliver-great-systems/bin/lib/jobs.cjs +2015 -0
  112. package/deliver-great-systems/bin/lib/jobs.test.cjs +2619 -0
  113. package/deliver-great-systems/bin/lib/merge-conflicts.cjs +654 -0
  114. package/deliver-great-systems/bin/lib/merge-conflicts.test.cjs +370 -0
  115. package/deliver-great-systems/bin/lib/migration.cjs +352 -0
  116. package/deliver-great-systems/bin/lib/migration.test.cjs +582 -0
  117. package/deliver-great-systems/bin/lib/milestone.cjs +243 -0
  118. package/deliver-great-systems/bin/lib/overlap.cjs +437 -0
  119. package/deliver-great-systems/bin/lib/overlap.test.cjs +747 -0
  120. package/deliver-great-systems/bin/lib/path-audit.test.cjs +384 -0
  121. package/deliver-great-systems/bin/lib/paths.cjs +144 -0
  122. package/deliver-great-systems/bin/lib/paths.test.cjs +486 -0
  123. package/deliver-great-systems/bin/lib/phase.cjs +910 -0
  124. package/deliver-great-systems/bin/lib/projects.cjs +691 -0
  125. package/deliver-great-systems/bin/lib/projects.test.cjs +871 -0
  126. package/deliver-great-systems/bin/lib/repos.cjs +1432 -0
  127. package/deliver-great-systems/bin/lib/repos.test.cjs +1882 -0
  128. package/deliver-great-systems/bin/lib/roadmap.cjs +305 -0
  129. package/deliver-great-systems/bin/lib/search.cjs +570 -0
  130. package/deliver-great-systems/bin/lib/specs.cjs +1303 -0
  131. package/deliver-great-systems/bin/lib/state.cjs +893 -0
  132. package/deliver-great-systems/bin/lib/template.cjs +228 -0
  133. package/deliver-great-systems/bin/lib/test-helpers.cjs +291 -0
  134. package/deliver-great-systems/bin/lib/verify.cjs +796 -0
  135. package/deliver-great-systems/references/checkpoints.md +776 -0
  136. package/deliver-great-systems/references/conflict-resolution.md +66 -0
  137. package/deliver-great-systems/references/context-tiers.md +166 -0
  138. package/deliver-great-systems/references/continuation-format.md +249 -0
  139. package/deliver-great-systems/references/decimal-phase-calculation.md +67 -0
  140. package/deliver-great-systems/references/git-integration.md +250 -0
  141. package/deliver-great-systems/references/git-planning-commit.md +40 -0
  142. package/deliver-great-systems/references/model-profile-resolution.md +36 -0
  143. package/deliver-great-systems/references/model-profiles.md +95 -0
  144. package/deliver-great-systems/references/phase-argument-parsing.md +61 -0
  145. package/deliver-great-systems/references/planning-config.md +224 -0
  146. package/deliver-great-systems/references/questioning.md +162 -0
  147. package/deliver-great-systems/references/spec-review-loop.md +177 -0
  148. package/deliver-great-systems/references/tdd.md +265 -0
  149. package/deliver-great-systems/references/ui-brand.md +160 -0
  150. package/deliver-great-systems/references/verification-patterns.md +612 -0
  151. package/deliver-great-systems/templates/DEBUG.md +166 -0
  152. package/deliver-great-systems/templates/UAT.md +251 -0
  153. package/deliver-great-systems/templates/VALIDATION.md +95 -0
  154. package/deliver-great-systems/templates/claude-md.md +74 -0
  155. package/deliver-great-systems/templates/codebase/architecture.md +257 -0
  156. package/deliver-great-systems/templates/codebase/concerns.md +312 -0
  157. package/deliver-great-systems/templates/codebase/conventions.md +309 -0
  158. package/deliver-great-systems/templates/codebase/integrations.md +282 -0
  159. package/deliver-great-systems/templates/codebase/stack.md +188 -0
  160. package/deliver-great-systems/templates/codebase/structure.md +287 -0
  161. package/deliver-great-systems/templates/codebase/testing.md +482 -0
  162. package/deliver-great-systems/templates/config.json +38 -0
  163. package/deliver-great-systems/templates/context.md +354 -0
  164. package/deliver-great-systems/templates/continue-here.md +80 -0
  165. package/deliver-great-systems/templates/debug-subagent-prompt.md +93 -0
  166. package/deliver-great-systems/templates/discovery.md +148 -0
  167. package/deliver-great-systems/templates/milestone-archive.md +125 -0
  168. package/deliver-great-systems/templates/milestone.md +117 -0
  169. package/deliver-great-systems/templates/phase-prompt.md +615 -0
  170. package/deliver-great-systems/templates/planner-subagent-prompt.md +119 -0
  171. package/deliver-great-systems/templates/project.md +186 -0
  172. package/deliver-great-systems/templates/requirements.md +233 -0
  173. package/deliver-great-systems/templates/research-project/ARCHITECTURE.md +206 -0
  174. package/deliver-great-systems/templates/research-project/FEATURES.md +149 -0
  175. package/deliver-great-systems/templates/research-project/PITFALLS.md +202 -0
  176. package/deliver-great-systems/templates/research-project/STACK.md +122 -0
  177. package/deliver-great-systems/templates/research-project/SUMMARY.md +172 -0
  178. package/deliver-great-systems/templates/research.md +554 -0
  179. package/deliver-great-systems/templates/retrospective.md +54 -0
  180. package/deliver-great-systems/templates/roadmap.md +204 -0
  181. package/deliver-great-systems/templates/state.md +178 -0
  182. package/deliver-great-systems/templates/summary-complex.md +59 -0
  183. package/deliver-great-systems/templates/summary-minimal.md +41 -0
  184. package/deliver-great-systems/templates/summary-standard.md +48 -0
  185. package/deliver-great-systems/templates/summary.md +253 -0
  186. package/deliver-great-systems/templates/user-setup.md +313 -0
  187. package/deliver-great-systems/templates/verification-report.md +324 -0
  188. package/deliver-great-systems/workflows/add-doc.md +151 -0
  189. package/deliver-great-systems/workflows/add-idea.md +96 -0
  190. package/deliver-great-systems/workflows/add-phase.md +120 -0
  191. package/deliver-great-systems/workflows/add-tests.md +359 -0
  192. package/deliver-great-systems/workflows/add-todo.md +162 -0
  193. package/deliver-great-systems/workflows/approve-spec.md +194 -0
  194. package/deliver-great-systems/workflows/audit-milestone.md +364 -0
  195. package/deliver-great-systems/workflows/audit-phase.md +462 -0
  196. package/deliver-great-systems/workflows/cancel-job.md +108 -0
  197. package/deliver-great-systems/workflows/check-todos.md +181 -0
  198. package/deliver-great-systems/workflows/cleanup.md +247 -0
  199. package/deliver-great-systems/workflows/codereview.md +526 -0
  200. package/deliver-great-systems/workflows/complete-milestone.md +1298 -0
  201. package/deliver-great-systems/workflows/consolidate-ideas.md +365 -0
  202. package/deliver-great-systems/workflows/create-milestone-job.md +177 -0
  203. package/deliver-great-systems/workflows/develop-idea.md +544 -0
  204. package/deliver-great-systems/workflows/diagnose-issues.md +231 -0
  205. package/deliver-great-systems/workflows/discovery-phase.md +301 -0
  206. package/deliver-great-systems/workflows/discuss-idea.md +263 -0
  207. package/deliver-great-systems/workflows/discuss-phase.md +733 -0
  208. package/deliver-great-systems/workflows/execute-phase.md +571 -0
  209. package/deliver-great-systems/workflows/execute-plan.md +592 -0
  210. package/deliver-great-systems/workflows/find-related-ideas.md +271 -0
  211. package/deliver-great-systems/workflows/health.md +173 -0
  212. package/deliver-great-systems/workflows/help.md +997 -0
  213. package/deliver-great-systems/workflows/import-spec.md +381 -0
  214. package/deliver-great-systems/workflows/init-product.md +767 -0
  215. package/deliver-great-systems/workflows/insert-phase.md +138 -0
  216. package/deliver-great-systems/workflows/list-docs.md +119 -0
  217. package/deliver-great-systems/workflows/list-ideas.md +154 -0
  218. package/deliver-great-systems/workflows/list-jobs.md +89 -0
  219. package/deliver-great-systems/workflows/list-phase-assumptions.md +192 -0
  220. package/deliver-great-systems/workflows/list-specs.md +101 -0
  221. package/deliver-great-systems/workflows/map-codebase.md +621 -0
  222. package/deliver-great-systems/workflows/new-milestone.md +591 -0
  223. package/deliver-great-systems/workflows/new-project.md +1113 -0
  224. package/deliver-great-systems/workflows/node-repair.md +94 -0
  225. package/deliver-great-systems/workflows/overlap-check.md +86 -0
  226. package/deliver-great-systems/workflows/pause-work.md +134 -0
  227. package/deliver-great-systems/workflows/plan-milestone-gaps.md +306 -0
  228. package/deliver-great-systems/workflows/plan-phase.md +698 -0
  229. package/deliver-great-systems/workflows/progress.md +386 -0
  230. package/deliver-great-systems/workflows/quick.md +845 -0
  231. package/deliver-great-systems/workflows/refine-spec.md +275 -0
  232. package/deliver-great-systems/workflows/reject-idea.md +109 -0
  233. package/deliver-great-systems/workflows/remove-doc.md +117 -0
  234. package/deliver-great-systems/workflows/remove-phase.md +163 -0
  235. package/deliver-great-systems/workflows/research-idea.md +325 -0
  236. package/deliver-great-systems/workflows/research-phase.md +81 -0
  237. package/deliver-great-systems/workflows/restore-idea.md +101 -0
  238. package/deliver-great-systems/workflows/resume-project.md +311 -0
  239. package/deliver-great-systems/workflows/rollback-job.md +130 -0
  240. package/deliver-great-systems/workflows/run-job.md +498 -0
  241. package/deliver-great-systems/workflows/search.md +130 -0
  242. package/deliver-great-systems/workflows/set-profile.md +83 -0
  243. package/deliver-great-systems/workflows/settings.md +470 -0
  244. package/deliver-great-systems/workflows/transition.md +563 -0
  245. package/deliver-great-systems/workflows/undo-consolidation.md +155 -0
  246. package/deliver-great-systems/workflows/update-idea.md +157 -0
  247. package/deliver-great-systems/workflows/update.md +242 -0
  248. package/deliver-great-systems/workflows/validate-phase.md +177 -0
  249. package/deliver-great-systems/workflows/verify-phase.md +253 -0
  250. package/deliver-great-systems/workflows/verify-work.md +671 -0
  251. package/deliver-great-systems/workflows/write-spec.md +450 -0
  252. package/hooks/dist/dgs-check-update.js +62 -0
  253. package/hooks/dist/dgs-context-monitor.js +141 -0
  254. package/hooks/dist/dgs-statusline.js +115 -0
  255. package/package.json +60 -0
  256. package/scripts/build-hooks.js +43 -0
@@ -0,0 +1,691 @@
1
+ /**
2
+ * Projects — Project lifecycle: subfolder creation, state reading,
3
+ * PROJECTS.md regeneration, repo tag scanning, project completion
4
+ *
5
+ * This module is the foundation for all Phase 3 commands: new-project,
6
+ * switch-project, list-projects, complete-project, and the projects CLI subcommand.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { safeReadFile, getProjectFolders, generateSlugInternal, isV2Install, output, error, loadConfig, getProjectDir } = require('./core.cjs');
12
+ const { writeConfigField } = require('./config.cjs');
13
+ const { getPlanningRoot } = require('./paths.cjs');
14
+
15
+ // ─── Constants ──────────────────────────────────────────────────────────────
16
+
17
+ const STANDARD_DIRS = ['phases', 'research', 'todos', 'quick', 'debug'];
18
+
19
+ // ─── Project Subfolder Creation ─────────────────────────────────────────────
20
+
21
+ /**
22
+ * Create a project subfolder under .planning/projects/<slug>/ with all standard
23
+ * planning files and directories.
24
+ *
25
+ * @param {string} cwd - Working directory (product root)
26
+ * @param {string} slug - Project slug (lowercase, hyphens, no special chars)
27
+ * @param {string} name - Human-readable project name
28
+ * @param {Object} [options] - Optional settings
29
+ * @param {string} [options.initial_repos] - Comma-separated initial repo names
30
+ * @returns {{ created: boolean, path?: string, reason?: string }}
31
+ */
32
+ function createProjectSubfolder(cwd, slug, name, options) {
33
+ const projectDir = getProjectDir(cwd, slug);
34
+ // Ensure .planning/projects/ container exists
35
+ fs.mkdirSync(path.dirname(projectDir), { recursive: true });
36
+
37
+ // Don't overwrite existing project
38
+ if (fs.existsSync(projectDir)) {
39
+ return { created: false, reason: 'already_exists' };
40
+ }
41
+
42
+ // Create project directory
43
+ fs.mkdirSync(projectDir, { recursive: true });
44
+
45
+ // Create standard subdirectories
46
+ for (const dir of STANDARD_DIRS) {
47
+ fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
48
+ }
49
+
50
+ // Create standard files with templates
51
+ const opts = options || {};
52
+
53
+ let projectMd = `# Project: ${name}\n\n## What This Is\n\n[Describe the project scope and goals]\n`;
54
+ if (opts.initial_repos) {
55
+ projectMd += `\n## Initial Repos\n\n${opts.initial_repos}\n`;
56
+ }
57
+ fs.writeFileSync(path.join(projectDir, 'PROJECT.md'), projectMd);
58
+
59
+ fs.writeFileSync(
60
+ path.join(projectDir, 'REQUIREMENTS.md'),
61
+ `# Requirements: ${name}\n\n## v1 Requirements\n\n`
62
+ );
63
+
64
+ fs.writeFileSync(
65
+ path.join(projectDir, 'ROADMAP.md'),
66
+ `# Roadmap: ${name}\n\n## Phases\n\n`
67
+ );
68
+
69
+ fs.writeFileSync(
70
+ path.join(projectDir, 'STATE.md'),
71
+ `# Project State\n\n## Current Position\n\nPhase: Not started\nStatus: New\nProgress: [░░░░░░░░░░] 0%\n`
72
+ );
73
+
74
+ return { created: true, path: projectDir };
75
+ }
76
+
77
+ // ─── Project State Reading ──────────────────────────────────────────────────
78
+
79
+ /**
80
+ * Read and parse a project's STATE.md at .planning/projects/<slug>/STATE.md
81
+ * to extract status information.
82
+ *
83
+ * Extracts:
84
+ * - phase: From "Phase: ..." line
85
+ * - status: From "Status: ..." line
86
+ * - progress: From "Progress: [...] NN%" line (integer)
87
+ * - completed_date: From "Completed: YYYY-MM-DD" line
88
+ *
89
+ * @param {string} cwd - Working directory (product root)
90
+ * @param {string} slug - Project slug
91
+ * @returns {{ phase: string, status: string, progress: number, completed_date: string|null } | null}
92
+ */
93
+ function readProjectState(cwd, slug) {
94
+ const statePath = path.join(getProjectDir(cwd, slug), 'STATE.md');
95
+ const content = safeReadFile(statePath);
96
+ if (!content) return null;
97
+
98
+ const phaseMatch = content.match(/Phase:\s*(.+)/i);
99
+ const statusMatch = content.match(/Status:\s*(.+)/i);
100
+ const progressMatch = content.match(/Progress:\s*\[[^\]]*\]\s*(\d+)%/);
101
+ const completedMatch = content.match(/Completed:\s*(\d{4}-\d{2}-\d{2})/);
102
+
103
+ return {
104
+ phase: phaseMatch ? phaseMatch[1].trim() : 'Unknown',
105
+ status: statusMatch ? statusMatch[1].trim() : 'Unknown',
106
+ progress: progressMatch ? parseInt(progressMatch[1], 10) : 0,
107
+ completed_date: completedMatch ? completedMatch[1] : null,
108
+ };
109
+ }
110
+
111
+ // ─── Repo Tag Scanning ──────────────────────────────────────────────────────
112
+
113
+ /**
114
+ * Scan a project's plan files for <repos>...</repos> tags and extract
115
+ * all unique repo names referenced.
116
+ *
117
+ * Scans all PLAN.md files under .planning/projects/<slug>/phases/ subdirectories.
118
+ *
119
+ * @param {string} cwd - Working directory (product root)
120
+ * @param {string} slug - Project slug
121
+ * @returns {string[]} Sorted, deduplicated array of repo names
122
+ */
123
+ function scanProjectReposTags(cwd, slug) {
124
+ const phasesDir = path.join(getProjectDir(cwd, slug), 'phases');
125
+ const allRepos = new Set();
126
+
127
+ try {
128
+ const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
129
+ .filter(e => e.isDirectory())
130
+ .map(e => e.name);
131
+
132
+ for (const phaseDir of phaseDirs) {
133
+ const fullPhaseDir = path.join(phasesDir, phaseDir);
134
+ let planFiles;
135
+ try {
136
+ planFiles = fs.readdirSync(fullPhaseDir)
137
+ .filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
138
+ } catch {
139
+ continue;
140
+ }
141
+
142
+ for (const planFile of planFiles) {
143
+ const content = safeReadFile(path.join(fullPhaseDir, planFile));
144
+ if (!content) continue;
145
+
146
+ const matches = content.match(/<repos>([^<]+)<\/repos>/g);
147
+ if (!matches) continue;
148
+
149
+ for (const match of matches) {
150
+ const inner = match.replace(/<\/?repos>/g, '');
151
+ inner
152
+ .split(',')
153
+ .map(n => n.trim())
154
+ .filter(n => n)
155
+ .forEach(n => allRepos.add(n));
156
+ }
157
+ }
158
+ }
159
+ } catch {
160
+ // phases directory doesn't exist or other error
161
+ }
162
+
163
+ return Array.from(allRepos).sort();
164
+ }
165
+
166
+ // ─── PROJECTS.md Regeneration ───────────────────────────────────────────────
167
+
168
+ /**
169
+ * Regenerate .planning/PROJECTS.md by scanning all project subfolders.
170
+ *
171
+ * Reads each project's STATE.md for status/phase/progress, scans plan
172
+ * <repos> tags for repos touched, and writes the derived PROJECTS.md.
173
+ *
174
+ * Ghost project guard: projects whose STATE.md is missing or unreadable
175
+ * are warned about and omitted from the output.
176
+ *
177
+ * @param {string} cwd - Working directory (product root)
178
+ * @returns {{ projects: Array, warnings: string[] }}
179
+ */
180
+ function regenerateProjectsMd(cwd) {
181
+ const slugs = getProjectFolders(cwd);
182
+ const warnings = [];
183
+ const projects = [];
184
+
185
+ for (const slug of slugs) {
186
+ const state = readProjectState(cwd, slug);
187
+ if (!state) {
188
+ warnings.push(`Ghost project: ${slug} (STATE.md missing or unreadable)`);
189
+ continue;
190
+ }
191
+
192
+ const reposTouched = scanProjectReposTags(cwd, slug);
193
+ projects.push({
194
+ name: slug,
195
+ status: state.status,
196
+ repos_touched: reposTouched.join(', '),
197
+ current_phase: state.phase,
198
+ completed_date: state.completed_date || '',
199
+ progress: state.progress,
200
+ });
201
+ }
202
+
203
+ // Write PROJECTS.md
204
+ let content = '# Projects\n\n';
205
+ content += '## Active\n\n';
206
+ content += '| Project | Status | Repos Touched | Current Phase |\n';
207
+ content += '|---------|--------|---------------|---------------|\n';
208
+
209
+ const active = projects.filter(p => !p.status.toLowerCase().includes('completed'));
210
+ for (const proj of active) {
211
+ content += `| ${proj.name} | ${proj.status} | ${proj.repos_touched} | ${proj.current_phase} |\n`;
212
+ }
213
+
214
+ content += '\n## Completed\n\n';
215
+ content += '| Project | Completed | Duration |\n';
216
+ content += '|---------|-----------|----------|\n';
217
+
218
+ const completed = projects.filter(p => p.status.toLowerCase().includes('completed'));
219
+ for (const proj of completed) {
220
+ content += `| ${proj.name} | ${proj.completed_date} | |\n`;
221
+ }
222
+
223
+ fs.writeFileSync(path.join(getPlanningRoot(cwd), 'PROJECTS.md'), content);
224
+
225
+ return { projects, warnings };
226
+ }
227
+
228
+ // ─── Project Completion ─────────────────────────────────────────────────────
229
+
230
+ /**
231
+ * Mark a project as completed by updating its STATE.md at
232
+ * .planning/projects/<slug>/STATE.md.
233
+ *
234
+ * - Updates "Status:" line to "completed"
235
+ * - Adds "Completed: YYYY-MM-DD" line with today's date
236
+ * - Preserves existing STATE.md content structure
237
+ *
238
+ * @param {string} cwd - Working directory (product root)
239
+ * @param {string} slug - Project slug
240
+ * @returns {{ completed: boolean, date?: string, error?: string }}
241
+ */
242
+ function completeProject(cwd, slug) {
243
+ const projectDir = getProjectDir(cwd, slug);
244
+ const statePath = path.join(projectDir, 'STATE.md');
245
+
246
+ // Check project directory exists
247
+ if (!fs.existsSync(projectDir)) {
248
+ return { completed: false, error: 'not_found' };
249
+ }
250
+
251
+ // Check STATE.md exists
252
+ const content = safeReadFile(statePath);
253
+ if (!content) {
254
+ return { completed: false, error: 'no_state_md' };
255
+ }
256
+
257
+ // Check if already completed
258
+ if (/Status:\s*completed/i.test(content)) {
259
+ return { completed: false, error: 'already_completed' };
260
+ }
261
+
262
+ const today = new Date().toISOString().slice(0, 10);
263
+ let updated = content;
264
+
265
+ // Update or add Status line
266
+ if (/Status:\s*.+/i.test(updated)) {
267
+ updated = updated.replace(/Status:\s*.+/i, 'Status: completed');
268
+ } else {
269
+ updated += '\nStatus: completed\n';
270
+ }
271
+
272
+ // Add or update Completed date
273
+ if (/Completed:\s*\d{4}-\d{2}-\d{2}/.test(updated)) {
274
+ updated = updated.replace(/Completed:\s*\d{4}-\d{2}-\d{2}/, `Completed: ${today}`);
275
+ } else {
276
+ // Add after Status line
277
+ updated = updated.replace(/(Status: completed)/, `$1\nCompleted: ${today}`);
278
+ }
279
+
280
+ fs.writeFileSync(statePath, updated);
281
+
282
+ return { completed: true, date: today };
283
+ }
284
+
285
+ // ─── Project Reactivation ────────────────────────────────────────────────────
286
+
287
+ /**
288
+ * Reactivate a completed project by updating its STATE.md at
289
+ * .planning/projects/<slug>/STATE.md.
290
+ *
291
+ * - Changes "Status: completed" back to "Status: In progress"
292
+ * - Removes the "Completed: YYYY-MM-DD" line entirely
293
+ * - Preserves all other STATE.md content
294
+ *
295
+ * @param {string} cwd - Working directory (product root)
296
+ * @param {string} slug - Project slug
297
+ * @returns {{ reactivated: boolean, error?: string }}
298
+ */
299
+ function reactivateProject(cwd, slug) {
300
+ const projectDir = getProjectDir(cwd, slug);
301
+ const statePath = path.join(projectDir, 'STATE.md');
302
+
303
+ // Check project directory exists
304
+ if (!fs.existsSync(projectDir)) {
305
+ return { reactivated: false, error: 'not_found' };
306
+ }
307
+
308
+ // Check STATE.md exists
309
+ const content = safeReadFile(statePath);
310
+ if (!content) {
311
+ return { reactivated: false, error: 'no_state_md' };
312
+ }
313
+
314
+ // Check project IS completed
315
+ if (!/Status:\s*completed/i.test(content)) {
316
+ return { reactivated: false, error: 'not_completed' };
317
+ }
318
+
319
+ let updated = content;
320
+
321
+ // Change Status back to In progress
322
+ updated = updated.replace(/Status:\s*completed/i, 'Status: In progress');
323
+
324
+ // Remove the Completed date line entirely (including its trailing newline)
325
+ updated = updated.replace(/Completed:\s*\d{4}-\d{2}-\d{2}\n?/, '');
326
+
327
+ fs.writeFileSync(statePath, updated);
328
+
329
+ return { reactivated: true };
330
+ }
331
+
332
+ // ─── PROJECTS.md Parsing ────────────────────────────────────────────────────
333
+
334
+ /**
335
+ * Parse .planning/PROJECTS.md into structured data.
336
+ *
337
+ * Returns null if the file doesn't exist or doesn't start with '# Projects'.
338
+ *
339
+ * @param {string} cwd - Working directory (product root)
340
+ * @returns {{ active: Array<{name, status, repos_touched, current_phase}>, completed: Array<{name, completed, duration}> } | null}
341
+ */
342
+ function parseProjectsMd(cwd) {
343
+ const filePath = path.join(getPlanningRoot(cwd), 'PROJECTS.md');
344
+ const content = safeReadFile(filePath);
345
+ if (!content) return null;
346
+
347
+ // v2 marker check
348
+ if (!content.startsWith('# Projects')) return null;
349
+
350
+ const active = [];
351
+ const completed = [];
352
+
353
+ // Split into Active and Completed sections
354
+ const sections = content.split('## Completed');
355
+ const activeSection = sections[0] || '';
356
+ const completedSection = sections[1] || '';
357
+
358
+ // Parse Active table
359
+ const activeLines = activeSection.split('\n');
360
+ let inActiveTable = false;
361
+ for (const line of activeLines) {
362
+ if (line.startsWith('|') && line.includes('Project') && line.includes('Status')) {
363
+ inActiveTable = true;
364
+ continue;
365
+ }
366
+ if (inActiveTable && line.startsWith('|---')) {
367
+ continue;
368
+ }
369
+ if (inActiveTable && line.startsWith('|')) {
370
+ const cells = line.split('|').map(c => c.trim());
371
+ if (cells.length >= 5 && cells[1]) {
372
+ active.push({
373
+ name: cells[1],
374
+ status: cells[2],
375
+ repos_touched: cells[3],
376
+ current_phase: cells[4],
377
+ });
378
+ }
379
+ } else if (inActiveTable) {
380
+ inActiveTable = false;
381
+ }
382
+ }
383
+
384
+ // Parse Completed table
385
+ const completedLines = completedSection.split('\n');
386
+ let inCompletedTable = false;
387
+ for (const line of completedLines) {
388
+ if (line.startsWith('|') && line.includes('Project') && line.includes('Completed')) {
389
+ inCompletedTable = true;
390
+ continue;
391
+ }
392
+ if (inCompletedTable && line.startsWith('|---')) {
393
+ continue;
394
+ }
395
+ if (inCompletedTable && line.startsWith('|')) {
396
+ const cells = line.split('|').map(c => c.trim());
397
+ if (cells.length >= 4 && cells[1]) {
398
+ completed.push({
399
+ name: cells[1],
400
+ completed: cells[2],
401
+ duration: cells[3],
402
+ });
403
+ }
404
+ } else if (inCompletedTable) {
405
+ inCompletedTable = false;
406
+ }
407
+ }
408
+
409
+ return { active, completed };
410
+ }
411
+
412
+ // ─── Slug Prefix Collision Detection ─────────────────────────────────────────
413
+
414
+ /**
415
+ * Check if a new project slug has a prefix collision with any existing slug.
416
+ *
417
+ * A prefix collision occurs when:
418
+ * - An existing slug is a prefix of the new slug (e.g., "api-v2" is prefix of "api-v2-hotfix")
419
+ * - The new slug is a prefix of an existing slug (e.g., "api" is prefix of "api-v2")
420
+ *
421
+ * @param {string} newSlug - The new project slug to check
422
+ * @param {string[]} existingSlugs - Array of existing project slugs
423
+ * @returns {{ collision: boolean, collidingSlug?: string }}
424
+ */
425
+ function checkSlugPrefixCollision(newSlug, existingSlugs) {
426
+ for (const existing of existingSlugs) {
427
+ if (existing === newSlug) continue; // Exact match is handled by createProjectSubfolder
428
+ if (existing.startsWith(newSlug) || newSlug.startsWith(existing)) {
429
+ return { collision: true, collidingSlug: existing };
430
+ }
431
+ }
432
+ return { collision: false };
433
+ }
434
+
435
+ // ─── CLI Command Functions ───────────────────────────────────────────────────
436
+
437
+ /**
438
+ * CLI: Create a new project subfolder and set it as the active project.
439
+ *
440
+ * - Requires v2 install
441
+ * - Generates slug from name
442
+ * - Creates subfolder with standard structure
443
+ * - Sets current_project in config.json
444
+ * - Regenerates PROJECTS.md
445
+ *
446
+ * @param {string} cwd - Working directory (product root)
447
+ * @param {string} name - Human-readable project name
448
+ * @param {Object} [options] - Optional settings
449
+ * @param {string} [options.initial_repos] - Comma-separated initial repo names
450
+ * @param {boolean} raw - Raw output mode
451
+ */
452
+ function cmdProjectsCreate(cwd, name, options, raw) {
453
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
454
+ if (!name) error('Project name required. Usage: projects create <name>');
455
+
456
+ const slug = generateSlugInternal(name);
457
+ if (!slug) error('Invalid project name — could not generate slug.');
458
+
459
+ // Check for slug prefix collisions before creating
460
+ const existingSlugs = getProjectFolders(cwd);
461
+ const prefixCheck = checkSlugPrefixCollision(slug, existingSlugs);
462
+
463
+ const result = createProjectSubfolder(cwd, slug, name, options);
464
+ if (!result.created) {
465
+ error(`Project "${slug}" already exists.`);
466
+ }
467
+
468
+ writeConfigField(cwd, 'current_project', slug);
469
+ regenerateProjectsMd(cwd);
470
+
471
+ const outputData = { created: true, slug, name, path: result.path };
472
+ if (prefixCheck.collision) {
473
+ outputData.warning = `Slug '${slug}' shares prefix with existing project '${prefixCheck.collidingSlug}'. Branch names may be ambiguous.`;
474
+ }
475
+ output(outputData, raw);
476
+ }
477
+
478
+ /**
479
+ * CLI: Switch the active project context.
480
+ *
481
+ * - Requires v2 install
482
+ * - Matches by exact slug or case-insensitive partial match
483
+ * - Updates current_project in config.json
484
+ *
485
+ * @param {string} cwd - Working directory (product root)
486
+ * @param {string} name - Project slug or name to switch to
487
+ * @param {boolean} raw - Raw output mode
488
+ */
489
+ function cmdProjectsSwitch(cwd, name, raw) {
490
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
491
+ if (!name) error('Project name required. Usage: projects switch <name>');
492
+
493
+ const projects = getProjectFolders(cwd);
494
+ if (projects.length === 0) {
495
+ error('No projects found. Create one first with: projects create <name>');
496
+ }
497
+
498
+ // Try exact slug match first
499
+ let matchedSlug = projects.find(p => p === name);
500
+
501
+ // Try case-insensitive match on slug
502
+ if (!matchedSlug) {
503
+ const lower = name.toLowerCase();
504
+ matchedSlug = projects.find(p => p.toLowerCase() === lower);
505
+ }
506
+
507
+ // Try slug generated from name
508
+ if (!matchedSlug) {
509
+ const generatedSlug = generateSlugInternal(name);
510
+ if (generatedSlug) {
511
+ matchedSlug = projects.find(p => p === generatedSlug);
512
+ }
513
+ }
514
+
515
+ if (!matchedSlug) {
516
+ error(`Project "${name}" not found. Available: ${projects.join(', ')}`);
517
+ }
518
+
519
+ // Guard: reject switching to completed projects
520
+ const state = readProjectState(cwd, matchedSlug);
521
+ if (state && state.status.toLowerCase().includes('completed')) {
522
+ error(`Project "${matchedSlug}" is completed. Use /dgs:reactivate-project to resume work on it.`);
523
+ }
524
+
525
+ writeConfigField(cwd, 'current_project', matchedSlug);
526
+
527
+ output({ switched: true, slug: matchedSlug, status: state }, raw);
528
+ }
529
+
530
+ /**
531
+ * CLI: List all projects with their status and repos touched.
532
+ *
533
+ * - Requires v2 install
534
+ * - Regenerates PROJECTS.md to ensure freshness
535
+ * - Returns active and completed project lists
536
+ *
537
+ * @param {string} cwd - Working directory (product root)
538
+ * @param {boolean} raw - Raw output mode
539
+ */
540
+ function cmdProjectsList(cwd, raw) {
541
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
542
+
543
+ const result = regenerateProjectsMd(cwd);
544
+ const active = result.projects.filter(p => !p.status.toLowerCase().includes('completed'));
545
+ const completed = result.projects.filter(p => p.status.toLowerCase().includes('completed'));
546
+
547
+ output({ active, completed, warnings: result.warnings }, raw);
548
+ }
549
+
550
+ /**
551
+ * CLI: Mark a project as completed.
552
+ *
553
+ * - Requires v2 install
554
+ * - Uses options.slug or falls back to current_project from config
555
+ * - Marks project as completed in STATE.md
556
+ * - Clears current_project in config.json
557
+ * - Regenerates PROJECTS.md
558
+ * - Reports remaining active projects
559
+ *
560
+ * @param {string} cwd - Working directory (product root)
561
+ * @param {Object} options - Options
562
+ * @param {string} [options.slug] - Specific project slug to complete
563
+ * @param {boolean} raw - Raw output mode
564
+ */
565
+ function cmdProjectsComplete(cwd, options, raw) {
566
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
567
+
568
+ const opts = options || {};
569
+ let slug = opts.slug;
570
+
571
+ if (!slug) {
572
+ const cfg = loadConfig(cwd);
573
+ slug = cfg.current_project;
574
+ }
575
+
576
+ if (!slug) {
577
+ error('No active project. Switch to one first with: projects switch <name>');
578
+ }
579
+
580
+ const result = completeProject(cwd, slug);
581
+ if (!result.completed) {
582
+ if (result.error === 'not_found') {
583
+ error(`Project "${slug}" not found.`);
584
+ } else if (result.error === 'no_state_md') {
585
+ error(`Project "${slug}" has no STATE.md file.`);
586
+ } else if (result.error === 'already_completed') {
587
+ error(`Project "${slug}" is already completed.`);
588
+ } else {
589
+ error(`Failed to complete project "${slug}": ${result.error}`);
590
+ }
591
+ }
592
+
593
+ const currentCfg = loadConfig(cwd);
594
+ if (currentCfg.current_project === slug) {
595
+ writeConfigField(cwd, 'current_project', null);
596
+ }
597
+ const regenResult = regenerateProjectsMd(cwd);
598
+ const remainingActive = regenResult.projects.filter(p => !p.status.toLowerCase().includes('completed'));
599
+
600
+ output({ completed: true, slug, date: result.date, remaining_active: remainingActive }, raw);
601
+ }
602
+
603
+ /**
604
+ * CLI: Reactivate a completed project.
605
+ *
606
+ * - Requires v2 install
607
+ * - Uses options.slug or falls back to current_project from config
608
+ * - Restores project status to "In progress" in STATE.md
609
+ * - Removes Completed date from STATE.md
610
+ * - Optionally sets the project as current_project in config.json
611
+ * - Regenerates PROJECTS.md
612
+ * - Reports remaining active projects
613
+ *
614
+ * @param {string} cwd - Working directory (product root)
615
+ * @param {Object} options - Options
616
+ * @param {string} [options.slug] - Specific project slug to reactivate
617
+ * @param {boolean} [options.set_current] - Set as current project after reactivation
618
+ * @param {boolean} raw - Raw output mode
619
+ */
620
+ function cmdProjectsReactivate(cwd, options, raw) {
621
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
622
+
623
+ const opts = options || {};
624
+ let slug = opts.slug;
625
+
626
+ if (!slug) {
627
+ const cfg = loadConfig(cwd);
628
+ slug = cfg.current_project;
629
+ }
630
+
631
+ if (!slug) {
632
+ error('No active project. Use --slug <name> to specify which project to reactivate.');
633
+ }
634
+
635
+ const result = reactivateProject(cwd, slug);
636
+ if (!result.reactivated) {
637
+ if (result.error === 'not_found') {
638
+ error(`Project "${slug}" not found.`);
639
+ } else if (result.error === 'no_state_md') {
640
+ error(`Project "${slug}" has no STATE.md file.`);
641
+ } else if (result.error === 'not_completed') {
642
+ error(`Project "${slug}" is not completed.`);
643
+ } else {
644
+ error(`Failed to reactivate project "${slug}": ${result.error}`);
645
+ }
646
+ }
647
+
648
+ if (opts.set_current) {
649
+ writeConfigField(cwd, 'current_project', slug);
650
+ }
651
+
652
+ const regenResult = regenerateProjectsMd(cwd);
653
+ const remainingActive = regenResult.projects.filter(p => !p.status.toLowerCase().includes('completed'));
654
+
655
+ output({ reactivated: true, slug, set_as_current: !!opts.set_current, remaining_active: remainingActive.length }, raw);
656
+ }
657
+
658
+ /**
659
+ * CLI: Regenerate PROJECTS.md from disk.
660
+ *
661
+ * - Requires v2 install
662
+ * - Scans all project folders and re-derives PROJECTS.md
663
+ *
664
+ * @param {string} cwd - Working directory (product root)
665
+ * @param {boolean} raw - Raw output mode
666
+ */
667
+ function cmdProjectsRegenerate(cwd, raw) {
668
+ if (!isV2Install(cwd)) error('v2 install required. Run /dgs:init-product first.');
669
+
670
+ const result = regenerateProjectsMd(cwd);
671
+ output({ regenerated: true, projects: result.projects, warnings: result.warnings }, raw);
672
+ }
673
+
674
+ // ─── Exports ────────────────────────────────────────────────────────────────
675
+
676
+ module.exports = {
677
+ createProjectSubfolder,
678
+ readProjectState,
679
+ scanProjectReposTags,
680
+ regenerateProjectsMd,
681
+ completeProject,
682
+ reactivateProject,
683
+ parseProjectsMd,
684
+ checkSlugPrefixCollision,
685
+ cmdProjectsCreate,
686
+ cmdProjectsSwitch,
687
+ cmdProjectsList,
688
+ cmdProjectsComplete,
689
+ cmdProjectsReactivate,
690
+ cmdProjectsRegenerate,
691
+ };