code-as-plan 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja-JP.md +834 -0
  3. package/README.ko-KR.md +823 -0
  4. package/README.md +1006 -0
  5. package/README.pt-BR.md +452 -0
  6. package/README.zh-CN.md +800 -0
  7. package/agents/cap-brainstormer.md +154 -0
  8. package/agents/cap-debugger.md +221 -0
  9. package/agents/cap-prototyper.md +170 -0
  10. package/agents/cap-reviewer.md +230 -0
  11. package/agents/cap-tester.md +193 -0
  12. package/bin/install.js +5002 -0
  13. package/cap/bin/gsd-tools.cjs +1141 -0
  14. package/cap/bin/lib/arc-scanner.cjs +341 -0
  15. package/cap/bin/lib/cap-feature-map.cjs +506 -0
  16. package/cap/bin/lib/cap-session.cjs +191 -0
  17. package/cap/bin/lib/cap-stack-docs.cjs +598 -0
  18. package/cap/bin/lib/cap-tag-scanner.cjs +458 -0
  19. package/cap/bin/lib/commands.cjs +959 -0
  20. package/cap/bin/lib/config.cjs +466 -0
  21. package/cap/bin/lib/convention-reader.cjs +180 -0
  22. package/cap/bin/lib/core.cjs +1230 -0
  23. package/cap/bin/lib/feature-aggregator.cjs +422 -0
  24. package/cap/bin/lib/frontmatter.cjs +336 -0
  25. package/cap/bin/lib/init.cjs +1442 -0
  26. package/cap/bin/lib/manifest-generator.cjs +381 -0
  27. package/cap/bin/lib/milestone.cjs +252 -0
  28. package/cap/bin/lib/model-profiles.cjs +68 -0
  29. package/cap/bin/lib/monorepo-context.cjs +224 -0
  30. package/cap/bin/lib/monorepo-migrator.cjs +507 -0
  31. package/cap/bin/lib/phase.cjs +888 -0
  32. package/cap/bin/lib/profile-output.cjs +952 -0
  33. package/cap/bin/lib/profile-pipeline.cjs +539 -0
  34. package/cap/bin/lib/roadmap.cjs +329 -0
  35. package/cap/bin/lib/security.cjs +382 -0
  36. package/cap/bin/lib/session-manager.cjs +290 -0
  37. package/cap/bin/lib/skeleton-generator.cjs +177 -0
  38. package/cap/bin/lib/state.cjs +1031 -0
  39. package/cap/bin/lib/template.cjs +222 -0
  40. package/cap/bin/lib/test-detector.cjs +61 -0
  41. package/cap/bin/lib/uat.cjs +282 -0
  42. package/cap/bin/lib/verify.cjs +888 -0
  43. package/cap/bin/lib/workspace-detector.cjs +369 -0
  44. package/cap/bin/lib/workstream.cjs +491 -0
  45. package/cap/commands/gsd/workstreams.md +63 -0
  46. package/cap/references/arc-standard.md +315 -0
  47. package/cap/references/cap-agent-architecture.md +102 -0
  48. package/cap/references/cap-gitignore-template +9 -0
  49. package/cap/references/cap-zero-deps.md +158 -0
  50. package/cap/references/checkpoints.md +778 -0
  51. package/cap/references/continuation-format.md +249 -0
  52. package/cap/references/decimal-phase-calculation.md +64 -0
  53. package/cap/references/feature-map-template.md +25 -0
  54. package/cap/references/git-integration.md +295 -0
  55. package/cap/references/git-planning-commit.md +38 -0
  56. package/cap/references/model-profile-resolution.md +36 -0
  57. package/cap/references/model-profiles.md +139 -0
  58. package/cap/references/phase-argument-parsing.md +61 -0
  59. package/cap/references/planning-config.md +202 -0
  60. package/cap/references/questioning.md +162 -0
  61. package/cap/references/session-template.json +8 -0
  62. package/cap/references/tdd.md +263 -0
  63. package/cap/references/ui-brand.md +160 -0
  64. package/cap/references/user-profiling.md +681 -0
  65. package/cap/references/verification-patterns.md +612 -0
  66. package/cap/references/workstream-flag.md +58 -0
  67. package/cap/templates/DEBUG.md +164 -0
  68. package/cap/templates/UAT.md +265 -0
  69. package/cap/templates/UI-SPEC.md +100 -0
  70. package/cap/templates/VALIDATION.md +76 -0
  71. package/cap/templates/claude-md.md +122 -0
  72. package/cap/templates/codebase/architecture.md +255 -0
  73. package/cap/templates/codebase/concerns.md +310 -0
  74. package/cap/templates/codebase/conventions.md +307 -0
  75. package/cap/templates/codebase/integrations.md +280 -0
  76. package/cap/templates/codebase/stack.md +186 -0
  77. package/cap/templates/codebase/structure.md +285 -0
  78. package/cap/templates/codebase/testing.md +480 -0
  79. package/cap/templates/config.json +44 -0
  80. package/cap/templates/context.md +352 -0
  81. package/cap/templates/continue-here.md +78 -0
  82. package/cap/templates/copilot-instructions.md +7 -0
  83. package/cap/templates/debug-subagent-prompt.md +91 -0
  84. package/cap/templates/dev-preferences.md +21 -0
  85. package/cap/templates/discovery.md +146 -0
  86. package/cap/templates/discussion-log.md +63 -0
  87. package/cap/templates/milestone-archive.md +123 -0
  88. package/cap/templates/milestone.md +115 -0
  89. package/cap/templates/phase-prompt.md +610 -0
  90. package/cap/templates/planner-subagent-prompt.md +117 -0
  91. package/cap/templates/project.md +186 -0
  92. package/cap/templates/requirements.md +231 -0
  93. package/cap/templates/research-project/ARCHITECTURE.md +204 -0
  94. package/cap/templates/research-project/FEATURES.md +147 -0
  95. package/cap/templates/research-project/PITFALLS.md +200 -0
  96. package/cap/templates/research-project/STACK.md +120 -0
  97. package/cap/templates/research-project/SUMMARY.md +170 -0
  98. package/cap/templates/research.md +552 -0
  99. package/cap/templates/retrospective.md +54 -0
  100. package/cap/templates/roadmap.md +202 -0
  101. package/cap/templates/state.md +176 -0
  102. package/cap/templates/summary-complex.md +59 -0
  103. package/cap/templates/summary-minimal.md +41 -0
  104. package/cap/templates/summary-standard.md +48 -0
  105. package/cap/templates/summary.md +248 -0
  106. package/cap/templates/user-profile.md +146 -0
  107. package/cap/templates/user-setup.md +311 -0
  108. package/cap/templates/verification-report.md +322 -0
  109. package/cap/workflows/add-phase.md +112 -0
  110. package/cap/workflows/add-tests.md +351 -0
  111. package/cap/workflows/add-todo.md +158 -0
  112. package/cap/workflows/audit-milestone.md +340 -0
  113. package/cap/workflows/audit-uat.md +109 -0
  114. package/cap/workflows/autonomous.md +891 -0
  115. package/cap/workflows/check-todos.md +177 -0
  116. package/cap/workflows/cleanup.md +152 -0
  117. package/cap/workflows/complete-milestone.md +767 -0
  118. package/cap/workflows/diagnose-issues.md +231 -0
  119. package/cap/workflows/discovery-phase.md +289 -0
  120. package/cap/workflows/discuss-phase-assumptions.md +653 -0
  121. package/cap/workflows/discuss-phase.md +1049 -0
  122. package/cap/workflows/do.md +104 -0
  123. package/cap/workflows/execute-phase.md +846 -0
  124. package/cap/workflows/execute-plan.md +514 -0
  125. package/cap/workflows/fast.md +105 -0
  126. package/cap/workflows/forensics.md +265 -0
  127. package/cap/workflows/health.md +181 -0
  128. package/cap/workflows/help.md +660 -0
  129. package/cap/workflows/insert-phase.md +130 -0
  130. package/cap/workflows/list-phase-assumptions.md +178 -0
  131. package/cap/workflows/list-workspaces.md +56 -0
  132. package/cap/workflows/manager.md +362 -0
  133. package/cap/workflows/map-codebase.md +377 -0
  134. package/cap/workflows/milestone-summary.md +223 -0
  135. package/cap/workflows/new-milestone.md +486 -0
  136. package/cap/workflows/new-project.md +1250 -0
  137. package/cap/workflows/new-workspace.md +237 -0
  138. package/cap/workflows/next.md +97 -0
  139. package/cap/workflows/node-repair.md +92 -0
  140. package/cap/workflows/note.md +156 -0
  141. package/cap/workflows/pause-work.md +176 -0
  142. package/cap/workflows/plan-milestone-gaps.md +273 -0
  143. package/cap/workflows/plan-phase.md +859 -0
  144. package/cap/workflows/plant-seed.md +169 -0
  145. package/cap/workflows/pr-branch.md +129 -0
  146. package/cap/workflows/profile-user.md +450 -0
  147. package/cap/workflows/progress.md +507 -0
  148. package/cap/workflows/quick.md +757 -0
  149. package/cap/workflows/remove-phase.md +155 -0
  150. package/cap/workflows/remove-workspace.md +90 -0
  151. package/cap/workflows/research-phase.md +82 -0
  152. package/cap/workflows/resume-project.md +326 -0
  153. package/cap/workflows/review.md +228 -0
  154. package/cap/workflows/session-report.md +146 -0
  155. package/cap/workflows/settings.md +283 -0
  156. package/cap/workflows/ship.md +228 -0
  157. package/cap/workflows/stats.md +60 -0
  158. package/cap/workflows/transition.md +671 -0
  159. package/cap/workflows/ui-phase.md +302 -0
  160. package/cap/workflows/ui-review.md +165 -0
  161. package/cap/workflows/update.md +323 -0
  162. package/cap/workflows/validate-phase.md +174 -0
  163. package/cap/workflows/verify-phase.md +254 -0
  164. package/cap/workflows/verify-work.md +637 -0
  165. package/commands/cap/annotate.md +165 -0
  166. package/commands/cap/brainstorm.md +238 -0
  167. package/commands/cap/debug.md +297 -0
  168. package/commands/cap/init.md +262 -0
  169. package/commands/cap/iterate.md +234 -0
  170. package/commands/cap/prototype.md +281 -0
  171. package/commands/cap/refresh-docs.md +37 -0
  172. package/commands/cap/review.md +272 -0
  173. package/commands/cap/scan.md +249 -0
  174. package/commands/cap/start.md +234 -0
  175. package/commands/cap/status.md +189 -0
  176. package/commands/cap/test.md +250 -0
  177. package/hooks/dist/gsd-check-update.js +114 -0
  178. package/hooks/dist/gsd-context-monitor.js +156 -0
  179. package/hooks/dist/gsd-prompt-guard.js +96 -0
  180. package/hooks/dist/gsd-statusline.js +119 -0
  181. package/hooks/dist/gsd-workflow-guard.js +94 -0
  182. package/package.json +51 -0
  183. package/scripts/base64-scan.sh +262 -0
  184. package/scripts/build-hooks.js +82 -0
  185. package/scripts/cap-removal-checklist.md +202 -0
  186. package/scripts/prompt-injection-scan.sh +198 -0
  187. package/scripts/run-tests.cjs +29 -0
  188. package/scripts/secret-scan.sh +227 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * feature-aggregator — Auto-generates FEATURES.md from PRD acceptance criteria
