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,652 @@
1
+ // @cap-feature(feature:F-033) Implement Feature Impact Analysis — detect overlap between proposed features and existing Feature Map entries, trace dependency chains, detect circular deps, propose resolutions
2
+ // @cap-decision AC-2 (auto-run during brainstorm) is enforced at the COMMAND layer (brainstorm.md), not in this module. This module exposes analyzeImpact() which the command calls before proposing new Feature Map entries.
3
+ // @cap-decision AC-6 (advisory only) — this module NEVER calls writeFeatureMap(). All proposals are returned as structured data for the caller to present. No Feature Map modifications, dependency reordering, or AC adjustments happen without explicit user approval.
4
+ // @cap-decision Pure logic module with explicit I/O for persistence — analysis functions are side-effect-free; only persistReport() writes to disk.
5
+ // @cap-constraint Zero external dependencies — uses only Node.js built-ins (fs, path).
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
11
+
12
+ const {
13
+ readFeatureMap,
14
+ } = require('./cap-feature-map.cjs');
15
+
16
+ const {
17
+ extractKeywords,
18
+ computeKeywordOverlap,
19
+ } = require('./cap-thread-tracker.cjs');
20
+
21
+ // --- Constants ---
22
+
23
+ /** Directory for impact analysis reports relative to project root. */
24
+ const IMPACT_DIR = path.join('.cap', 'memory', 'impact');
25
+
26
+ /** Minimum Jaccard similarity (0-1) for two ACs to be considered overlapping. */
27
+ const AC_SIMILARITY_THRESHOLD = 0.25;
28
+
29
+ /** Minimum number of shared keywords for AC overlap to count. */
30
+ const AC_MIN_SHARED_KEYWORDS = 2;
31
+
32
+ /** Minimum file path overlap ratio (0-1) for a file conflict to be reported. */
33
+ const FILE_OVERLAP_THRESHOLD = 0.1;
34
+
35
+ // --- Types ---
36
+
37
+ /**
38
+ * @typedef {Object} ACOverlap
39
+ * @property {string} existingFeatureId - ID of the existing feature
40
+ * @property {string} existingFeatureTitle - Title of the existing feature
41
+ * @property {string} existingACId - ID of the existing AC
42
+ * @property {string} existingACDescription - Description of the existing AC
43
+ * @property {string} proposedACId - ID of the proposed AC
44
+ * @property {string} proposedACDescription - Description of the proposed AC
45
+ * @property {number} similarity - Jaccard similarity score (0-1)
46
+ * @property {string[]} sharedKeywords - Keywords shared between the two ACs
47
+ * @property {string} reason - Human-readable explanation of the overlap
48
+ */
49
+
50
+ /**
51
+ * @typedef {Object} DependencyChain
52
+ * @property {string[]} upstream - Feature IDs that the target depends on (transitively)
53
+ * @property {string[]} downstream - Feature IDs that depend on the target (transitively)
54
+ * @property {number} depth - Maximum traversal depth reached
55
+ */
56
+
57
+ /**
58
+ * @typedef {Object} CircularDepResult
59
+ * @property {boolean} hasCycle - Whether a cycle was detected
60
+ * @property {string[]} cycle - The cycle path as feature IDs (e.g., ["F-001", "F-003", "F-001"])
61
+ */
62
+
63
+ /**
64
+ * @typedef {Object} FileConflict
65
+ * @property {string} filePath - The conflicting file path
66
+ * @property {string[]} existingFeatureIds - Features already referencing this file
67
+ */
68
+
69
+ /**
70
+ * @typedef {Object} Resolution
71
+ * @property {'merge'|'split'|'adjust'|'flag'} type - Resolution type
72
+ * @property {string} description - Human-readable description of the proposed resolution
73
+ * @property {string[]} affectedFeatures - Feature IDs involved in this resolution
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} ImpactReport
78
+ * @property {string} proposedFeatureTitle - Title of the proposed feature
79
+ * @property {string} timestamp - ISO timestamp when the report was generated
80
+ * @property {ACOverlap[]} overlappingACs - AC-level overlaps found
81
+ * @property {DependencyChain} affectedChains - Dependency chain analysis
82
+ * @property {FileConflict[]} fileConflicts - File path conflicts
83
+ * @property {CircularDepResult} circularRisks - Circular dependency analysis
84
+ * @property {Resolution[]} resolutions - Proposed resolutions
85
+ */
86
+
87
+ // --- AC Overlap Detection ---
88
+
89
+ // @cap-todo(ac:F-033/AC-1) Detect overlap between proposed feature and existing Feature Map entries by comparing AC descriptions, dependency chains, and referenced file paths
90
+
91
+ /**
92
+ * Detect overlapping ACs between a proposed feature and existing features.
93
+ * Uses keyword extraction + Jaccard similarity for AC description matching
94
+ * and file path overlap detection.
95
+ *
96
+ * @param {import('./cap-feature-map.cjs').Feature} proposedFeature - The proposed feature
97
+ * @param {import('./cap-feature-map.cjs').Feature[]} existingFeatures - All existing features
98
+ * @param {Object} [options]
99
+ * @param {number} [options.similarityThreshold] - Override AC similarity threshold
100
+ * @param {number} [options.minSharedKeywords] - Override minimum shared keywords
101
+ * @returns {{ overlaps: ACOverlap[], fileConflicts: FileConflict[] }}
102
+ */
103
+ function detectOverlap(proposedFeature, existingFeatures, options = {}) {
104
+ const {
105
+ similarityThreshold = AC_SIMILARITY_THRESHOLD,
106
+ minSharedKeywords = AC_MIN_SHARED_KEYWORDS,
107
+ } = options;
108
+
109
+ const overlaps = [];
110
+ const fileConflicts = [];
111
+
112
+ // --- AC description overlap ---
113
+ const proposedACs = proposedFeature.acs || [];
114
+
115
+ for (const proposedAC of proposedACs) {
116
+ const proposedKeywords = extractKeywords(proposedAC.description);
117
+ if (proposedKeywords.length === 0) continue;
118
+
119
+ for (const existing of existingFeatures) {
120
+ // Skip self-comparison
121
+ if (existing.id === proposedFeature.id) continue;
122
+
123
+ for (const existingAC of existing.acs || []) {
124
+ const existingKeywords = extractKeywords(existingAC.description);
125
+ if (existingKeywords.length === 0) continue;
126
+
127
+ const { shared, overlapRatio } = computeKeywordOverlap(proposedKeywords, existingKeywords);
128
+
129
+ if (overlapRatio >= similarityThreshold && shared.length >= minSharedKeywords) {
130
+ overlaps.push({
131
+ existingFeatureId: existing.id,
132
+ existingFeatureTitle: existing.title,
133
+ existingACId: existingAC.id,
134
+ existingACDescription: existingAC.description,
135
+ proposedACId: proposedAC.id,
136
+ proposedACDescription: proposedAC.description,
137
+ similarity: Math.round(overlapRatio * 1000) / 1000,
138
+ sharedKeywords: shared,
139
+ reason: `Shared keywords: ${shared.join(', ')} (Jaccard: ${(overlapRatio * 100).toFixed(1)}%)`,
140
+ });
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ // --- File path overlap ---
147
+ const proposedFiles = new Set(proposedFeature.files || []);
148
+ if (proposedFiles.size > 0) {
149
+ /** @type {Map<string, string[]>} filePath -> featureIds */
150
+ const fileOwners = new Map();
151
+ for (const existing of existingFeatures) {
152
+ if (existing.id === proposedFeature.id) continue;
153
+ for (const file of existing.files || []) {
154
+ if (!fileOwners.has(file)) fileOwners.set(file, []);
155
+ fileOwners.get(file).push(existing.id);
156
+ }
157
+ }
158
+
159
+ for (const file of proposedFiles) {
160
+ const owners = fileOwners.get(file);
161
+ if (owners && owners.length > 0) {
162
+ fileConflicts.push({
163
+ filePath: file,
164
+ existingFeatureIds: [...owners],
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ return { overlaps, fileConflicts };
171
+ }
172
+
173
+ // --- Dependency Chain Tracing ---
174
+
175
+ // @cap-todo(ac:F-033/AC-4) Trace full dependency chains — if A depends on B depends on C, changing B ACs shall surface impact on both A and C
176
+
177
+ /**
178
+ * Trace full dependency chain for a feature in both directions.
179
+ * Given a feature, finds all upstream dependencies (things it depends on)
180
+ * and downstream dependents (things that depend on it), with cycle detection.
181
+ *
182
+ * @param {string} featureId - The feature to trace from
183
+ * @param {import('./cap-feature-map.cjs').Feature[]} features - All features
184
+ * @param {'both'|'upstream'|'downstream'} [direction='both'] - Which direction to trace
185
+ * @returns {DependencyChain}
186
+ */
187
+ function traceDependencyChain(featureId, features, direction = 'both') {
188
+ const upstream = [];
189
+ const downstream = [];
190
+
191
+ // Build lookup maps
192
+ /** @type {Map<string, string[]>} featureId -> dependencies (what it depends on) */
193
+ const depsMap = new Map();
194
+ /** @type {Map<string, string[]>} featureId -> dependents (what depends on it) */
195
+ const dependentsMap = new Map();
196
+
197
+ for (const f of features) {
198
+ depsMap.set(f.id, f.dependencies || []);
199
+ for (const dep of f.dependencies || []) {
200
+ if (!dependentsMap.has(dep)) dependentsMap.set(dep, []);
201
+ dependentsMap.get(dep).push(f.id);
202
+ }
203
+ }
204
+
205
+ let maxDepth = 0;
206
+
207
+ // Trace upstream (things this feature depends on, recursively)
208
+ if (direction === 'both' || direction === 'upstream') {
209
+ const visited = new Set();
210
+ const queue = [{ id: featureId, depth: 0 }];
211
+ while (queue.length > 0) {
212
+ const { id, depth } = queue.shift();
213
+ const deps = depsMap.get(id) || [];
214
+ for (const dep of deps) {
215
+ if (visited.has(dep)) continue;
216
+ visited.add(dep);
217
+ upstream.push(dep);
218
+ if (depth + 1 > maxDepth) maxDepth = depth + 1;
219
+ queue.push({ id: dep, depth: depth + 1 });
220
+ }
221
+ }
222
+ }
223
+
224
+ // Trace downstream (things that depend on this feature, recursively)
225
+ if (direction === 'both' || direction === 'downstream') {
226
+ const visited = new Set();
227
+ const queue = [{ id: featureId, depth: 0 }];
228
+ while (queue.length > 0) {
229
+ const { id, depth } = queue.shift();
230
+ const deps = dependentsMap.get(id) || [];
231
+ for (const dep of deps) {
232
+ if (visited.has(dep)) continue;
233
+ visited.add(dep);
234
+ downstream.push(dep);
235
+ if (depth + 1 > maxDepth) maxDepth = depth + 1;
236
+ queue.push({ id: dep, depth: depth + 1 });
237
+ }
238
+ }
239
+ }
240
+
241
+ return { upstream, downstream, depth: maxDepth };
242
+ }
243
+
244
+ // --- Circular Dependency Detection ---
245
+
246
+ // @cap-todo(ac:F-033/AC-8) Detect circular dependency risks when new features are proposed and warn before Feature Map entries are written
247
+
248
+ /**
249
+ * Detect if adding proposed dependencies would create a circular dependency.
250
+ * Uses DFS cycle detection on the dependency graph augmented with proposed edges.
251
+ *
252
+ * @param {string} proposedFeatureId - The feature ID being proposed
253
+ * @param {string[]} proposedDeps - Proposed dependency list for the new feature
254
+ * @param {import('./cap-feature-map.cjs').Feature[]} existingFeatures - All existing features
255
+ * @returns {CircularDepResult}
256
+ */
257
+ function detectCircularDeps(proposedFeatureId, proposedDeps, existingFeatures) {
258
+ // Build adjacency list: featureId -> [dependencies]
259
+ /** @type {Map<string, string[]>} */
260
+ const graph = new Map();
261
+
262
+ for (const f of existingFeatures) {
263
+ graph.set(f.id, [...(f.dependencies || [])]);
264
+ }
265
+
266
+ // Add proposed feature and its dependencies
267
+ graph.set(proposedFeatureId, [...proposedDeps]);
268
+
269
+ // DFS cycle detection
270
+ const WHITE = 0; // unvisited
271
+ const GRAY = 1; // in current path
272
+ const BLACK = 2; // fully processed
273
+
274
+ /** @type {Map<string, number>} */
275
+ const color = new Map();
276
+ /** @type {Map<string, string|null>} */
277
+ const parent = new Map();
278
+
279
+ for (const id of graph.keys()) {
280
+ color.set(id, WHITE);
281
+ parent.set(id, null);
282
+ }
283
+
284
+ /**
285
+ * DFS from node, returns cycle path if found.
286
+ * @param {string} node
287
+ * @returns {string[]|null}
288
+ */
289
+ function dfs(node) {
290
+ color.set(node, GRAY);
291
+
292
+ for (const neighbor of graph.get(node) || []) {
293
+ // Neighbor might not be in graph (e.g., references non-existent feature)
294
+ if (!graph.has(neighbor)) continue;
295
+
296
+ if (color.get(neighbor) === GRAY) {
297
+ // Found cycle — reconstruct path
298
+ const cycle = [neighbor, node];
299
+ let current = node;
300
+ while (current !== neighbor) {
301
+ current = parent.get(current);
302
+ if (current === null || current === neighbor) break;
303
+ cycle.push(current);
304
+ }
305
+ cycle.push(neighbor);
306
+ cycle.reverse();
307
+ return cycle;
308
+ }
309
+
310
+ if (color.get(neighbor) === WHITE) {
311
+ parent.set(neighbor, node);
312
+ const result = dfs(neighbor);
313
+ if (result) return result;
314
+ }
315
+ }
316
+
317
+ color.set(node, BLACK);
318
+ return null;
319
+ }
320
+
321
+ // Start DFS from proposed feature to find cycles involving it
322
+ const cycle = dfs(proposedFeatureId);
323
+ if (cycle) {
324
+ return { hasCycle: true, cycle };
325
+ }
326
+
327
+ // Also check all other unvisited nodes (in case proposed deps create a cycle elsewhere)
328
+ for (const id of graph.keys()) {
329
+ if (color.get(id) === WHITE) {
330
+ const result = dfs(id);
331
+ if (result) {
332
+ return { hasCycle: true, cycle: result };
333
+ }
334
+ }
335
+ }
336
+
337
+ return { hasCycle: false, cycle: [] };
338
+ }
339
+
340
+ // --- Impact Report Generation ---
341
+
342
+ // @cap-todo(ac:F-033/AC-3) Present structured impact report: overlapping ACs with similarity reasoning, affected dependency chains, implementation file conflicts
343
+
344
+ /**
345
+ * Generate a full impact report for a proposed feature.
346
+ * Orchestrates overlap detection, dependency tracing, and circular dep checks.
347
+ *
348
+ * @param {import('./cap-feature-map.cjs').Feature} proposedFeature - The proposed feature (must have id, title, acs, files, dependencies)
349
+ * @param {import('./cap-feature-map.cjs').Feature[]} existingFeatures - All existing features from Feature Map
350
+ * @param {Object} [options]
351
+ * @param {number} [options.similarityThreshold] - Override AC similarity threshold
352
+ * @param {number} [options.minSharedKeywords] - Override minimum shared keywords
353
+ * @returns {ImpactReport}
354
+ */
355
+ function generateImpactReport(proposedFeature, existingFeatures, options = {}) {
356
+ // AC overlap and file conflicts
357
+ const { overlaps, fileConflicts } = detectOverlap(proposedFeature, existingFeatures, options);
358
+
359
+ // Dependency chain analysis
360
+ // Merge existing features with proposed feature for full graph
361
+ const allFeatures = [...existingFeatures.filter(f => f.id !== proposedFeature.id), proposedFeature];
362
+ const affectedChains = traceDependencyChain(proposedFeature.id, allFeatures);
363
+
364
+ // Circular dependency check
365
+ const circularRisks = detectCircularDeps(
366
+ proposedFeature.id,
367
+ proposedFeature.dependencies || [],
368
+ existingFeatures
369
+ );
370
+
371
+ // Generate resolutions
372
+ const report = {
373
+ proposedFeatureTitle: proposedFeature.title || '',
374
+ timestamp: new Date().toISOString(),
375
+ overlappingACs: overlaps,
376
+ affectedChains,
377
+ fileConflicts,
378
+ circularRisks,
379
+ resolutions: [],
380
+ };
381
+
382
+ report.resolutions = proposeResolutions(report);
383
+
384
+ return report;
385
+ }
386
+
387
+ // --- Resolution Proposals ---
388
+
389
+ // @cap-todo(ac:F-033/AC-5) Propose concrete resolutions: merge ACs into existing feature, split into separate features, adjust dependency ordering, or flag as intentional duplication
390
+
391
+ /**
392
+ * Propose concrete resolutions for issues found in the impact report.
393
+ * For each overlap/conflict, suggests merge, split, adjust, or flag.
394
+ *
395
+ * @param {ImpactReport} report - The impact report to generate resolutions for
396
+ * @returns {Resolution[]}
397
+ */
398
+ function proposeResolutions(report) {
399
+ const resolutions = [];
400
+
401
+ // --- Circular dependency resolution ---
402
+ if (report.circularRisks.hasCycle) {
403
+ resolutions.push({
404
+ type: 'adjust',
405
+ description: `Circular dependency detected: ${report.circularRisks.cycle.join(' -> ')}. Reorder dependencies or break the cycle by extracting shared concerns into a separate feature.`,
406
+ affectedFeatures: [...new Set(report.circularRisks.cycle)],
407
+ });
408
+ }
409
+
410
+ // --- Group AC overlaps by existing feature ---
411
+ /** @type {Map<string, ACOverlap[]>} existingFeatureId -> overlaps */
412
+ const overlapsByFeature = new Map();
413
+ for (const overlap of report.overlappingACs) {
414
+ if (!overlapsByFeature.has(overlap.existingFeatureId)) {
415
+ overlapsByFeature.set(overlap.existingFeatureId, []);
416
+ }
417
+ overlapsByFeature.get(overlap.existingFeatureId).push(overlap);
418
+ }
419
+
420
+ for (const [featureId, overlaps] of overlapsByFeature) {
421
+ const featureTitle = overlaps[0].existingFeatureTitle;
422
+ const avgSimilarity = overlaps.reduce((sum, o) => sum + o.similarity, 0) / overlaps.length;
423
+
424
+ if (avgSimilarity >= 0.5) {
425
+ // High overlap — suggest merge
426
+ resolutions.push({
427
+ type: 'merge',
428
+ description: `High AC overlap (avg ${(avgSimilarity * 100).toFixed(0)}%) with ${featureId} (${featureTitle}). Consider merging ${overlaps.length} overlapping AC(s) into the existing feature.`,
429
+ affectedFeatures: [featureId],
430
+ });
431
+ } else if (overlaps.length >= 3) {
432
+ // Many partial overlaps — suggest split
433
+ resolutions.push({
434
+ type: 'split',
435
+ description: `Multiple partial overlaps (${overlaps.length} ACs) with ${featureId} (${featureTitle}). Consider extracting shared concerns into a separate shared feature.`,
436
+ affectedFeatures: [featureId],
437
+ });
438
+ } else {
439
+ // Low overlap — flag for awareness
440
+ resolutions.push({
441
+ type: 'flag',
442
+ description: `Minor AC overlap with ${featureId} (${featureTitle}): ${overlaps.map(o => `${o.proposedACId} ~ ${o.existingACId}`).join(', ')}. May be intentional duplication.`,
443
+ affectedFeatures: [featureId],
444
+ });
445
+ }
446
+ }
447
+
448
+ // --- File conflict resolutions ---
449
+ if (report.fileConflicts.length > 0) {
450
+ const affectedFiles = report.fileConflicts.map(c => c.filePath);
451
+ const affectedFeatures = [...new Set(report.fileConflicts.flatMap(c => c.existingFeatureIds))];
452
+
453
+ resolutions.push({
454
+ type: 'adjust',
455
+ description: `File ownership conflicts on ${affectedFiles.length} file(s): ${affectedFiles.join(', ')}. Verify intended scope or split responsibilities.`,
456
+ affectedFeatures,
457
+ });
458
+ }
459
+
460
+ return resolutions;
461
+ }
462
+
463
+ // --- Report Persistence ---
464
+
465
+ // @cap-todo(ac:F-033/AC-7) Persist impact analysis results in .cap/memory/impact/{feature-id}.md for audit trail and future reference
466
+
467
+ /**
468
+ * Persist an impact report to .cap/memory/impact/{feature-id}.md.
469
+ * Creates the directory if it does not exist.
470
+ *
471
+ * @param {string} cwd - Absolute path to project root
472
+ * @param {string} featureId - Feature ID for the report filename
473
+ * @param {ImpactReport} report - The impact report to persist
474
+ */
475
+ function persistReport(cwd, featureId, report) {
476
+ const impactDir = path.join(cwd, IMPACT_DIR);
477
+ if (!fs.existsSync(impactDir)) {
478
+ fs.mkdirSync(impactDir, { recursive: true });
479
+ }
480
+
481
+ const filePath = path.join(impactDir, `${featureId}.md`);
482
+ const content = serializeReport(featureId, report);
483
+ fs.writeFileSync(filePath, content, 'utf8');
484
+ }
485
+
486
+ /**
487
+ * Serialize an impact report to markdown format.
488
+ *
489
+ * @param {string} featureId - Feature ID
490
+ * @param {ImpactReport} report - The impact report
491
+ * @returns {string} Markdown content
492
+ */
493
+ function serializeReport(featureId, report) {
494
+ const lines = [
495
+ `# Impact Analysis: ${featureId}`,
496
+ '',
497
+ `**Feature:** ${report.proposedFeatureTitle}`,
498
+ `**Generated:** ${report.timestamp}`,
499
+ '',
500
+ ];
501
+
502
+ // --- Overlapping ACs ---
503
+ lines.push('## Overlapping ACs');
504
+ lines.push('');
505
+ if (report.overlappingACs.length === 0) {
506
+ lines.push('No AC overlaps detected.');
507
+ } else {
508
+ lines.push('| Proposed AC | Existing Feature | Existing AC | Similarity | Shared Keywords |');
509
+ lines.push('|-------------|-----------------|-------------|------------|-----------------|');
510
+ for (const o of report.overlappingACs) {
511
+ lines.push(`| ${o.proposedACId} | ${o.existingFeatureId} | ${o.existingACId} | ${(o.similarity * 100).toFixed(1)}% | ${o.sharedKeywords.join(', ')} |`);
512
+ }
513
+ }
514
+ lines.push('');
515
+
516
+ // --- Dependency Chains ---
517
+ lines.push('## Dependency Chains');
518
+ lines.push('');
519
+ lines.push(`**Upstream (depends on):** ${report.affectedChains.upstream.length > 0 ? report.affectedChains.upstream.join(', ') : 'none'}`);
520
+ lines.push(`**Downstream (depended on by):** ${report.affectedChains.downstream.length > 0 ? report.affectedChains.downstream.join(', ') : 'none'}`);
521
+ lines.push(`**Max depth:** ${report.affectedChains.depth}`);
522
+ lines.push('');
523
+
524
+ // --- File Conflicts ---
525
+ lines.push('## File Conflicts');
526
+ lines.push('');
527
+ if (report.fileConflicts.length === 0) {
528
+ lines.push('No file conflicts detected.');
529
+ } else {
530
+ for (const c of report.fileConflicts) {
531
+ lines.push(`- \`${c.filePath}\` — also referenced by: ${c.existingFeatureIds.join(', ')}`);
532
+ }
533
+ }
534
+ lines.push('');
535
+
536
+ // --- Circular Dependency Risks ---
537
+ lines.push('## Circular Dependency Risks');
538
+ lines.push('');
539
+ if (report.circularRisks.hasCycle) {
540
+ lines.push(`**WARNING:** Circular dependency detected: ${report.circularRisks.cycle.join(' -> ')}`);
541
+ } else {
542
+ lines.push('No circular dependencies detected.');
543
+ }
544
+ lines.push('');
545
+
546
+ // --- Proposed Resolutions ---
547
+ lines.push('## Proposed Resolutions');
548
+ lines.push('');
549
+ if (report.resolutions.length === 0) {
550
+ lines.push('No resolutions needed — clean integration.');
551
+ } else {
552
+ for (let i = 0; i < report.resolutions.length; i++) {
553
+ const r = report.resolutions[i];
554
+ lines.push(`${i + 1}. **[${r.type.toUpperCase()}]** ${r.description}`);
555
+ lines.push(` Affects: ${r.affectedFeatures.join(', ')}`);
556
+ }
557
+ }
558
+ lines.push('');
559
+
560
+ lines.push('---');
561
+ lines.push('*This report is advisory only. No Feature Map modifications were made.*');
562
+ lines.push('');
563
+
564
+ return lines.join('\n');
565
+ }
566
+
567
+ /**
568
+ * Load a previously persisted impact report from disk.
569
+ *
570
+ * @param {string} cwd - Absolute path to project root
571
+ * @param {string} featureId - Feature ID to load report for
572
+ * @returns {string|null} Raw markdown content, or null if not found
573
+ */
574
+ function loadReport(cwd, featureId) {
575
+ const filePath = path.join(cwd, IMPACT_DIR, `${featureId}.md`);
576
+ try {
577
+ if (!fs.existsSync(filePath)) return null;
578
+ return fs.readFileSync(filePath, 'utf8');
579
+ } catch (_e) {
580
+ return null;
581
+ }
582
+ }
583
+
584
+ // --- Main Entry Point ---
585
+
586
+ // @cap-todo(ac:F-033/AC-6) All proposals shall be advisory only — no Feature Map modifications without explicit user approval
587
+ // @cap-decision analyzeImpact reads Feature Map but NEVER writes to it. All returned data is advisory for caller to present.
588
+
589
+ /**
590
+ * Main entry point: analyze the impact of a proposed feature against the existing Feature Map.
591
+ * Reads Feature Map, runs all analysis, returns full report.
592
+ * Does NOT modify Feature Map.
593
+ *
594
+ * @param {string} cwd - Absolute path to project root
595
+ * @param {import('./cap-feature-map.cjs').Feature} proposedFeature - The proposed feature
596
+ * @param {Object} [options]
597
+ * @param {boolean} [options.persist] - If true, persist report to disk (default: false)
598
+ * @param {string|null} [options.appPath] - Relative app path for monorepo scoping
599
+ * @param {number} [options.similarityThreshold] - Override AC similarity threshold
600
+ * @param {number} [options.minSharedKeywords] - Override minimum shared keywords
601
+ * @returns {ImpactReport}
602
+ */
603
+ function analyzeImpact(cwd, proposedFeature, options = {}) {
604
+ const {
605
+ persist = false,
606
+ appPath = null,
607
+ similarityThreshold,
608
+ minSharedKeywords,
609
+ } = options;
610
+
611
+ // @cap-todo(ac:F-081/AC-4 iter:2) Migrated to {safe: true} opt-in to preserve CLI on duplicate-ID FEATURE-MAP.
612
+ // @cap-decision(F-081/iter2) Warn on parseError; continue with partial map for read-only display.
613
+ const featureMap = readFeatureMap(cwd, appPath, { safe: true });
614
+ if (featureMap && featureMap.parseError) {
615
+ console.warn('cap: impact-analysis — duplicate feature ID detected, report uses partial map: ' + String(featureMap.parseError.message).trim());
616
+ }
617
+ const existingFeatures = featureMap.features || [];
618
+
619
+ const report = generateImpactReport(proposedFeature, existingFeatures, {
620
+ similarityThreshold,
621
+ minSharedKeywords,
622
+ });
623
+
624
+ if (persist && proposedFeature.id) {
625
+ persistReport(cwd, proposedFeature.id, report);
626
+ }
627
+
628
+ return report;
629
+ }
630
+
631
+ module.exports = {
632
+ // Core analysis
633
+ detectOverlap,
634
+ traceDependencyChain,
635
+ detectCircularDeps,
636
+ generateImpactReport,
637
+ proposeResolutions,
638
+
639
+ // Persistence
640
+ persistReport,
641
+ serializeReport,
642
+ loadReport,
643
+
644
+ // Main entry point
645
+ analyzeImpact,
646
+
647
+ // Constants (exposed for testing and configuration)
648
+ IMPACT_DIR,
649
+ AC_SIMILARITY_THRESHOLD,
650
+ AC_MIN_SHARED_KEYWORDS,
651
+ FILE_OVERLAP_THRESHOLD,
652
+ };