@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,243 @@
1
+ /**
2
+ * Milestone — Milestone and requirements lifecycle operations
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const { escapeRegex, getMilestonePhaseFilter, output, error } = require('./core.cjs');
8
+ const { extractFrontmatter } = require('./frontmatter.cjs');
9
+ const { getPlanningRoot } = require('./paths.cjs');
10
+ const { writeStateMd } = require('./state.cjs');
11
+
12
+ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) {
13
+ if (!reqIdsRaw || reqIdsRaw.length === 0) {
14
+ error('requirement IDs required. Usage: requirements mark-complete REQ-01,REQ-02 or REQ-01 REQ-02');
15
+ }
16
+
17
+ // Accept comma-separated, space-separated, or bracket-wrapped: [REQ-01, REQ-02]
18
+ const reqIds = reqIdsRaw
19
+ .join(' ')
20
+ .replace(/[\[\]]/g, '')
21
+ .split(/[,\s]+/)
22
+ .map(r => r.trim())
23
+ .filter(Boolean);
24
+
25
+ if (reqIds.length === 0) {
26
+ error('no valid requirement IDs found');
27
+ }
28
+
29
+ const reqPath = path.join(getPlanningRoot(cwd), 'REQUIREMENTS.md');
30
+ if (!fs.existsSync(reqPath)) {
31
+ output({ updated: false, reason: 'REQUIREMENTS.md not found', ids: reqIds }, raw, 'no requirements file');
32
+ return;
33
+ }
34
+
35
+ let reqContent = fs.readFileSync(reqPath, 'utf-8');
36
+ const updated = [];
37
+ const notFound = [];
38
+
39
+ for (const reqId of reqIds) {
40
+ let found = false;
41
+ const reqEscaped = escapeRegex(reqId);
42
+
43
+ // Update checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID**
44
+ const checkboxPattern = new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqEscaped}\\*\\*)`, 'gi');
45
+ if (checkboxPattern.test(reqContent)) {
46
+ reqContent = reqContent.replace(checkboxPattern, '$1x$2');
47
+ found = true;
48
+ }
49
+
50
+ // Update traceability table: | REQ-ID | Phase N | Pending | → | REQ-ID | Phase N | Complete |
51
+ const tablePattern = new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi');
52
+ if (tablePattern.test(reqContent)) {
53
+ // Re-read since test() advances lastIndex for global regex
54
+ reqContent = reqContent.replace(
55
+ new RegExp(`(\\|\\s*${reqEscaped}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'),
56
+ '$1 Complete $2'
57
+ );
58
+ found = true;
59
+ }
60
+
61
+ if (found) {
62
+ updated.push(reqId);
63
+ } else {
64
+ notFound.push(reqId);
65
+ }
66
+ }
67
+
68
+ if (updated.length > 0) {
69
+ fs.writeFileSync(reqPath, reqContent, 'utf-8');
70
+ }
71
+
72
+ output({
73
+ updated: updated.length > 0,
74
+ marked_complete: updated,
75
+ not_found: notFound,
76
+ total: reqIds.length,
77
+ }, raw, `${updated.length}/${reqIds.length} requirements marked complete`);
78
+ }
79
+
80
+ function cmdMilestoneComplete(cwd, version, options, raw) {
81
+ if (!version) {
82
+ error('version required for milestone complete (e.g., v1.0)');
83
+ }
84
+
85
+ const planRoot = getPlanningRoot(cwd);
86
+ const roadmapPath = path.join(planRoot, 'ROADMAP.md');
87
+ const reqPath = path.join(planRoot, 'REQUIREMENTS.md');
88
+ const statePath = path.join(planRoot, 'STATE.md');
89
+ const milestonesPath = path.join(planRoot, 'MILESTONES.md');
90
+ const archiveDir = path.join(planRoot, 'milestones');
91
+ const phasesDir = path.join(planRoot, 'phases');
92
+ const today = new Date().toISOString().split('T')[0];
93
+ const milestoneName = options.name || version;
94
+
95
+ // Ensure archive directory exists
96
+ fs.mkdirSync(archiveDir, { recursive: true });
97
+
98
+ // Scope stats and accomplishments to only the phases belonging to the
99
+ // current milestone's ROADMAP. Uses the shared filter from core.cjs
100
+ // (same logic used by cmdPhasesList and other callers).
101
+ const isDirInMilestone = getMilestonePhaseFilter(cwd);
102
+
103
+ // Gather stats from phases (scoped to current milestone only)
104
+ let phaseCount = 0;
105
+ let totalPlans = 0;
106
+ let totalTasks = 0;
107
+ const accomplishments = [];
108
+
109
+ try {
110
+ const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
111
+ const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
112
+
113
+ for (const dir of dirs) {
114
+ if (!isDirInMilestone(dir)) continue;
115
+
116
+ phaseCount++;
117
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
118
+ const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
119
+ const summaries = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
120
+ totalPlans += plans.length;
121
+
122
+ // Extract one-liners from summaries
123
+ for (const s of summaries) {
124
+ try {
125
+ const content = fs.readFileSync(path.join(phasesDir, dir, s), 'utf-8');
126
+ const fm = extractFrontmatter(content);
127
+ if (fm['one-liner']) {
128
+ accomplishments.push(fm['one-liner']);
129
+ }
130
+ // Count tasks
131
+ const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
132
+ totalTasks += taskMatches.length;
133
+ } catch {}
134
+ }
135
+ }
136
+ } catch {}
137
+
138
+ // Archive ROADMAP.md
139
+ if (fs.existsSync(roadmapPath)) {
140
+ const roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
141
+ fs.writeFileSync(path.join(archiveDir, `${version}-ROADMAP.md`), roadmapContent, 'utf-8');
142
+ }
143
+
144
+ // Archive REQUIREMENTS.md
145
+ if (fs.existsSync(reqPath)) {
146
+ const reqContent = fs.readFileSync(reqPath, 'utf-8');
147
+ const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`;
148
+ fs.writeFileSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent, 'utf-8');
149
+ }
150
+
151
+ // Archive audit file if exists
152
+ const auditFile = path.join(planRoot, `${version}-MILESTONE-AUDIT.md`);
153
+ if (fs.existsSync(auditFile)) {
154
+ fs.renameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
155
+ }
156
+
157
+ // Create/append MILESTONES.md entry
158
+ const accomplishmentsList = accomplishments.map(a => `- ${a}`).join('\n');
159
+ const milestoneEntry = `## ${version} ${milestoneName} (Shipped: ${today})\n\n**Phases completed:** ${phaseCount} phases, ${totalPlans} plans, ${totalTasks} tasks\n\n**Key accomplishments:**\n${accomplishmentsList || '- (none recorded)'}\n\n---\n\n`;
160
+
161
+ if (fs.existsSync(milestonesPath)) {
162
+ const existing = fs.readFileSync(milestonesPath, 'utf-8');
163
+ if (!existing.trim()) {
164
+ // Empty file — treat like new
165
+ fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
166
+ } else {
167
+ // Insert after the header line(s) for reverse chronological order (newest first)
168
+ const headerMatch = existing.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
169
+ if (headerMatch) {
170
+ const header = headerMatch[1];
171
+ const rest = existing.slice(header.length);
172
+ fs.writeFileSync(milestonesPath, header + milestoneEntry + rest, 'utf-8');
173
+ } else {
174
+ // No recognizable header — prepend the entry
175
+ fs.writeFileSync(milestonesPath, milestoneEntry + existing, 'utf-8');
176
+ }
177
+ }
178
+ } else {
179
+ fs.writeFileSync(milestonesPath, `# Milestones\n\n${milestoneEntry}`, 'utf-8');
180
+ }
181
+
182
+ // Update STATE.md
183
+ if (fs.existsSync(statePath)) {
184
+ let stateContent = fs.readFileSync(statePath, 'utf-8');
185
+ stateContent = stateContent.replace(
186
+ /(\*\*Status:\*\*\s*).*/,
187
+ `$1${version} milestone complete`
188
+ );
189
+ stateContent = stateContent.replace(
190
+ /(\*\*Last Activity:\*\*\s*).*/,
191
+ `$1${today}`
192
+ );
193
+ stateContent = stateContent.replace(
194
+ /(\*\*Last Activity Description:\*\*\s*).*/,
195
+ `$1${version} milestone completed and archived`
196
+ );
197
+ writeStateMd(statePath, stateContent, cwd);
198
+ }
199
+
200
+ // Archive phase directories if requested
201
+ let phasesArchived = false;
202
+ if (options.archivePhases) {
203
+ try {
204
+ const phaseArchiveDir = path.join(archiveDir, `${version}-phases`);
205
+ fs.mkdirSync(phaseArchiveDir, { recursive: true });
206
+
207
+ const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
208
+ const phaseDirNames = phaseEntries.filter(e => e.isDirectory()).map(e => e.name);
209
+ let archivedCount = 0;
210
+ for (const dir of phaseDirNames) {
211
+ if (!isDirInMilestone(dir)) continue;
212
+ fs.renameSync(path.join(phasesDir, dir), path.join(phaseArchiveDir, dir));
213
+ archivedCount++;
214
+ }
215
+ phasesArchived = archivedCount > 0;
216
+ } catch {}
217
+ }
218
+
219
+ const result = {
220
+ version,
221
+ name: milestoneName,
222
+ date: today,
223
+ phases: phaseCount,
224
+ plans: totalPlans,
225
+ tasks: totalTasks,
226
+ accomplishments,
227
+ archived: {
228
+ roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
229
+ requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
230
+ audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
231
+ phases: phasesArchived,
232
+ },
233
+ milestones_updated: true,
234
+ state_updated: fs.existsSync(statePath),
235
+ };
236
+
237
+ output(result, raw);
238
+ }
239
+
240
+ module.exports = {
241
+ cmdRequirementsMarkComplete,
242
+ cmdMilestoneComplete,
243
+ };
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Overlap — Cross-project overlap detection at repo and file level
3
+ *
4
+ * Scans plan files across all active projects to detect which repos and files
5
+ * are touched by multiple projects simultaneously. Provides both repo-level
6
+ * and file-level overlap reporting.
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { safeReadFile, getProjectFolders, output, error, getProjectDir } = require('./core.cjs');
12
+ const { parseProjectsMd, readProjectState } = require('./projects.cjs');
13
+ const { parseReposMd, resolveFileToRepo } = require('./repos.cjs');
14
+
15
+ // ─── Severity Order ─────────────────────────────────────────────────────────
16
+
17
+ const SEVERITY_ORDER = { HIGH: 0, MEDIUM: 1, LOW: 2 };
18
+
19
+ // ─── Scan Project Plan Files ─────────────────────────────────────────────────
20
+
21
+ /**
22
+ * Scan a project's plan files and extract repos and files from XML tags.
23
+ *
24
+ * Scans all *-PLAN.md files under .planning/projects/<slug>/phases/ subdirectories.
25
+ * For each plan file, extracts all <repos> and <files> tags.
26
+ *
27
+ * @param {string} cwd - Working directory (product root)
28
+ * @param {string} slug - Project slug
29
+ * @returns {Array<{ plan: string, repos: string[], files: string[] }>}
30
+ */
31
+ function scanProjectPlanFiles(cwd, slug) {
32
+ const phasesDir = path.join(getProjectDir(cwd, slug), 'phases');
33
+ const results = [];
34
+
35
+ try {
36
+ const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
37
+ .filter(e => e.isDirectory())
38
+ .map(e => e.name);
39
+
40
+ // Exclude archived phase directories
41
+ const activePhaseDirs = excludeArchivedPhases(
42
+ phaseDirs.map(d => path.join(phasesDir, d))
43
+ ).map(d => path.basename(d));
44
+
45
+ for (const phaseDir of activePhaseDirs) {
46
+ const fullPhaseDir = path.join(phasesDir, phaseDir);
47
+ let files;
48
+ try {
49
+ files = fs.readdirSync(fullPhaseDir)
50
+ .filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md');
51
+ } catch {
52
+ continue;
53
+ }
54
+
55
+ for (const planFile of files) {
56
+ const content = safeReadFile(path.join(fullPhaseDir, planFile));
57
+ if (!content) continue;
58
+
59
+ const repos = new Set();
60
+ const filesList = new Set();
61
+
62
+ // Extract <repos> tags
63
+ const reposMatches = content.match(/<repos>([^<]*)<\/repos>/g);
64
+ if (reposMatches) {
65
+ for (const match of reposMatches) {
66
+ const inner = match.replace(/<\/?repos>/g, '');
67
+ inner.split(',').map(n => n.trim()).filter(n => n).forEach(n => repos.add(n));
68
+ }
69
+ }
70
+
71
+ // Extract <files> tags
72
+ const filesMatches = content.match(/<files>([^<]*)<\/files>/g);
73
+ if (filesMatches) {
74
+ for (const match of filesMatches) {
75
+ const inner = match.replace(/<\/?files>/g, '');
76
+ inner.split(/[,\n]/).map(n => n.trim()).filter(n => n).forEach(n => filesList.add(n));
77
+ }
78
+ }
79
+
80
+ results.push({
81
+ plan: `${phaseDir}/${planFile}`,
82
+ repos: Array.from(repos).sort(),
83
+ files: Array.from(filesList).sort(),
84
+ });
85
+ }
86
+ }
87
+ } catch {
88
+ // phases directory doesn't exist or other error
89
+ }
90
+
91
+ return results;
92
+ }
93
+
94
+ // ─── Exclude Archived Phases ─────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Filter out phase directories that are in archived milestone paths.
98
+ *
99
+ * Archived paths match patterns like:
100
+ * .planning/milestones/v1.0-phases/01-setup
101
+ *
102
+ * @param {string[]} phaseDirs - Array of full phase directory paths
103
+ * @returns {string[]} Filtered array with archived paths removed
104
+ */
105
+ function excludeArchivedPhases(phaseDirs) {
106
+ return phaseDirs.filter(dir => {
107
+ const normalized = dir.replace(/\\/g, '/');
108
+ return !normalized.includes('/milestones/');
109
+ });
110
+ }
111
+
112
+ // ─── Classify Overlap Severity ───────────────────────────────────────────────
113
+
114
+ /**
115
+ * Classify the severity of an overlap between projects in a shared repo.
116
+ *
117
+ * - HIGH: Two or more projects modify the exact same file(s).
118
+ * - MEDIUM: No same-file conflicts, but projects share directories.
119
+ * - LOW: Projects touch different directories in the same repo.
120
+ *
121
+ * @param {{ file_conflicts: Array<{ file: string, projects: string[] }>, project_files: Object<string, string[]> }} overlapEntry
122
+ * - file_conflicts: files touched by 2+ projects
123
+ * - project_files: mapping of project slug -> array of file paths in this repo
124
+ * @returns {{ severity: string, reason: string }}
125
+ */
126
+ function classifyOverlapSeverity(overlapEntry) {
127
+ const { file_conflicts, project_files } = overlapEntry;
128
+
129
+ // HIGH: actual file-level conflicts
130
+ if (file_conflicts && file_conflicts.length > 0) {
131
+ const fileList = file_conflicts.map(f => f.file).join(', ');
132
+ return {
133
+ severity: 'HIGH',
134
+ reason: `${file_conflicts.length} file(s) modified by multiple projects: ${fileList}`,
135
+ };
136
+ }
137
+
138
+ // Compute per-project directory sets
139
+ if (project_files) {
140
+ const projectDirSets = {};
141
+ for (const [project, files] of Object.entries(project_files)) {
142
+ const dirs = new Set();
143
+ for (const file of files) {
144
+ dirs.add(path.dirname(file));
145
+ }
146
+ projectDirSets[project] = dirs;
147
+ }
148
+
149
+ // Check for directory intersection between any two projects
150
+ const projects = Object.keys(projectDirSets);
151
+ const sharedDirs = new Set();
152
+ for (let i = 0; i < projects.length; i++) {
153
+ for (let j = i + 1; j < projects.length; j++) {
154
+ for (const dir of projectDirSets[projects[i]]) {
155
+ if (projectDirSets[projects[j]].has(dir)) {
156
+ sharedDirs.add(dir);
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ if (sharedDirs.size > 0) {
163
+ const dirList = Array.from(sharedDirs).sort().join(', ');
164
+ return {
165
+ severity: 'MEDIUM',
166
+ reason: `Projects share ${sharedDirs.size} directory(ies): ${dirList}`,
167
+ };
168
+ }
169
+ }
170
+
171
+ // LOW: different directories in same repo
172
+ return {
173
+ severity: 'LOW',
174
+ reason: 'Projects touch different directories in same repo',
175
+ };
176
+ }
177
+
178
+ // ─── Get Active Project Slugs ────────────────────────────────────────────────
179
+
180
+ /**
181
+ * Get slugs of all active (non-completed) projects.
182
+ *
183
+ * Reads PROJECTS.md to get active project slugs. Falls back to scanning
184
+ * .planning/ subfolders if PROJECTS.md is missing. Applies ghost project
185
+ * guard (skips folders without STATE.md).
186
+ *
187
+ * @param {string} cwd - Working directory (product root)
188
+ * @returns {string[]} Array of active project slugs
189
+ */
190
+ function getActiveProjectSlugs(cwd) {
191
+ // Try PROJECTS.md first
192
+ const projectsMd = parseProjectsMd(cwd);
193
+ if (projectsMd && projectsMd.active) {
194
+ // Filter to only those with folders on disk and valid STATE.md
195
+ return projectsMd.active
196
+ .map(p => p.name)
197
+ .filter(slug => {
198
+ const state = readProjectState(cwd, slug);
199
+ return state !== null;
200
+ });
201
+ }
202
+
203
+ // Fallback: scan .planning/ subfolders
204
+ const folders = getProjectFolders(cwd);
205
+ return folders.filter(slug => {
206
+ const state = readProjectState(cwd, slug);
207
+ if (!state) return false;
208
+ // Exclude completed projects
209
+ return !state.status.toLowerCase().includes('completed');
210
+ });
211
+ }
212
+
213
+ // ─── Build Overlap Matrix ────────────────────────────────────────────────────
214
+
215
+ /**
216
+ * Scan all active projects and build the overlap matrix.
217
+ *
218
+ * For each active project, scans plan files and extracts repos/files tags.
219
+ * Builds a repo-to-projects mapping, then filters to repos touched by 2+ projects.
220
+ * For overlapping repos, identifies specific file-level conflicts.
221
+ *
222
+ * @param {string} cwd - Working directory (product root)
223
+ * @returns {{ overlapping_repos: Array<{ repo: string, projects: string[], file_conflicts: Array<{ file: string, projects: string[] }> }>, warnings: string[] }}
224
+ */
225
+ function buildOverlapMatrix(cwd) {
226
+ const warnings = [];
227
+ const activeSlugs = getActiveProjectSlugs(cwd);
228
+
229
+ // Load registered repos for centralized file-to-repo resolution
230
+ const reposMd = parseReposMd(cwd);
231
+ const registeredRepos = reposMd ? reposMd.repos : [];
232
+
233
+ // repo -> { projects: Set, files: Map<file, Set<project>>, project_files: Map<project, Set<file>> }
234
+ const repoMap = {};
235
+
236
+ for (const slug of activeSlugs) {
237
+ // Ghost project guard
238
+ const projectDir = getProjectDir(cwd, slug);
239
+ if (!fs.existsSync(projectDir)) {
240
+ warnings.push(`Ghost project: ${slug} (folder missing)`);
241
+ continue;
242
+ }
243
+
244
+ const planData = scanProjectPlanFiles(cwd, slug);
245
+ for (const plan of planData) {
246
+ for (const repo of plan.repos) {
247
+ if (!repoMap[repo]) {
248
+ repoMap[repo] = { projects: new Set(), files: {}, project_files: {} };
249
+ }
250
+ repoMap[repo].projects.add(slug);
251
+ }
252
+ // Map files to repos using plan's <repos> context (repo-relative paths
253
+ // can't be resolved via prefix-match alone)
254
+ for (const file of plan.files) {
255
+ // If plan has explicit repos, use them as context
256
+ const targetRepos = plan.repos.length > 0 ? plan.repos : [];
257
+
258
+ // Fallback: try resolveFileToRepo for legacy product-root-relative paths
259
+ if (targetRepos.length === 0) {
260
+ const resolved = resolveFileToRepo(file, registeredRepos);
261
+ const targetRepo = resolved ? resolved.name : '__unknown__';
262
+ if (!repoMap[targetRepo]) {
263
+ repoMap[targetRepo] = { projects: new Set(), files: {}, project_files: {} };
264
+ }
265
+ repoMap[targetRepo].projects.add(slug);
266
+ if (!repoMap[targetRepo].files[file]) {
267
+ repoMap[targetRepo].files[file] = new Set();
268
+ }
269
+ repoMap[targetRepo].files[file].add(slug);
270
+ if (!repoMap[targetRepo].project_files[slug]) {
271
+ repoMap[targetRepo].project_files[slug] = [];
272
+ }
273
+ if (!repoMap[targetRepo].project_files[slug].includes(file)) {
274
+ repoMap[targetRepo].project_files[slug].push(file);
275
+ }
276
+ continue;
277
+ }
278
+
279
+ for (const repoName of targetRepos) {
280
+ if (!repoMap[repoName]) {
281
+ repoMap[repoName] = { projects: new Set(), files: {}, project_files: {} };
282
+ }
283
+ repoMap[repoName].projects.add(slug);
284
+ if (!repoMap[repoName].files[file]) {
285
+ repoMap[repoName].files[file] = new Set();
286
+ }
287
+ repoMap[repoName].files[file].add(slug);
288
+ if (!repoMap[repoName].project_files[slug]) {
289
+ repoMap[repoName].project_files[slug] = [];
290
+ }
291
+ if (!repoMap[repoName].project_files[slug].includes(file)) {
292
+ repoMap[repoName].project_files[slug].push(file);
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ // Also check PROJECTS.md for ghost projects not caught above
300
+ const projectsMd = parseProjectsMd(cwd);
301
+ if (projectsMd && projectsMd.active) {
302
+ for (const p of projectsMd.active) {
303
+ const projectDir = getProjectDir(cwd, p.name);
304
+ if (!fs.existsSync(projectDir) && !warnings.some(w => w.includes(p.name))) {
305
+ warnings.push(`Ghost project: ${p.name} (folder missing)`);
306
+ }
307
+ }
308
+ }
309
+
310
+ // Filter to repos touched by 2+ projects
311
+ const overlapping_repos = [];
312
+ for (const [repo, data] of Object.entries(repoMap)) {
313
+ if (repo === '__unknown__') continue;
314
+ if (data.projects.size < 2) continue;
315
+
316
+ // Identify file-level conflicts (files touched by 2+ projects)
317
+ const file_conflicts = [];
318
+ for (const [file, projectSet] of Object.entries(data.files)) {
319
+ if (projectSet.size >= 2) {
320
+ file_conflicts.push({
321
+ file,
322
+ projects: Array.from(projectSet).sort(),
323
+ });
324
+ }
325
+ }
326
+
327
+ // Classify severity based on file conflicts and project file locations
328
+ const severityResult = classifyOverlapSeverity({
329
+ file_conflicts,
330
+ project_files: data.project_files,
331
+ });
332
+
333
+ overlapping_repos.push({
334
+ repo,
335
+ projects: Array.from(data.projects).sort(),
336
+ file_conflicts: file_conflicts.sort((a, b) => a.file.localeCompare(b.file)),
337
+ severity: severityResult.severity,
338
+ severity_reason: severityResult.reason,
339
+ });
340
+ }
341
+
342
+ // Sort by severity (HIGH first, then MEDIUM, then LOW), then by repo name within same severity
343
+ overlapping_repos.sort((a, b) => {
344
+ const sevDiff = SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity];
345
+ if (sevDiff !== 0) return sevDiff;
346
+ return a.repo.localeCompare(b.repo);
347
+ });
348
+
349
+ return { overlapping_repos, warnings };
350
+ }
351
+
352
+ // ─── Format Overlap Report ───────────────────────────────────────────────────
353
+
354
+ /**
355
+ * Format the overlap matrix for human-readable display.
356
+ *
357
+ * @param {{ overlapping_repos: Array, warnings: string[] }} matrix
358
+ * @returns {string} Formatted report text
359
+ */
360
+ function formatOverlapReport(matrix) {
361
+ if (!matrix.overlapping_repos || matrix.overlapping_repos.length === 0) {
362
+ return 'No cross-project overlaps detected.\n\nEach active project touches unique repos.';
363
+ }
364
+
365
+ const lines = [];
366
+ const count = matrix.overlapping_repos.length;
367
+ lines.push(`${count} repo(s) touched by multiple active projects\n`);
368
+
369
+ // Repo-level table with severity column
370
+ lines.push('| Repo | Severity | Projects |');
371
+ lines.push('|------|----------|----------|');
372
+ for (const overlap of matrix.overlapping_repos) {
373
+ const severity = overlap.severity || 'LOW';
374
+ lines.push(`| ${overlap.repo} | ${severity} | ${overlap.projects.join(', ')} |`);
375
+ }
376
+
377
+ // File-level drill-down only for HIGH severity repos
378
+ const highSeverityRepos = matrix.overlapping_repos.filter(
379
+ o => o.severity === 'HIGH' && o.file_conflicts && o.file_conflicts.length > 0
380
+ );
381
+ if (highSeverityRepos.length > 0) {
382
+ lines.push('');
383
+ lines.push('### File-Level Conflicts');
384
+ for (const overlap of highSeverityRepos) {
385
+ lines.push('');
386
+ lines.push(`**${overlap.repo}:**`);
387
+ for (const conflict of overlap.file_conflicts) {
388
+ lines.push(`- ${conflict.file} — ${conflict.projects.join(', ')}`);
389
+ }
390
+ }
391
+ }
392
+
393
+ lines.push('');
394
+ lines.push('This is informational — overlapping repos don\'t block work.');
395
+ lines.push('Coordinate merges manually if projects touch the same files.');
396
+
397
+ if (matrix.warnings.length > 0) {
398
+ lines.push('');
399
+ lines.push('### Warnings');
400
+ for (const w of matrix.warnings) {
401
+ lines.push(`- ${w}`);
402
+ }
403
+ }
404
+
405
+ return lines.join('\n');
406
+ }
407
+
408
+ // ─── CLI Command Function ────────────────────────────────────────────────────
409
+
410
+ /**
411
+ * CLI: Run overlap check and output results.
412
+ *
413
+ * @param {string} cwd - Working directory (product root)
414
+ * @param {boolean} raw - Raw output mode
415
+ */
416
+ function cmdOverlapCheck(cwd, raw) {
417
+ const matrix = buildOverlapMatrix(cwd);
418
+
419
+ if (raw) {
420
+ output(matrix, raw);
421
+ } else {
422
+ const report = formatOverlapReport(matrix);
423
+ output({ ...matrix, report }, false);
424
+ }
425
+ }
426
+
427
+ // ─── Exports ────────────────────────────────────────────────────────────────
428
+
429
+ module.exports = {
430
+ scanProjectPlanFiles,
431
+ buildOverlapMatrix,
432
+ excludeArchivedPhases,
433
+ formatOverlapReport,
434
+ getActiveProjectSlugs,
435
+ classifyOverlapSeverity,
436
+ cmdOverlapCheck,
437
+ };