3
+ * and @gsd-tag status in CODE-INVENTORY.md.
4
+ *
5
+ * Requirements: FMAP-01, FMAP-02, FMAP-03, FMAP-04, FMAP-05
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ // @gsd-context(phase:12) Feature aggregator — reads PRDs and CODE-INVENTORY.md to produce FEATURES.md.
11
+ // Dual-input design: PRD ACs are authoritative, code tags refine completion status.
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+
17
+ // ── PRD Parsing ─────────────────────────────────────────────────────────────
18
+
19
+ // @gsd-pattern AC lines in PRDs follow the format: AC-N: description text
20
+ // This regex must match the exact format emitted by /gsd:brainstorm and /gsd:prototype PRD templates.
21
+ const AC_LINE_RE = /^(?:[-*]\s*)?(?:\*\*)?AC-(\d+)(?:\*\*)?:\s*(.+)$/gm;
22
+
23
+ // @gsd-pattern Feature group headings in PRDs use ## or ### markdown headers
24
+ const FEATURE_GROUP_RE = /^#{2,3}\s+(.+)$/gm;
25
+
26
+ // @gsd-pattern Dependency sections in PRDs use "## Dependencies" or "### Dependencies"
27
+ const DEPENDENCY_SECTION_RE = /^#{2,3}\s+Dependencies\s*$/im;
28
+
29
+ /**
30
+ * Parse a PRD file and extract acceptance criteria, feature groups, and dependencies.
31
+ *
32
+ * @gsd-api Parameters: prdContent (string) — raw PRD markdown.
33
+ * Returns: { acs: Array<{id, description, group}>, dependencies: Array<{from, to}>, groups: string[] }
34
+ *
35
+ * @param {string} prdContent - Raw PRD Markdown content
36
+ * @param {string} [prdName] - Name of the PRD file (for provenance tracking)
37
+ * @returns {{ acs: Array<{id: string, description: string, group: string, prdSource: string}>, dependencies: Array<{from: string, to: string}>, groups: string[] }}
38
+ */
39
+ function parsePrd(prdContent, prdName) {
40
+ // @gsd-todo(ref:AC-1) Implement full PRD parsing — extract ACs, feature groups, and dependency sections from PRD markdown
41
+ const acs = [];
42
+ const groups = [];
43
+ const dependencies = [];
44
+
45
+ let currentGroup = '(ungrouped)';
46
+
47
+ const lines = prdContent.split('\n');
48
+ let inDependencySection = false;
49
+
50
+ for (const line of lines) {
51
+ // Detect feature group headings
52
+ const groupMatch = line.match(/^#{2,3}\s+(.+)$/);
53
+ if (groupMatch) {
54
+ const heading = groupMatch[1].trim();
55
+ if (/^dependencies$/i.test(heading)) {
56
+ inDependencySection = true;
57
+ continue;
58
+ }
59
+ inDependencySection = false;
60
+ currentGroup = heading;
61
+ if (!groups.includes(heading)) {
62
+ groups.push(heading);
63
+ }
64
+ continue;
65
+ }
66
+
67
+ // Parse dependency lines (format: "- FeatureA -> FeatureB" or "- FeatureA depends on FeatureB")
68
+ if (inDependencySection) {
69
+ const depArrowMatch = line.match(/^[-*]\s+(.+?)\s*(?:->|→)\s*(.+)$/);
70
+ if (depArrowMatch) {
71
+ dependencies.push({
72
+ from: depArrowMatch[1].trim(),
73
+ to: depArrowMatch[2].trim(),
74
+ });
75
+ continue;
76
+ }
77
+ const depTextMatch = line.match(/^[-*]\s+(.+?)\s+depends on\s+(.+)$/i);
78
+ if (depTextMatch) {
79
+ dependencies.push({
80
+ from: depTextMatch[1].trim(),
81
+ to: depTextMatch[2].trim(),
82
+ });
83
+ continue;
84
+ }
85
+ }
86
+
87
+ // Parse AC lines
88
+ const acMatch = line.match(/^(?:[-*]\s*)?(?:\*\*)?AC-(\d+)(?:\*\*)?:\s*(.+)$/);
89
+ if (acMatch) {
90
+ inDependencySection = false;
91
+ acs.push({
92
+ id: `AC-${acMatch[1]}`,
93
+ description: acMatch[2].trim(),
94
+ group: currentGroup,
95
+ prdSource: prdName || 'unknown',
96
+ });
97
+ }
98
+ }
99
+
100
+ return { acs, dependencies, groups };
101
+ }
102
+
103
+ // ── CODE-INVENTORY Parsing ──────────────────────────────────────────────────
104
+
105
+ // @gsd-pattern Open @gsd-todo tags with ref:AC-N metadata indicate incomplete ACs.
106
+ // Absence of such a tag means the AC is considered done.
107
+ const TODO_REF_RE = /ref:(AC-\d+)/;
108
+
109
+ /**
110
+ * Parse CODE-INVENTORY.md to find open @gsd-todo tags with AC references.
111
+ *
112
+ * @gsd-api Parameters: inventoryContent (string) — raw CODE-INVENTORY.md.
113
+ * Returns: Set<string> of open AC IDs (e.g., Set(['AC-1', 'AC-3']))
114
+ *
115
+ * @param {string} inventoryContent - Raw CODE-INVENTORY.md content
116
+ * @returns {Set<string>} Set of AC IDs that still have open @gsd-todo tags
117
+ */
118
+ function parseOpenTodos(inventoryContent) {
119
+ // @gsd-todo(ref:AC-2) Implement CODE-INVENTORY.md parsing to extract open @gsd-todo(ref:AC-N) tags and determine per-AC completion status
120
+ const openAcIds = new Set();
121
+ const lines = inventoryContent.split('\n');
122
+
123
+ // We look for lines in the @gsd-todo section that contain ref:AC-N metadata
124
+ let inTodoSection = false;
125
+
126
+ for (const line of lines) {
127
+ // Detect the @gsd-todo section heading
128
+ if (/^###\s+@gsd-todo/.test(line)) {
129
+ inTodoSection = true;
130
+ continue;
131
+ }
132
+ // Exit todo section on next ### heading
133
+ if (inTodoSection && /^###\s+@gsd-/.test(line)) {
134
+ inTodoSection = false;
135
+ continue;
136
+ }
137
+
138
+ if (inTodoSection) {
139
+ const refMatch = line.match(TODO_REF_RE);
140
+ if (refMatch) {
141
+ openAcIds.add(refMatch[1]);
142
+ }
143
+ }
144
+ }
145
+
146
+ return openAcIds;
147
+ }
148
+
149
+ // ── Cross-Reference Engine ──────────────────────────────────────────────────
150
+
151
+ // @gsd-decision AC completion is derived from tag presence: if a @gsd-todo with ref:AC-N
152
+ // exists in CODE-INVENTORY.md, that AC is "open". If absent, it is "done".
153
+ // This avoids needing explicit "done" markers — the absence of work IS the signal.
154
+
155
+ /**
156
+ * Cross-reference PRD ACs with open @gsd-todo tags to determine completion status.
157
+ *
158
+ * @gsd-api Parameters: acs (Array), openTodoAcIds (Set<string>).
159
+ * Returns: Array of AC objects enriched with `status` field ('done' | 'open').
160
+ *
161
+ * @param {Array<{id: string, description: string, group: string, prdSource: string}>} acs
162
+ * @param {Set<string>} openTodoAcIds - AC IDs that still have open @gsd-todo tags
163
+ * @returns {Array<{id: string, description: string, group: string, prdSource: string, status: string}>}
164
+ */
165
+ function crossReference(acs, openTodoAcIds) {
166
+ return acs.map(ac => ({
167
+ ...ac,
168
+ status: openTodoAcIds.has(ac.id) ? 'open' : 'done',
169
+ }));
170
+ }
171
+
172
+ // ── Dependency Extraction ───────────────────────────────────────────────────
173
+
174
+ /**
175
+ * Extract and format dependency relationships from parsed PRD data.
176
+ *
177
+ * @gsd-api Parameters: dependencies (Array<{from, to}>).
178
+ * Returns: string — Markdown-formatted dependency visualization.
179
+ *
180
+ * @param {Array<{from: string, to: string}>} dependencies
181
+ * @returns {string} Markdown dependency section content
182
+ */
183
+ function formatDependencies(dependencies) {
184
+ // @gsd-todo(ref:AC-3) Implement dependency visualization in FEATURES.md from PRD dependency sections
185
+ if (!dependencies || dependencies.length === 0) {
186
+ return 'No cross-feature dependencies documented.';
187
+ }
188
+
189
+ const lines = [];
190
+ lines.push('```');
191
+ for (const dep of dependencies) {
192
+ lines.push(` ${dep.from} --> ${dep.to}`);
193
+ }
194
+ lines.push('```');
195
+ lines.push('');
196
+
197
+ // Also produce a readable list
198
+ for (const dep of dependencies) {
199
+ lines.push(`- **${dep.from}** depends on **${dep.to}**`);
200
+ }
201
+
202
+ return lines.join('\n');
203
+ }
204
+
205
+ // ── FEATURES.md Generation ──────────────────────────────────────────────────
206
+
207
+ // @gsd-constraint FEATURES.md is a derived read-only artifact. It must never be manually edited.
208
+ // The header includes last-updated and source-hash to signal this.
209
+
210
+ /**
211
+ * Generate the complete FEATURES.md content string.
212
+ *
213
+ * @gsd-api Parameters: enrichedAcs (Array), dependencies (Array), groups (string[]).
214
+ * Returns: string — complete FEATURES.md Markdown content with header, status table, and dependencies.
215
+ *
216
+ * @param {Array<{id: string, description: string, group: string, prdSource: string, status: string}>} enrichedAcs
217
+ * @param {Array<{from: string, to: string}>} dependencies
218
+ * @param {string[]} groups
219
+ * @param {string[]} prdSources - List of PRD file paths used as input
220
+ * @returns {string}
221
+ */
222
+ function generateFeaturesMarkdown(enrichedAcs, dependencies, groups, prdSources) {
223
+ // @gsd-todo(ref:AC-5) Generate FEATURES.md as a derived read-only artifact with last-updated timestamp and source-hash header
224
+ const now = new Date().toISOString();
225
+
226
+ // Compute source hash from input data for staleness detection
227
+ const hashInput = JSON.stringify({ enrichedAcs, dependencies, prdSources });
228
+ const sourceHash = crypto.createHash('sha256').update(hashInput).digest('hex').slice(0, 12);
229
+
230
+ const totalAcs = enrichedAcs.length;
231
+ const doneAcs = enrichedAcs.filter(ac => ac.status === 'done').length;
232
+ const openAcs = totalAcs - doneAcs;
233
+ const completionPct = totalAcs > 0 ? Math.round((doneAcs / totalAcs) * 100) : 0;
234
+
235
+ const lines = [];
236
+
237
+ // ── Header ──
238
+ lines.push('# FEATURES.md');
239
+ lines.push('');
240
+ lines.push('> **This file is auto-generated. Do not edit manually.**');
241
+ lines.push(`> **Last updated:** ${now}`);
242
+ lines.push(`> **Source hash:** ${sourceHash}`);
243
+ lines.push(`> **Sources:** ${prdSources.join(', ')}`);
244
+ lines.push('');
245
+
246
+ // ── Overall Progress ──
247
+ lines.push('## Overall Progress');
248
+ lines.push('');
249
+ lines.push(`| Metric | Value |`);
250
+ lines.push(`|--------|-------|`);
251
+ lines.push(`| Total ACs | ${totalAcs} |`);
252
+ lines.push(`| Done | ${doneAcs} |`);
253
+ lines.push(`| Open | ${openAcs} |`);
254
+ lines.push(`| Completion | ${completionPct}% |`);
255
+ lines.push('');
256
+
257
+ // ── Features by Group ──
258
+ lines.push('## Features by Group');
259
+ lines.push('');
260
+
261
+ // Determine groups to render (use parsed groups, plus catch ungrouped)
262
+ const groupOrder = groups.length > 0 ? [...groups] : ['(ungrouped)'];
263
+ if (!groupOrder.includes('(ungrouped)')) {
264
+ const ungrouped = enrichedAcs.filter(ac => ac.group === '(ungrouped)');
265
+ if (ungrouped.length > 0) {
266
+ groupOrder.push('(ungrouped)');
267
+ }
268
+ }
269
+
270
+ for (const group of groupOrder) {
271
+ const groupAcs = enrichedAcs.filter(ac => ac.group === group);
272
+ if (groupAcs.length === 0) continue;
273
+
274
+ const groupDone = groupAcs.filter(ac => ac.status === 'done').length;
275
+ const groupTotal = groupAcs.length;
276
+ const groupPct = Math.round((groupDone / groupTotal) * 100);
277
+
278
+ lines.push(`### ${group}`);
279
+ lines.push('');
280
+ lines.push(`**Progress:** ${groupDone}/${groupTotal} (${groupPct}%)`);
281
+ lines.push('');
282
+ lines.push('| AC | Status | Description | Source |');
283
+ lines.push('|----|--------|-------------|--------|');
284
+
285
+ for (const ac of groupAcs) {
286
+ const statusIcon = ac.status === 'done' ? 'DONE' : 'OPEN';
287
+ lines.push(`| ${ac.id} | ${statusIcon} | ${ac.description} | ${ac.prdSource} |`);
288
+ }
289
+
290
+ lines.push('');
291
+ }
292
+
293
+ // ── Dependencies ──
294
+ lines.push('## Dependencies');
295
+ lines.push('');
296
+ lines.push(formatDependencies(dependencies));
297
+ lines.push('');
298
+
299
+ // ── Footer ──
300
+ lines.push('---');
301
+ lines.push('*Generated by feature-aggregator.cjs via `aggregate-features` subcommand.*');
302
+ lines.push('*Regenerated automatically on every `extract-tags` run.*');
303
+ lines.push('');
304
+
305
+ return lines.join('\n');
306
+ }
307
+
308
+ // ── File Discovery ──────────────────────────────────────────────────────────
309
+
310
+ // @gsd-decision PRD discovery uses a simple glob: .planning/PRD.md and .planning/PRD-*.md
311
+ // No recursive search needed — PRDs live at the .planning/ root by convention.
312
+
313
+ /**
314
+ * Discover all PRD files in the .planning/ directory.
315
+ *
316
+ * @param {string} planningDir - Path to .planning/ directory
317
+ * @returns {string[]} Array of absolute PRD file paths
318
+ */
319
+ function discoverPrdFiles(planningDir) {
320
+ const prdFiles = [];
321
+ try {
322
+ const entries = fs.readdirSync(planningDir);
323
+ for (const entry of entries) {
324
+ if (entry === 'PRD.md' || (entry.startsWith('PRD-') && entry.endsWith('.md'))) {
325
+ prdFiles.push(path.join(planningDir, entry));
326
+ }
327
+ }
328
+ } catch {
329
+ // .planning/ may not exist yet
330
+ }
331
+ return prdFiles;
332
+ }
333
+
334
+ // ── CLI Entry Point ─────────────────────────────────────────────────────────
335
+
336
+ // @gsd-pattern CLI entry follows arc-scanner.cjs cmdExtractTags pattern:
337
+ // accept cwd + opts, resolve paths, call pure functions, write output.
338
+
339
+ /**
340
+ * CLI entry point: aggregate features from PRDs and CODE-INVENTORY.md,
341
+ * write .planning/FEATURES.md.
342
+ *
343
+ * Called by gsd-tools.cjs case 'aggregate-features'.
344
+ *
345
+ * @gsd-api CLI entry: cmdAggregateFeatures(cwd, opts).
346
+ * opts.outputFile defaults to .planning/FEATURES.md.
347
+ * opts.inventoryFile defaults to .planning/prototype/CODE-INVENTORY.md.
348
+ *
349
+ * @param {string} cwd - Current working directory
350
+ * @param {Object} [opts] - Options
351
+ * @param {string} [opts.outputFile] - Output path (default: .planning/FEATURES.md)
352
+ * @param {string} [opts.inventoryFile] - CODE-INVENTORY.md path
353
+ */
354
+ function cmdAggregateFeatures(cwd, opts) {
355
+ // @gsd-todo(ref:AC-4) Wire aggregate-features into extract-tags auto-chain so FEATURES.md regenerates on every extract-tags run
356
+ opts = opts || {};
357
+
358
+ const planningDir = path.join(cwd, '.planning');
359
+ const outputFile = opts.outputFile || path.join(planningDir, 'FEATURES.md');
360
+ const inventoryFile = opts.inventoryFile || path.join(planningDir, 'prototype', 'CODE-INVENTORY.md');
361
+
362
+ // Step 1: Discover and parse PRD files
363
+ const prdFiles = discoverPrdFiles(planningDir);
364
+ if (prdFiles.length === 0) {
365
+ // @gsd-risk No PRDs found — FEATURES.md cannot be generated without at least one PRD.
366
+ // This is expected for projects that have not yet run /gsd:brainstorm or created a PRD manually.
367
+ process.stderr.write('feature-aggregator: No PRD files found in .planning/ — skipping FEATURES.md generation.\n');
368
+ return;
369
+ }
370
+
371
+ let allAcs = [];
372
+ let allDependencies = [];
373
+ let allGroups = [];
374
+ const prdSources = [];
375
+
376
+ for (const prdFile of prdFiles) {
377
+ const content = fs.readFileSync(prdFile, 'utf-8');
378
+ const prdName = path.basename(prdFile);
379
+ prdSources.push(prdName);
380
+
381
+ const parsed = parsePrd(content, prdName);
382
+ allAcs = allAcs.concat(parsed.acs);
383
+ allDependencies = allDependencies.concat(parsed.dependencies);
384
+ for (const g of parsed.groups) {
385
+ if (!allGroups.includes(g)) allGroups.push(g);
386
+ }
387
+ }
388
+
389
+ // Step 2: Parse CODE-INVENTORY.md for open @gsd-todo tags
390
+ let openTodoAcIds = new Set();
391
+ try {
392
+ const inventoryContent = fs.readFileSync(inventoryFile, 'utf-8');
393
+ openTodoAcIds = parseOpenTodos(inventoryContent);
394
+ } catch {
395
+ // CODE-INVENTORY.md may not exist yet — treat all ACs as done (no open todos)
396
+ // @gsd-risk If CODE-INVENTORY.md is missing, all ACs appear "done" by default.
397
+ // This is intentional: no code = no open todos. But it may confuse users on first run.
398
+ }
399
+
400
+ // Step 3: Cross-reference to determine AC completion status
401
+ const enrichedAcs = crossReference(allAcs, openTodoAcIds);
402
+
403
+ // Step 4: Generate FEATURES.md content
404
+ const markdown = generateFeaturesMarkdown(enrichedAcs, allDependencies, allGroups, prdSources);
405
+
406
+ // Step 5: Write output
407
+ const outDir = path.dirname(outputFile);
408
+ fs.mkdirSync(outDir, { recursive: true });
409
+ fs.writeFileSync(outputFile, markdown, 'utf-8');
410
+
411
+ process.stderr.write(`feature-aggregator: Wrote ${outputFile} (${allAcs.length} ACs, ${prdSources.length} PRD(s))\n`);
412
+ }
413
+
414
+ module.exports = {
415
+ parsePrd,
416
+ parseOpenTodos,
417
+ crossReference,
418
+ formatDependencies,
419
+ generateFeaturesMarkdown,
420
+ discoverPrdFiles,
421
+ cmdAggregateFeatures,
422
+ };