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,545 @@
1
+ // @cap-feature(feature:F-032) Build Thread Reconnection and Synthesis Engine — compare returning threads, propose reconnection strategies, detect AC conflicts, merge/supersede/branch/resume threads
2
+ // @cap-decision Pure logic module with explicit I/O functions — same pattern as cap-thread-tracker.cjs. Analysis functions are side-effect-free; only logResolution and executeSupersede write to disk.
3
+ // @cap-decision AC-3 (user approval) is enforced at the COMMAND layer (brainstorm.md), not in this module. This module produces proposals; the command presents them for user approval before executing.
4
+ // @cap-constraint Zero external dependencies — uses only Node.js built-ins (fs, path, crypto).
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('node:fs');
9
+ const path = require('node:path');
10
+ const crypto = require('node:crypto');
11
+
12
+ const {
13
+ extractKeywords,
14
+ computeKeywordOverlap,
15
+ loadThread,
16
+ saveThread,
17
+ loadIndex,
18
+ saveIndex,
19
+ THREADS_DIR,
20
+ } = require('./cap-thread-tracker.cjs');
21
+
22
+ const {
23
+ readFeatureMap,
24
+ } = require('./cap-feature-map.cjs');
25
+
26
+ // --- Constants ---
27
+
28
+ /** Directory for resolution records relative to project root. */
29
+ const RESOLUTIONS_DIR = path.join('.cap', 'memory', 'threads', 'resolutions');
30
+
31
+ /** Overlap score thresholds for strategy recommendation. */
32
+ const STRATEGY_THRESHOLDS = {
33
+ /** Above this overlap score, recommend merge or resume. */
34
+ HIGH_OVERLAP: 0.6,
35
+ /** Above this overlap score, recommend branch. Below recommends supersede. */
36
+ MODERATE_OVERLAP: 0.3,
37
+ };
38
+
39
+ /** Reconnection strategy names. */
40
+ const STRATEGIES = /** @type {const} */ (['merge', 'supersede', 'branch', 'resume']);
41
+
42
+ // --- Types ---
43
+
44
+ /**
45
+ * @typedef {Object} ThreadComparison
46
+ * @property {Object} oldSummary - Summary of the old thread
47
+ * @property {string} oldSummary.problemStatement - Old thread's problem statement
48
+ * @property {string} oldSummary.solutionShape - Old thread's solution direction
49
+ * @property {string[]} oldSummary.boundaryDecisions - Old thread's boundary decisions
50
+ * @property {string[]} oldSummary.featureIds - Old thread's feature IDs
51
+ * @property {Object} newSummary - Summary of the new direction
52
+ * @property {string} newSummary.problemStatement - New prompt/problem statement
53
+ * @property {string[]} newSummary.keywords - Extracted keywords from new prompt
54
+ * @property {number} overlapScore - Keyword overlap ratio (0-1)
55
+ * @property {string[]} sharedFeatures - Feature IDs shared between old and new
56
+ * @property {string[]} divergentPoints - Areas where old and new differ
57
+ */
58
+
59
+ /**
60
+ * @typedef {Object} StrategyProposal
61
+ * @property {'merge'|'supersede'|'branch'|'resume'} recommended - Recommended strategy
62
+ * @property {string} reasoning - Why this strategy was recommended
63
+ * @property {Array<{strategy: string, reasoning: string}>} alternatives - Other viable strategies
64
+ */
65
+
66
+ /**
67
+ * @typedef {Object} ACConflict
68
+ * @property {string} featureId - The feature containing the conflict
69
+ * @property {Object} oldAC - The existing AC
70
+ * @property {string} oldAC.id - AC identifier
71
+ * @property {string} oldAC.description - AC description
72
+ * @property {Object} newAC - The conflicting new AC
73
+ * @property {string} newAC.id - AC identifier
74
+ * @property {string} newAC.description - AC description
75
+ * @property {string} reason - Why these ACs conflict
76
+ */
77
+
78
+ /**
79
+ * @typedef {Object} ConflictResult
80
+ * @property {ACConflict[]} conflicts - Contradictory ACs
81
+ * @property {Array<{featureId: string, ac: Object}>} compatible - Non-conflicting ACs
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} MergeResult
86
+ * @property {Array<{id: string, description: string, status: string, source: string}>} mergedACs - Combined AC set
87
+ * @property {ACConflict[]} conflicts - ACs that conflict and need manual resolution
88
+ * @property {Object} mergedThread - The merged thread object
89
+ */
90
+
91
+ /**
92
+ * @typedef {Object} SupersedeResult
93
+ * @property {Object} archivedThread - The old thread with archived flag
94
+ * @property {Object} updatedIndex - The updated thread index
95
+ */
96
+
97
+ /**
98
+ * @typedef {Object} ResolutionRecord
99
+ * @property {string} id - Resolution ID
100
+ * @property {string} timestamp - ISO timestamp
101
+ * @property {'merge'|'supersede'|'branch'|'resume'} strategy - Strategy that was applied
102
+ * @property {string[]} threadIds - Thread IDs involved
103
+ * @property {Object} details - Strategy-specific details
104
+ * @property {string} reasoning - Why this resolution was chosen
105
+ */
106
+
107
+ /**
108
+ * @typedef {Object} ReconnectResult
109
+ * @property {ThreadComparison} comparison - Side-by-side comparison
110
+ * @property {StrategyProposal} proposal - Recommended strategy
111
+ * @property {ConflictResult|null} conflicts - AC conflicts if detected
112
+ */
113
+
114
+ // --- Thread Comparison ---
115
+
116
+ // @cap-todo(ac:F-032/AC-1) Present side-by-side comparison of previous thread conclusions versus new session direction when returning thread detected
117
+
118
+ /**
119
+ * Produce a structured comparison between an old thread and a new prompt direction.
120
+ * @param {Object} oldThread - The previously stored thread object
121
+ * @param {string} newPrompt - The new session's problem statement / prompt
122
+ * @returns {ThreadComparison}
123
+ */
124
+ function compareThreads(oldThread, newPrompt) {
125
+ const oldKeywords = oldThread.keywords || extractKeywords(
126
+ [oldThread.problemStatement, oldThread.solutionShape, ...(oldThread.boundaryDecisions || [])].join(' ')
127
+ );
128
+ const newKeywords = extractKeywords(newPrompt);
129
+
130
+ const { shared, overlapRatio } = computeKeywordOverlap(oldKeywords, newKeywords);
131
+
132
+ // Identify divergent points: keywords in new but not old, and keywords in old but not new
133
+ const oldSet = new Set(oldKeywords);
134
+ const newSet = new Set(newKeywords);
135
+ const divergentPoints = [];
136
+
137
+ const onlyInNew = newKeywords.filter(k => !oldSet.has(k));
138
+ const onlyInOld = oldKeywords.filter(k => !newSet.has(k));
139
+
140
+ if (onlyInNew.length > 0) {
141
+ divergentPoints.push(`New direction introduces: ${onlyInNew.slice(0, 5).join(', ')}`);
142
+ }
143
+ if (onlyInOld.length > 0) {
144
+ divergentPoints.push(`Previous thread covered: ${onlyInOld.slice(0, 5).join(', ')}`);
145
+ }
146
+
147
+ // Check for shared feature IDs by extracting F-NNN patterns from the new prompt
148
+ const featurePattern = /F-\d{3}/g;
149
+ const newFeatureRefs = (newPrompt.match(featurePattern) || []);
150
+ const oldFeatureIds = oldThread.featureIds || [];
151
+ const sharedFeatures = oldFeatureIds.filter(fid => newFeatureRefs.includes(fid));
152
+
153
+ return {
154
+ oldSummary: {
155
+ problemStatement: oldThread.problemStatement || '',
156
+ solutionShape: oldThread.solutionShape || '',
157
+ boundaryDecisions: oldThread.boundaryDecisions || [],
158
+ featureIds: oldFeatureIds,
159
+ },
160
+ newSummary: {
161
+ problemStatement: newPrompt,
162
+ keywords: newKeywords,
163
+ },
164
+ overlapScore: overlapRatio,
165
+ sharedFeatures,
166
+ divergentPoints,
167
+ };
168
+ }
169
+
170
+ // --- Strategy Proposal ---
171
+
172
+ // @cap-todo(ac:F-032/AC-2) Propose one of four reconnection strategies: merge, supersede, branch, or resume
173
+
174
+ /**
175
+ * Based on a thread comparison, recommend a reconnection strategy.
176
+ * @param {ThreadComparison} comparison - Result from compareThreads
177
+ * @returns {StrategyProposal}
178
+ */
179
+ function proposeStrategy(comparison) {
180
+ const { overlapScore, sharedFeatures, divergentPoints } = comparison;
181
+ const hasSharedFeatures = sharedFeatures.length > 0;
182
+ const hasDivergence = divergentPoints.length > 0;
183
+
184
+ // @cap-decision Strategy selection heuristic: high overlap + no divergence = resume; high overlap + divergence = merge; moderate overlap = branch; low overlap = supersede.
185
+
186
+ let recommended;
187
+ let reasoning;
188
+ const alternatives = [];
189
+
190
+ if (overlapScore >= STRATEGY_THRESHOLDS.HIGH_OVERLAP && !hasDivergence) {
191
+ // Very similar, no new direction — resume where left off
192
+ recommended = 'resume';
193
+ reasoning = `High keyword overlap (${(overlapScore * 100).toFixed(0)}%) with no significant divergence — the new session appears to continue the same work.`;
194
+ alternatives.push({
195
+ strategy: 'merge',
196
+ reasoning: 'Could merge if the new session adds new ACs to the existing thread.',
197
+ });
198
+ } else if (overlapScore >= STRATEGY_THRESHOLDS.HIGH_OVERLAP && hasDivergence) {
199
+ // High overlap but new direction — merge the threads
200
+ recommended = 'merge';
201
+ reasoning = `High keyword overlap (${(overlapScore * 100).toFixed(0)}%) but with divergent points — merging would combine the best of both directions.`;
202
+ alternatives.push({
203
+ strategy: 'resume',
204
+ reasoning: 'Could resume if the divergent points are minor refinements rather than new directions.',
205
+ });
206
+ alternatives.push({
207
+ strategy: 'branch',
208
+ reasoning: 'Could branch if the divergent direction should be explored independently.',
209
+ });
210
+ } else if (overlapScore >= STRATEGY_THRESHOLDS.MODERATE_OVERLAP || hasSharedFeatures) {
211
+ // Moderate overlap — they share some ground but go different ways
212
+ recommended = 'branch';
213
+ reasoning = `Moderate keyword overlap (${(overlapScore * 100).toFixed(0)}%)${hasSharedFeatures ? ' with shared features' : ''} — the topics are related but distinct enough to coexist.`;
214
+ alternatives.push({
215
+ strategy: 'merge',
216
+ reasoning: 'Could merge if the directions are ultimately converging.',
217
+ });
218
+ alternatives.push({
219
+ strategy: 'supersede',
220
+ reasoning: 'Could supersede if the old direction is no longer relevant.',
221
+ });
222
+ } else {
223
+ // Low overlap — new direction replaces old
224
+ recommended = 'supersede';
225
+ reasoning = `Low keyword overlap (${(overlapScore * 100).toFixed(0)}%) and no shared features — the new session takes a fundamentally different approach.`;
226
+ alternatives.push({
227
+ strategy: 'branch',
228
+ reasoning: 'Could branch if the old direction might still be valuable independently.',
229
+ });
230
+ }
231
+
232
+ return { recommended, reasoning, alternatives };
233
+ }
234
+
235
+ // --- AC Conflict Detection ---
236
+
237
+ // @cap-todo(ac:F-032/AC-6) Detect AC-level conflicts between threads — contradictory acceptance criteria from different brainstorm sessions
238
+
239
+ /**
240
+ * Detect conflicts between existing ACs (from old thread's features) and new ACs.
241
+ * @param {string[]} oldFeatureIds - Feature IDs from the old thread
242
+ * @param {Array<{featureId: string, id: string, description: string}>} newACs - New ACs to compare against
243
+ * @param {Object} featureMap - Parsed feature map from readFeatureMap
244
+ * @returns {ConflictResult}
245
+ */
246
+ function detectACConflicts(oldFeatureIds, newACs, featureMap) {
247
+ const conflicts = [];
248
+ const compatible = [];
249
+
250
+ // Collect existing ACs from old thread's features
251
+ const existingACs = [];
252
+ for (const fid of oldFeatureIds) {
253
+ const feature = (featureMap.features || []).find(f => f.id === fid);
254
+ if (!feature) continue;
255
+ for (const ac of (feature.acs || [])) {
256
+ existingACs.push({ featureId: fid, ...ac });
257
+ }
258
+ }
259
+
260
+ // Compare each new AC against existing ACs for the same feature
261
+ for (const newAC of newACs) {
262
+ let hasConflict = false;
263
+
264
+ const sameFeatureACs = existingACs.filter(eac => eac.featureId === newAC.featureId);
265
+ for (const oldAC of sameFeatureACs) {
266
+ const conflictReason = detectSingleConflict(oldAC, newAC);
267
+ if (conflictReason) {
268
+ conflicts.push({
269
+ featureId: newAC.featureId,
270
+ oldAC: { id: oldAC.id, description: oldAC.description },
271
+ newAC: { id: newAC.id, description: newAC.description },
272
+ reason: conflictReason,
273
+ });
274
+ hasConflict = true;
275
+ }
276
+ }
277
+
278
+ if (!hasConflict) {
279
+ compatible.push({ featureId: newAC.featureId, ac: { id: newAC.id, description: newAC.description } });
280
+ }
281
+ }
282
+
283
+ return { conflicts, compatible };
284
+ }
285
+
286
+ /**
287
+ * Detect if two ACs contradict each other.
288
+ * Uses keyword overlap and negation detection as a heuristic.
289
+ * @param {Object} acA - First AC with description
290
+ * @param {Object} acB - Second AC with description
291
+ * @returns {string|null} Conflict reason or null if compatible
292
+ */
293
+ function detectSingleConflict(acA, acB) {
294
+ const descA = (acA.description || '').toLowerCase();
295
+ const descB = (acB.description || '').toLowerCase();
296
+
297
+ // High keyword overlap with negation signals a contradiction
298
+ const keywordsA = extractKeywords(descA);
299
+ const keywordsB = extractKeywords(descB);
300
+ const { overlapRatio } = computeKeywordOverlap(keywordsA, keywordsB);
301
+
302
+ // Check for negation patterns
303
+ const negationPatterns = [
304
+ /\bshall not\b/,
305
+ /\bmust not\b/,
306
+ /\bshould not\b/,
307
+ /\bnever\b/,
308
+ /\bno\b/,
309
+ /\bdisable\b/,
310
+ /\bremove\b/,
311
+ /\bwithout\b/,
312
+ /\bprevent\b/,
313
+ /\bprohibit\b/,
314
+ ];
315
+
316
+ const aNegated = negationPatterns.some(p => p.test(descA));
317
+ const bNegated = negationPatterns.some(p => p.test(descB));
318
+
319
+ // High overlap + one negated = likely contradiction
320
+ if (overlapRatio >= 0.4 && (aNegated !== bNegated)) {
321
+ return `High keyword overlap (${(overlapRatio * 100).toFixed(0)}%) with opposing intent — one AC negates what the other requires.`;
322
+ }
323
+
324
+ // Very high overlap on same feature = potential duplicate or contradiction
325
+ if (overlapRatio >= 0.7) {
326
+ return `Very high keyword overlap (${(overlapRatio * 100).toFixed(0)}%) — ACs may duplicate or contradict each other.`;
327
+ }
328
+
329
+ return null;
330
+ }
331
+
332
+ // --- Merge Execution ---
333
+
334
+ // @cap-todo(ac:F-032/AC-4) When merge approved, produce unified AC set combining non-conflicting criteria and flagging conflicts for manual resolution
335
+
336
+ /**
337
+ * Combine ACs from old and new threads, keeping non-conflicting ones and flagging conflicts.
338
+ * @param {Object} oldThread - The old thread object
339
+ * @param {Object} newThread - The new thread object
340
+ * @param {Object} featureMap - Parsed feature map
341
+ * @param {Object} [resolution] - Optional manual conflict resolutions
342
+ * @param {Object.<string, 'keep-old'|'keep-new'|'keep-both'>} [resolution.conflictChoices] - How to resolve each conflict by conflict index
343
+ * @returns {MergeResult}
344
+ */
345
+ function executeMerge(oldThread, newThread, featureMap, resolution = {}) {
346
+ const allFeatureIds = [...new Set([
347
+ ...(oldThread.featureIds || []),
348
+ ...(newThread.featureIds || []),
349
+ ])];
350
+
351
+ const mergedACs = [];
352
+ // @cap-risk Untested code path: conflicts array is always returned empty — conflict detection within merge is not yet implemented
353
+ const conflicts = [];
354
+ // @cap-risk Untested code path: conflictChoices parameter is accepted but never consumed — manual conflict resolution logic is not yet wired up
355
+ const conflictChoices = resolution.conflictChoices || {};
356
+
357
+ for (const fid of allFeatureIds) {
358
+ const feature = (featureMap.features || []).find(f => f.id === fid);
359
+ if (!feature) continue;
360
+
361
+ const oldHasFeature = (oldThread.featureIds || []).includes(fid);
362
+ const newHasFeature = (newThread.featureIds || []).includes(fid);
363
+
364
+ if (oldHasFeature && !newHasFeature) {
365
+ // Only in old thread — carry forward
366
+ for (const ac of (feature.acs || [])) {
367
+ mergedACs.push({ ...ac, source: `old:${oldThread.id}` });
368
+ }
369
+ } else if (!oldHasFeature && newHasFeature) {
370
+ // Only in new thread — include
371
+ for (const ac of (feature.acs || [])) {
372
+ mergedACs.push({ ...ac, source: `new:${newThread.id}` });
373
+ }
374
+ } else {
375
+ // Both threads reference this feature — check for conflicts
376
+ for (const ac of (feature.acs || [])) {
377
+ mergedACs.push({ ...ac, source: 'shared' });
378
+ }
379
+ }
380
+ }
381
+
382
+ // Build merged thread
383
+ const mergedThread = {
384
+ ...newThread,
385
+ featureIds: allFeatureIds,
386
+ boundaryDecisions: [
387
+ ...(oldThread.boundaryDecisions || []),
388
+ ...(newThread.boundaryDecisions || []),
389
+ ],
390
+ solutionShape: newThread.solutionShape || oldThread.solutionShape,
391
+ parentThreadId: oldThread.id,
392
+ divergencePoint: `Merged from thread ${oldThread.id}`,
393
+ };
394
+
395
+ return { mergedACs, conflicts, mergedThread };
396
+ }
397
+
398
+ // --- Supersede Execution ---
399
+
400
+ // @cap-todo(ac:F-032/AC-5) When supersede approved, mark old thread as archived and update Feature Map entries that referenced old thread ACs
401
+
402
+ /**
403
+ * Mark old thread as archived and update the thread index.
404
+ * @param {Object} oldThread - The thread to archive
405
+ * @param {Object} newThread - The thread that supersedes it
406
+ * @param {string} cwd - Project root path
407
+ * @returns {SupersedeResult}
408
+ */
409
+ function executeSupersede(oldThread, newThread, cwd) {
410
+ // Mark old thread as archived
411
+ const archivedThread = {
412
+ ...oldThread,
413
+ archived: true,
414
+ archivedBy: newThread.id,
415
+ archivedAt: new Date().toISOString(),
416
+ };
417
+
418
+ // Save archived thread to disk
419
+ saveThread(cwd, archivedThread);
420
+
421
+ // Update index — mark the old thread entry with archived flag
422
+ const index = loadIndex(cwd);
423
+ const entry = index.threads.find(t => t.id === oldThread.id);
424
+ if (entry) {
425
+ entry.archived = true;
426
+ entry.archivedBy = newThread.id;
427
+ }
428
+ saveIndex(cwd, index);
429
+
430
+ return { archivedThread, updatedIndex: index };
431
+ }
432
+
433
+ // --- Resolution Logging ---
434
+
435
+ // @cap-todo(ac:F-032/AC-7) Log synthesis results in .cap/memory/threads/ with resolution record documenting what was merged, split, or discarded and why
436
+
437
+ /**
438
+ * Write a resolution record to .cap/memory/threads/resolutions/.
439
+ * @param {string} cwd - Project root path
440
+ * @param {Object} resolution - Resolution details
441
+ * @param {'merge'|'supersede'|'branch'|'resume'} resolution.strategy - Strategy applied
442
+ * @param {string[]} resolution.threadIds - Thread IDs involved
443
+ * @param {Object} resolution.details - Strategy-specific details (mergedACs, archivedThread, etc.)
444
+ * @param {string} resolution.reasoning - Why this resolution was chosen
445
+ * @returns {ResolutionRecord}
446
+ */
447
+ function logResolution(cwd, resolution) {
448
+ const resolutionsDir = path.join(cwd, RESOLUTIONS_DIR);
449
+ if (!fs.existsSync(resolutionsDir)) {
450
+ fs.mkdirSync(resolutionsDir, { recursive: true });
451
+ }
452
+
453
+ const timestamp = new Date().toISOString();
454
+ const id = 'res-' + crypto.randomBytes(4).toString('hex');
455
+
456
+ const record = {
457
+ id,
458
+ timestamp,
459
+ strategy: resolution.strategy,
460
+ threadIds: resolution.threadIds,
461
+ details: resolution.details || {},
462
+ reasoning: resolution.reasoning || '',
463
+ };
464
+
465
+ const filePath = path.join(resolutionsDir, `${id}.json`);
466
+ // @cap-decision Sorted keys and 2-space indent for git-friendly diffs — matches cap-thread-tracker.cjs pattern
467
+ fs.writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n', 'utf8');
468
+
469
+ return record;
470
+ }
471
+
472
+ // --- Main Entry Point ---
473
+
474
+ // @cap-decision reconnect() orchestrates compare -> propose -> return proposal. Execution (merge/supersede/branch/resume) is a separate step because AC-3 requires user approval between proposal and execution.
475
+
476
+ /**
477
+ * Main entry point: compare a returning thread match against the new prompt,
478
+ * propose a reconnection strategy, and detect AC conflicts.
479
+ * Does NOT execute — returns the proposal for the command layer to present.
480
+ * @param {string} cwd - Project root path
481
+ * @param {Object} matchResult - A single match from checkPriorThreads
482
+ * @param {string} matchResult.threadId - The matching thread ID
483
+ * @param {number} matchResult.score - Relevance score
484
+ * @param {string} newPrompt - The new session's problem statement
485
+ * @param {Object} [options]
486
+ * @param {string[]} [options.newFeatureIds] - Feature IDs referenced in new session
487
+ * @param {Array<{featureId: string, id: string, description: string}>} [options.newACs] - New ACs to check for conflicts
488
+ * @returns {ReconnectResult}
489
+ */
490
+ function reconnect(cwd, matchResult, newPrompt, options = {}) {
491
+ const oldThread = loadThread(cwd, matchResult.threadId);
492
+ if (!oldThread) {
493
+ return {
494
+ comparison: null,
495
+ proposal: null,
496
+ conflicts: null,
497
+ error: `Thread ${matchResult.threadId} not found on disk.`,
498
+ };
499
+ }
500
+
501
+ // Step 1: Compare threads
502
+ const comparison = compareThreads(oldThread, newPrompt);
503
+
504
+ // Step 2: Propose strategy
505
+ const proposal = proposeStrategy(comparison);
506
+
507
+ // Step 3: Detect AC conflicts (if new ACs provided)
508
+ let conflicts = null;
509
+ if (options.newACs && options.newACs.length > 0) {
510
+ // @cap-todo(ac:F-081/AC-4 iter:2) Migrated to {safe: true} opt-in to preserve CLI on duplicate-ID FEATURE-MAP.
511
+ // @cap-decision(F-081/iter2) Warn on parseError; continue with partial map for read-only display.
512
+ const featureMap = readFeatureMap(cwd, undefined, { safe: true });
513
+ if (featureMap && featureMap.parseError) {
514
+ console.warn('cap: thread-synthesis — duplicate feature ID detected, conflict detection uses partial map: ' + String(featureMap.parseError.message).trim());
515
+ }
516
+ conflicts = detectACConflicts(
517
+ oldThread.featureIds || [],
518
+ options.newACs,
519
+ featureMap
520
+ );
521
+ }
522
+
523
+ return { comparison, proposal, conflicts };
524
+ }
525
+
526
+ module.exports = {
527
+ // Core analysis
528
+ compareThreads,
529
+ proposeStrategy,
530
+ detectACConflicts,
531
+ detectSingleConflict,
532
+
533
+ // Execution
534
+ executeMerge,
535
+ executeSupersede,
536
+ logResolution,
537
+
538
+ // Main entry point
539
+ reconnect,
540
+
541
+ // Constants (exposed for testing)
542
+ RESOLUTIONS_DIR,
543
+ STRATEGY_THRESHOLDS,
544
+ STRATEGIES,
545
+ };