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,80 @@
1
+ // @cap-feature(feature:F-058) Claude-Code Plugin Manifest — shared constants + helpers for plugin / marketplace / npx name handling.
2
+ // @cap-decision CAP Pro 1.0 rebrand: plugin/marketplace/npm names all unified to `cap-pro`. The
3
+ // /cap:* slash-command namespace is preserved at the file-layout level (commands/cap/*.md), not
4
+ // via the plugin name itself. Centralising the names in this module so cap-doctor, installer, and
5
+ // tests reference the same source of truth.
6
+ // @cap-constraint Zero external deps — node: built-ins only.
7
+
8
+ 'use strict';
9
+
10
+ /**
11
+ * Plugin name in `.claude-plugin/plugin.json` and the value Claude's plugin
12
+ * cache prefixes its directory entries with (`cap-pro@<source>/`).
13
+ * The /cap:* slash-command namespace is preserved by the file layout
14
+ * (commands/cap/*.md), not by this name.
15
+ */
16
+ const PLUGIN_NAME = 'cap-pro';
17
+
18
+ /** Marketplace slug — the value `/plugin install <x>` resolves against. */
19
+ const MARKETPLACE_NAME = 'cap-pro';
20
+
21
+ /** npm package name (matches MARKETPLACE_NAME deliberately — one distribution, two channels). */
22
+ const NPM_PACKAGE_NAME = 'cap-pro';
23
+
24
+ /** Legacy npm/plugin/marketplace name kept for cleanup/migration logic. Frozen at code-as-plan@7.x. */
25
+ const LEGACY_NPM_PACKAGE_NAME = 'code-as-plan';
26
+ const LEGACY_PLUGIN_NAME = 'cap';
27
+
28
+ /**
29
+ * Names reserved by Anthropic / Claude Code — MUST NOT be used as the marketplace `name` field
30
+ * per the Claude Code plugin specification. Kept as an array so consumers can `.includes()`
31
+ * or enumerate for error messages.
32
+ * @cap-decision Reserved list is hard-coded rather than fetched — the upstream list changes
33
+ * rarely and a network fetch here would couple doctor health checks to registry availability.
34
+ */
35
+ const RESERVED_MARKETPLACE_NAMES = Object.freeze([
36
+ 'claude-code-marketplace',
37
+ 'claude-code-plugins',
38
+ 'claude-plugins-official',
39
+ 'anthropic-marketplace',
40
+ 'anthropic-plugins',
41
+ 'agent-skills',
42
+ 'knowledge-work-plugins',
43
+ 'life-sciences',
44
+ ]);
45
+
46
+ const RESERVED_SET = new Set(RESERVED_MARKETPLACE_NAMES);
47
+
48
+ /**
49
+ * @param {string} name
50
+ * @returns {boolean} true if `name` is on the reserved marketplace name list
51
+ */
52
+ function isReservedMarketplaceName(name) {
53
+ return typeof name === 'string' && RESERVED_SET.has(name);
54
+ }
55
+
56
+ /**
57
+ * A `.claude-plugin/plugin.json` is a CAP footprint if its `name` matches PLUGIN_NAME
58
+ * (current — `cap-pro`) or LEGACY_PLUGIN_NAME (pre-1.0 — `cap`). Backward-compat is
59
+ * deliberate: a user who installed `code-as-plan@7.x` still has `cap` plugin manifests
60
+ * in `~/.claude/plugins/cache/`, and the doctor should detect them so the cleanup pass
61
+ * can offer to remove them. A differently-named manifest belongs to another plugin and
62
+ * must not be counted as a CAP install.
63
+ * @param {unknown} parsedManifest
64
+ * @returns {boolean}
65
+ */
66
+ function isCapPluginManifest(parsedManifest) {
67
+ if (!parsedManifest || typeof parsedManifest !== 'object') return false;
68
+ return parsedManifest.name === PLUGIN_NAME || parsedManifest.name === LEGACY_PLUGIN_NAME;
69
+ }
70
+
71
+ module.exports = {
72
+ PLUGIN_NAME,
73
+ MARKETPLACE_NAME,
74
+ NPM_PACKAGE_NAME,
75
+ LEGACY_PLUGIN_NAME,
76
+ LEGACY_NPM_PACKAGE_NAME,
77
+ RESERVED_MARKETPLACE_NAMES,
78
+ isReservedMarketplaceName,
79
+ isCapPluginManifest,
80
+ };
@@ -0,0 +1,399 @@
1
+ // @cap-feature(feature:F-039) Realtime Affinity Detection — evaluates 4 realtime signals against all existing threads during an active session, surfaces results via gradient UX bands, caches in SESSION.json
2
+ // @cap-decision I/O module — loads threads, graph, and session state to orchestrate realtime affinity detection. Pure logic (detect, format) separated from I/O (load, cache).
3
+ // @cap-decision Gradient UX — presentation scales with affinity weight: urgent gets full context block, notify gets one-liner, silent is stored only, discard is dropped entirely.
4
+ // @cap-decision Session caching under realtimeAffinity key — persists results across agent hand-offs within the same session without recomputation.
5
+ // @cap-constraint Zero external dependencies — uses only Node.js built-ins (fs, path) and sibling CAP modules.
6
+
7
+ 'use strict';
8
+
9
+ const path = require('node:path');
10
+
11
+ // --- Lazy Requires ---
12
+ // @cap-decision Lazy require pattern avoids circular dependency issues and keeps startup fast — modules loaded only when needed.
13
+
14
+ /** @returns {import('./cap-affinity-engine.cjs')} */
15
+ function _engine() { return require('./cap-affinity-engine.cjs'); }
16
+
17
+ /** @returns {import('./cap-thread-tracker.cjs')} */
18
+ function _tracker() { return require('./cap-thread-tracker.cjs'); }
19
+
20
+ /** @returns {import('./cap-memory-graph.cjs')} */
21
+ function _graph() { return require('./cap-memory-graph.cjs'); }
22
+
23
+ /** @returns {import('./cap-session.cjs')} */
24
+ function _session() { return require('./cap-session.cjs'); }
25
+
26
+ // --- Types ---
27
+
28
+ /**
29
+ * @typedef {Object} RealtimeMatch
30
+ * @property {string} threadId - Target thread ID
31
+ * @property {string} threadName - Target thread name
32
+ * @property {number} score - Composite affinity score (0.0-1.0)
33
+ * @property {string} band - Affinity band: 'urgent'|'notify'|'silent'
34
+ * @property {string} strongestSignal - Name of the highest-scoring signal
35
+ * @property {number} strongestScore - Score of the strongest signal (0.0-1.0)
36
+ * @property {string} strongestReason - Human-readable reason from the strongest signal
37
+ */
38
+
39
+ /**
40
+ * @typedef {Object} RealtimeResult
41
+ * @property {string} activeThreadId - The thread being compared against
42
+ * @property {string} computedAt - ISO timestamp of computation
43
+ * @property {RealtimeMatch[]} matches - Matches sorted by score descending (excludes discard band)
44
+ */
45
+
46
+ /**
47
+ * @typedef {Object} FormattedNotification
48
+ * @property {string} band - 'urgent'|'notify'|'silent'
49
+ * @property {string} text - Formatted display text
50
+ * @property {string} threadId - Thread ID for follow-up actions
51
+ */
52
+
53
+ // --- Core Detection ---
54
+
55
+ // @cap-todo(ac:F-039/AC-1) During an active session, evaluate 4 realtime signals against all existing threads whenever the active thread context changes
56
+ // @cap-todo(ac:F-039/AC-2) Realtime evaluation of all 4 signals against full thread index shall complete within 200ms
57
+
58
+ /**
59
+ * Detect realtime affinity between the active thread and all other threads.
60
+ * Evaluates only the 4 realtime signals (feature-id-overlap, shared-files,
61
+ * temporal-proximity, causal-chains) for speed.
62
+ *
63
+ * @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The currently active thread
64
+ * @param {import('./cap-thread-tracker.cjs').Thread[]} allThreads - All known threads (self is filtered out)
65
+ * @param {import('./cap-affinity-engine.cjs').AffinityContext} context - Graph and thread data
66
+ * @param {import('./cap-affinity-engine.cjs').AffinityConfig} [config] - Optional affinity config
67
+ * @returns {RealtimeResult}
68
+ */
69
+ function detectRealtimeAffinity(activeThread, allThreads, context, config) {
70
+ const engine = _engine();
71
+ const cfg = config || engine.loadConfig(process.cwd());
72
+ const computedAt = new Date().toISOString();
73
+ const matches = [];
74
+
75
+ for (const thread of allThreads) {
76
+ // Skip self-comparison
77
+ if (thread.id === activeThread.id) continue;
78
+
79
+ const result = engine.computeRealtimeAffinity(activeThread, thread, context, cfg);
80
+
81
+ // Discard band is not stored
82
+ if (result.band === 'discard') continue;
83
+
84
+ // Find strongest signal
85
+ const strongest = _findStrongestSignal(result.signals);
86
+
87
+ matches.push({
88
+ threadId: thread.id,
89
+ threadName: thread.name || thread.id,
90
+ score: result.compositeScore,
91
+ band: result.band,
92
+ strongestSignal: strongest.signal,
93
+ strongestScore: strongest.score,
94
+ strongestReason: strongest.reason,
95
+ });
96
+ }
97
+
98
+ // Sort by score descending
99
+ matches.sort((a, b) => b.score - a.score);
100
+
101
+ return {
102
+ activeThreadId: activeThread.id,
103
+ computedAt,
104
+ matches,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Find the highest-scoring signal from a signal results array.
110
+ * @param {import('./cap-affinity-engine.cjs').SignalResult[]} signals
111
+ * @returns {import('./cap-affinity-engine.cjs').SignalResult}
112
+ */
113
+ function _findStrongestSignal(signals) {
114
+ if (!signals || signals.length === 0) {
115
+ return { signal: 'unknown', score: 0, reason: 'No signals computed' };
116
+ }
117
+
118
+ let best = signals[0];
119
+ for (let i = 1; i < signals.length; i++) {
120
+ if (signals[i].score > best.score) {
121
+ best = signals[i];
122
+ }
123
+ }
124
+ return best;
125
+ }
126
+
127
+ // --- Formatting ---
128
+
129
+ // @cap-todo(ac:F-039/AC-3) Threads scoring in urgent band (>=0.90) surfaced as full context block with thread name, strongest signal, and load-context offer
130
+
131
+ /**
132
+ * Format an urgent-band match as a full context block.
133
+ * @param {RealtimeMatch} match
134
+ * @returns {string}
135
+ */
136
+ function formatUrgentBlock(match) {
137
+ return [
138
+ '',
139
+ '--- Strongly related thread found ---',
140
+ `Thread: "${match.threadName}" (${match.threadId})`,
141
+ `Strongest signal: ${match.strongestSignal} (${match.strongestScore.toFixed(2)}) — ${match.strongestReason}`,
142
+ '',
143
+ 'Load this thread\'s context? [yes/no]',
144
+ '',
145
+ ].join('\n');
146
+ }
147
+
148
+ // @cap-todo(ac:F-039/AC-4) Threads scoring in notify band (0.75-0.89) surfaced as compact single-line notification
149
+
150
+ /**
151
+ * Format a notify-band match as a compact one-liner.
152
+ * @param {RealtimeMatch} match
153
+ * @returns {string}
154
+ */
155
+ function formatNotifyLine(match) {
156
+ return `Related: "${match.threadName}" — ${match.strongestSignal} (${match.strongestScore.toFixed(2)})`;
157
+ }
158
+
159
+ // @cap-todo(ac:F-039/AC-5) Threads scoring in silent band (0.40-0.74) produce no visible output — only queryable via /cap:status
160
+
161
+ /**
162
+ * Format all results according to their band.
163
+ * Silent-band matches are included in the output array but marked as silent
164
+ * so callers can choose whether to display them.
165
+ *
166
+ * @param {RealtimeResult} result - Detection result
167
+ * @returns {FormattedNotification[]}
168
+ */
169
+ function formatResults(result) {
170
+ const notifications = [];
171
+
172
+ for (const match of result.matches) {
173
+ switch (match.band) {
174
+ case 'urgent':
175
+ notifications.push({
176
+ band: 'urgent',
177
+ text: formatUrgentBlock(match),
178
+ threadId: match.threadId,
179
+ });
180
+ break;
181
+ case 'notify':
182
+ notifications.push({
183
+ band: 'notify',
184
+ text: formatNotifyLine(match),
185
+ threadId: match.threadId,
186
+ });
187
+ break;
188
+ case 'silent':
189
+ // Silent: no visible output, but included for queryability
190
+ notifications.push({
191
+ band: 'silent',
192
+ text: '',
193
+ threadId: match.threadId,
194
+ });
195
+ break;
196
+ // discard: never reaches here (filtered in detectRealtimeAffinity)
197
+ }
198
+ }
199
+
200
+ return notifications;
201
+ }
202
+
203
+ // --- Session Cache ---
204
+
205
+ // @cap-todo(ac:F-039/AC-7) Realtime affinity results cached in SESSION.json under key realtimeAffinity so they persist across agent hand-offs
206
+
207
+ /**
208
+ * Cache realtime affinity results in SESSION.json under the realtimeAffinity key.
209
+ * @param {string} cwd - Project root directory
210
+ * @param {RealtimeResult} result - Detection results to cache
211
+ */
212
+ function cacheResults(cwd, result) {
213
+ const session = _session();
214
+ session.updateSession(cwd, {
215
+ realtimeAffinity: {
216
+ activeThreadId: result.activeThreadId,
217
+ computedAt: result.computedAt,
218
+ results: result.matches.map(m => ({
219
+ threadId: m.threadId,
220
+ threadName: m.threadName,
221
+ score: m.score,
222
+ band: m.band,
223
+ strongestSignal: m.strongestSignal,
224
+ strongestScore: m.strongestScore,
225
+ strongestReason: m.strongestReason,
226
+ })),
227
+ },
228
+ });
229
+ }
230
+
231
+ /**
232
+ * Load cached realtime affinity results from SESSION.json.
233
+ * @param {string} cwd - Project root directory
234
+ * @returns {RealtimeResult|null} Cached result or null if not present
235
+ */
236
+ function loadCachedResults(cwd) {
237
+ const session = _session();
238
+ const data = session.loadSession(cwd);
239
+ const cached = data.realtimeAffinity;
240
+
241
+ if (!cached || !cached.activeThreadId) return null;
242
+
243
+ return {
244
+ activeThreadId: cached.activeThreadId,
245
+ computedAt: cached.computedAt,
246
+ matches: (cached.results || []).map(r => ({
247
+ threadId: r.threadId,
248
+ threadName: r.threadName,
249
+ score: r.score,
250
+ band: r.band,
251
+ strongestSignal: r.strongestSignal,
252
+ strongestScore: r.strongestScore,
253
+ strongestReason: r.strongestReason,
254
+ })),
255
+ };
256
+ }
257
+
258
+ /**
259
+ * Clear cached realtime affinity results from SESSION.json.
260
+ * @param {string} cwd - Project root directory
261
+ */
262
+ function clearCache(cwd) {
263
+ const session = _session();
264
+ session.updateSession(cwd, { realtimeAffinity: null });
265
+ }
266
+
267
+ // --- Integration Hooks ---
268
+
269
+ // @cap-todo(ac:F-039/AC-6) Realtime detector integrates with cap-brainstormer at session start and with cap-thread-tracker when thread context is updated
270
+
271
+ /**
272
+ * Called by cap-brainstormer at session start.
273
+ * Loads all threads, runs realtime affinity detection, caches results,
274
+ * and returns formatted notifications for display.
275
+ *
276
+ * @param {string} cwd - Project root directory
277
+ * @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The active thread for this session
278
+ * @returns {FormattedNotification[]}
279
+ */
280
+ function onSessionStart(cwd, activeThread) {
281
+ const { allThreads, context, config } = _loadDetectionContext(cwd);
282
+
283
+ const result = detectRealtimeAffinity(activeThread, allThreads, context, config);
284
+ cacheResults(cwd, result);
285
+ return formatResults(result);
286
+ }
287
+
288
+ /**
289
+ * Called when thread context changes mid-session (e.g., user switches threads).
290
+ * If the active thread has not changed, returns cached results.
291
+ * Otherwise, recomputes and caches fresh results.
292
+ *
293
+ * @param {string} cwd - Project root directory
294
+ * @param {import('./cap-thread-tracker.cjs').Thread} activeThread - The new active thread
295
+ * @returns {FormattedNotification[]}
296
+ */
297
+ function onThreadContextChange(cwd, activeThread) {
298
+ // Check if we can reuse cached results
299
+ const cached = loadCachedResults(cwd);
300
+ if (cached && cached.activeThreadId === activeThread.id) {
301
+ return formatResults(cached);
302
+ }
303
+
304
+ // Recompute
305
+ return onSessionStart(cwd, activeThread);
306
+ }
307
+
308
+ /**
309
+ * Load all data needed for realtime affinity detection.
310
+ * Consolidates thread index loading, thread data loading, graph loading,
311
+ * and config loading into a single helper.
312
+ *
313
+ * @param {string} cwd - Project root directory
314
+ * @returns {{ allThreads: import('./cap-thread-tracker.cjs').Thread[], context: import('./cap-affinity-engine.cjs').AffinityContext, config: import('./cap-affinity-engine.cjs').AffinityConfig }}
315
+ */
316
+ function _loadDetectionContext(cwd) {
317
+ const tracker = _tracker();
318
+ const graphMod = _graph();
319
+ const engine = _engine();
320
+
321
+ const index = tracker.loadIndex(cwd);
322
+ const graph = graphMod.loadGraph(cwd);
323
+ const config = engine.loadConfig(cwd);
324
+
325
+ // Load full thread data for each entry in the index
326
+ const allThreads = [];
327
+ for (const entry of index.threads || []) {
328
+ const thread = tracker.loadThread(cwd, entry.id);
329
+ if (thread) {
330
+ allThreads.push(thread);
331
+ }
332
+ }
333
+
334
+ const context = {
335
+ graph,
336
+ allThreads,
337
+ threadIndex: index.threads || [],
338
+ };
339
+
340
+ return { allThreads, context, config };
341
+ }
342
+
343
+ // --- Query ---
344
+
345
+ /**
346
+ * Return cached silent-band matches for /cap:status display.
347
+ * Silent matches produce no visible output during normal flow but
348
+ * are accessible on demand.
349
+ *
350
+ * @param {string} cwd - Project root directory
351
+ * @returns {RealtimeMatch[]} Silent-band matches, or empty array if no cache
352
+ */
353
+ function getSilentMatches(cwd) {
354
+ const cached = loadCachedResults(cwd);
355
+ if (!cached) return [];
356
+ return cached.matches.filter(m => m.band === 'silent');
357
+ }
358
+
359
+ /**
360
+ * Return all cached matches regardless of band, for diagnostic use.
361
+ * @param {string} cwd - Project root directory
362
+ * @returns {RealtimeMatch[]} All cached matches, or empty array
363
+ */
364
+ function getAllCachedMatches(cwd) {
365
+ const cached = loadCachedResults(cwd);
366
+ if (!cached) return [];
367
+ return cached.matches;
368
+ }
369
+
370
+ // --- Module Exports ---
371
+
372
+ // @cap-decision Exporting internal helpers prefixed with _ for testing, following project convention.
373
+
374
+ module.exports = {
375
+ // Core detection
376
+ detectRealtimeAffinity,
377
+
378
+ // Formatting
379
+ formatUrgentBlock,
380
+ formatNotifyLine,
381
+ formatResults,
382
+
383
+ // Session cache
384
+ cacheResults,
385
+ loadCachedResults,
386
+ clearCache,
387
+
388
+ // Integration hooks
389
+ onSessionStart,
390
+ onThreadContextChange,
391
+
392
+ // Query
393
+ getSilentMatches,
394
+ getAllCachedMatches,
395
+
396
+ // Internal (for testing)
397
+ _findStrongestSignal,
398
+ _loadDetectionContext,
399
+ };