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