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,52 @@
1
+ 'use strict';
2
+
3
+ // @cap-feature(feature:F-040, primary:true) Cluster display orchestrator -- thin module that wires
4
+ // the pure formatters (cap-cluster-format.cjs), helper utilities (cap-cluster-helpers.cjs), and the
5
+ // I/O layer (cap-cluster-io.cjs) into a single public surface. This is the canonical module for
6
+ // /cap:cluster and /cap:status integration.
7
+ // @cap-feature(feature:F-050) Refactored from a 696-line single file into a 4-module split:
8
+ // - cap-cluster-format.cjs : 4 public format* functions (no I/O)
9
+ // - cap-cluster-helpers.cjs : 11 pure _* helpers shared by formatters and tests
10
+ // - cap-cluster-io.cjs : disk loaders, affinity + clustering pipeline, structured diagnostics
11
+ // - cap-cluster-display.cjs : this orchestrator -- re-exports the union for backward compatibility
12
+ // @cap-decision Public API of cap-cluster-display.cjs is preserved exactly -- callers see the same 19
13
+ // exports as before refactor (verified via Object.keys diff in F-050/AC-4 acceptance gate).
14
+ // @cap-decision Re-export pattern (explicit property assignment) chosen over `Object.assign(...)` so the
15
+ // canonical export list is greppable in this file -- a future reader can verify the public surface
16
+ // without having to load three other modules.
17
+
18
+ // @cap-todo(ac:F-050/AC-1) Split into format/helpers/io/orchestrator modules; orchestrator stays under 60 lines.
19
+ // @cap-todo(ac:F-050/AC-4) Public API unchanged -- this module re-exports the union of format + helpers + io.
20
+
21
+ const format = require('./cap-cluster-format.cjs');
22
+ const helpers = require('./cap-cluster-helpers.cjs');
23
+ const io = require('./cap-cluster-io.cjs');
24
+
25
+ module.exports = {
26
+ // --- Pure formatting (re-exported from cap-cluster-format.cjs) ---
27
+ formatClusterOverview: format.formatClusterOverview,
28
+ formatClusterDetail: format.formatClusterDetail,
29
+ formatNeuralMemoryStatus: format.formatNeuralMemoryStatus,
30
+ formatRealtimeNotifications: format.formatRealtimeNotifications,
31
+
32
+ // --- I/O convenience (re-exported from cap-cluster-io.cjs) ---
33
+ loadAndFormatOverview: io.loadAndFormatOverview,
34
+ loadAndFormatDetail: io.loadAndFormatDetail,
35
+ loadAndFormatStatus: io.loadAndFormatStatus,
36
+
37
+ // --- Internal helpers (re-exported from cap-cluster-helpers.cjs for testing + backward compat) ---
38
+ _buildAffinityMap: helpers._buildAffinityMap,
39
+ _pairKey: helpers._pairKey,
40
+ _computeAvgAffinity: helpers._computeAvgAffinity,
41
+ _countDormantMembers: helpers._countDormantMembers,
42
+ _isNodeDormant: helpers._isNodeDormant,
43
+ _countAllDormantNodes: helpers._countAllDormantNodes,
44
+ _getJoinedDate: helpers._getJoinedDate,
45
+ _buildPairwiseRows: helpers._buildPairwiseRows,
46
+ _extractSharedConcepts: helpers._extractSharedConcepts,
47
+ _computeDriftStatus: helpers._computeDriftStatus,
48
+ _findHighestAffinityPair: helpers._findHighestAffinityPair,
49
+
50
+ // --- I/O internal (re-exported from cap-cluster-io.cjs for testing + backward compat) ---
51
+ _loadClusterData: io._loadClusterData,
52
+ };
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ // @cap-feature(feature:F-050) Pure formatting layer extracted from cap-cluster-display.cjs.
4
+ // This module contains only the 4 public format* functions. Internal helpers live in cap-cluster-helpers.cjs.
5
+ // All functions take data as input and return strings -- no I/O, no requires of detect/affinity/graph modules.
6
+ // @cap-feature(feature:F-040) Cluster display formatting -- overview, detail, status, realtime notifications.
7
+ // @cap-decision Helpers separated into cap-cluster-helpers.cjs to keep this file under the F-050/AC-1
8
+ // 300-line limit. Trade-off: one extra require per format function. Cost is negligible because helpers
9
+ // are loaded once at module initialization.
10
+
11
+ const helpers = require('./cap-cluster-helpers.cjs');
12
+
13
+ // --- JSDoc Typedefs ---
14
+
15
+ /**
16
+ * @typedef {Object} Cluster
17
+ * @property {string} id - Cluster ID (hash of sorted member IDs)
18
+ * @property {string[]} members - Thread IDs in this cluster
19
+ * @property {string} label - Human-readable label (e.g., "auth . session . cookies")
20
+ * @property {string} createdAt - ISO timestamp
21
+ */
22
+
23
+ // --- Pure Formatting Functions (no I/O) ---
24
+
25
+ // @cap-todo(ac:F-050/AC-1) Pure formatter module -- no I/O, no requires of detect/affinity/graph modules.
26
+ // @cap-todo(ac:F-040/AC-1) Format overview table of all clusters with labels, member counts, avg affinity, dormant count
27
+ // @cap-todo(ac:F-040/AC-7) Consistent markdown table formatting with aligned headers
28
+
29
+ /**
30
+ * Format a cluster overview table showing all detected clusters.
31
+ *
32
+ * @param {Cluster[]} clusters - Detected clusters with labels and members
33
+ * @param {Object} graph - MemoryGraph instance
34
+ * @param {Object[]} [affinityResults] - Pairwise affinity results for avg score calculation
35
+ * @returns {string} Formatted markdown overview
36
+ */
37
+ function formatClusterOverview(clusters, graph, affinityResults) {
38
+ if (!clusters || clusters.length === 0) {
39
+ return [
40
+ 'Neural Memory Clusters',
41
+ '',
42
+ 'No clusters detected. Run /cap:iterate or /cap:prototype to build thread history,',
43
+ 'then affinity scores will be computed automatically.',
44
+ ].join('\n');
45
+ }
46
+
47
+ const affinityMap = helpers._buildAffinityMap(affinityResults || []);
48
+ const rows = [];
49
+ let totalThreads = 0;
50
+ let totalDormant = 0;
51
+
52
+ for (let i = 0; i < clusters.length; i++) {
53
+ const cluster = clusters[i];
54
+ const memberCount = cluster.members.length;
55
+ const avgAffinity = helpers._computeAvgAffinity(cluster.members, affinityMap);
56
+ const dormantCount = helpers._countDormantMembers(cluster.members, graph);
57
+
58
+ totalThreads += memberCount;
59
+ totalDormant += dormantCount;
60
+
61
+ rows.push({
62
+ index: i + 1,
63
+ label: cluster.label,
64
+ memberCount,
65
+ avgAffinity,
66
+ dormantCount,
67
+ });
68
+ }
69
+
70
+ const lines = [
71
+ 'Neural Memory Clusters',
72
+ '',
73
+ '| # | Label | Members | Avg Affinity | Dormant |',
74
+ '|---|-------|---------|-------------|---------|',
75
+ ];
76
+
77
+ for (const row of rows) {
78
+ lines.push(
79
+ `| ${row.index} | ${row.label} | ${row.memberCount} threads | ${row.avgAffinity.toFixed(2)} | ${row.dormantCount} |`
80
+ );
81
+ }
82
+
83
+ lines.push('');
84
+ lines.push(`Total: ${clusters.length} clusters, ${totalThreads} threads, ${totalDormant} dormant nodes`);
85
+
86
+ return lines.join('\n');
87
+ }
88
+
89
+ // @cap-todo(ac:F-040/AC-2) Format detail view: members table, pairwise affinity, shared concepts, drift status
90
+
91
+ /**
92
+ * Format a cluster detail view with members, pairwise scores, shared concepts, drift status.
93
+ *
94
+ * @param {Cluster} cluster - The cluster to display
95
+ * @param {Object[]} affinityResults - All pairwise affinity results
96
+ * @param {Object} graph - MemoryGraph instance
97
+ * @param {Object[]} threads - All thread objects (from thread tracker)
98
+ * @returns {string} Formatted markdown detail view
99
+ */
100
+ function formatClusterDetail(cluster, affinityResults, graph, threads) {
101
+ if (!cluster) {
102
+ return 'Cluster not found. Run /cap:cluster to see available clusters.';
103
+ }
104
+
105
+ const threadMap = new Map();
106
+ for (const t of threads) {
107
+ threadMap.set(t.id, t);
108
+ }
109
+
110
+ const lines = [];
111
+
112
+ // Header
113
+ lines.push(`Cluster: ${cluster.label}`);
114
+ lines.push('');
115
+
116
+ // Members table
117
+ lines.push('Members:');
118
+ lines.push('| Thread | Name | Joined | Dormant |');
119
+ lines.push('|--------|------|--------|---------|');
120
+
121
+ for (const memberId of cluster.members) {
122
+ const thread = threadMap.get(memberId);
123
+ const name = thread ? (thread.name || thread.id) : memberId;
124
+ const joined = helpers._getJoinedDate(memberId, cluster, graph);
125
+ const isDormant = helpers._isNodeDormant(memberId, graph);
126
+ lines.push(`| ${memberId} | ${name} | ${joined} | ${isDormant ? 'yes' : 'no'} |`);
127
+ }
128
+
129
+ lines.push('');
130
+
131
+ // Pairwise affinity table
132
+ const pairRows = helpers._buildPairwiseRows(cluster.members, affinityResults);
133
+
134
+ if (pairRows.length > 0) {
135
+ lines.push('Pairwise Affinity:');
136
+ lines.push('| Thread A | Thread B | Score | Strongest Signal |');
137
+ lines.push('|----------|----------|-------|-----------------|');
138
+
139
+ for (const row of pairRows) {
140
+ lines.push(`| ${row.threadA} | ${row.threadB} | ${row.score.toFixed(2)} | ${row.strongestSignal} |`);
141
+ }
142
+
143
+ lines.push('');
144
+ }
145
+
146
+ // Shared concepts
147
+ const concepts = helpers._extractSharedConcepts(cluster.members, threads);
148
+ if (concepts.length > 0) {
149
+ lines.push(`Shared Concepts: ${concepts.join(', ')}`);
150
+ } else {
151
+ lines.push('Shared Concepts: (none detected)');
152
+ }
153
+
154
+ // Drift status
155
+ const driftStatus = helpers._computeDriftStatus(cluster.members, graph);
156
+ lines.push(`Drift Status: ${driftStatus}`);
157
+
158
+ return lines.join('\n');
159
+ }
160
+
161
+ // @cap-todo(ac:F-040/AC-3) Format Neural Memory section for /cap:status integration
162
+
163
+ /**
164
+ * Format the Neural Memory status section for /cap:status.
165
+ *
166
+ * @param {Cluster[]} clusters - Detected clusters
167
+ * @param {Object} graph - MemoryGraph instance
168
+ * @param {Object[]} [affinityResults] - Pairwise affinity results for highest pair
169
+ * @returns {string} Formatted status section
170
+ */
171
+ function formatNeuralMemoryStatus(clusters, graph, affinityResults) {
172
+ const activeClusters = (clusters || []).length;
173
+ const dormantCount = helpers._countAllDormantNodes(graph);
174
+
175
+ // Find highest affinity pair
176
+ const highestPair = helpers._findHighestAffinityPair(affinityResults || []);
177
+
178
+ // Last clustering timestamp from graph metadata
179
+ const lastClustering = (graph && graph.metadata && graph.metadata.lastClusteredAt)
180
+ ? graph.metadata.lastClusteredAt
181
+ : (graph && graph.lastUpdated) || 'never';
182
+
183
+ const lines = [
184
+ 'Neural Memory',
185
+ ` Active clusters: ${activeClusters}`,
186
+ ` Dormant nodes: ${dormantCount}`,
187
+ ];
188
+
189
+ if (highestPair) {
190
+ lines.push(
191
+ ` Highest affinity: ${highestPair.sourceThreadId} \u2194 ${highestPair.targetThreadId} (${highestPair.compositeScore.toFixed(2)}, ${highestPair.band || 'unknown'})`
192
+ );
193
+ } else {
194
+ lines.push(' Highest affinity: (no pairs computed)');
195
+ }
196
+
197
+ lines.push(` Last clustering: ${lastClustering}`);
198
+
199
+ return lines.join('\n');
200
+ }
201
+
202
+ // @cap-todo(ac:F-040/AC-4) Format realtime notifications for /cap:start passive check
203
+ // @cap-todo(ac:F-040/AC-5) Format realtime notifications for /cap:brainstorm passive check
204
+
205
+ /**
206
+ * Format realtime affinity notifications for display during /cap:start or /cap:brainstorm.
207
+ * Shows urgent and notify-band threads before session work begins.
208
+ *
209
+ * @param {Array<{band: string, text: string, threadId: string}>} notifications - Formatted notification objects
210
+ * @returns {string} Formatted notification block, or empty string if nothing to show
211
+ */
212
+ function formatRealtimeNotifications(notifications) {
213
+ if (!notifications || notifications.length === 0) return '';
214
+
215
+ const urgent = notifications.filter(n => n.band === 'urgent');
216
+ const notify = notifications.filter(n => n.band === 'notify');
217
+
218
+ if (urgent.length === 0 && notify.length === 0) return '';
219
+
220
+ const lines = ['Related Threads (Neural Memory)'];
221
+ lines.push('');
222
+
223
+ if (urgent.length > 0) {
224
+ for (const u of urgent) {
225
+ lines.push(u.text);
226
+ }
227
+ lines.push('');
228
+ }
229
+
230
+ if (notify.length > 0) {
231
+ for (const n of notify) {
232
+ lines.push(n.text);
233
+ }
234
+ lines.push('');
235
+ }
236
+
237
+ return lines.join('\n');
238
+ }
239
+
240
+ module.exports = {
241
+ formatClusterOverview,
242
+ formatClusterDetail,
243
+ formatNeuralMemoryStatus,
244
+ formatRealtimeNotifications,
245
+ };
@@ -0,0 +1,295 @@
1
+ 'use strict';
2
+
3
+ // @cap-feature(feature:F-050) Pure helper utilities for cluster display formatting.
4
+ // Extracted from the original 696-line cap-cluster-display.cjs as part of the F-050 split. These
5
+ // functions take graph/affinity/thread data as input and return primitive values (numbers, strings,
6
+ // arrays, Maps). Zero I/O. Zero requires of detect/affinity/graph/thread modules.
7
+ // @cap-decision Helpers live in their own module to keep both the formatter (cap-cluster-format.cjs)
8
+ // and the orchestrator (cap-cluster-display.cjs) under the F-050/AC-1 300-line cap.
9
+
10
+ // --- JSDoc Typedefs ---
11
+
12
+ /**
13
+ * @typedef {Object} PairwiseRow
14
+ * @property {string} threadA - Thread ID A
15
+ * @property {string} threadB - Thread ID B
16
+ * @property {number} score - Composite affinity score
17
+ * @property {string} strongestSignal - Name of strongest signal with score
18
+ */
19
+
20
+ // @cap-todo(ac:F-050/AC-1) Pure helpers in their own module to keep formatter file under 300 lines.
21
+
22
+ /**
23
+ * Build a fast lookup map: "tidA|tidB" -> AffinityResult. Higher-scoring duplicates win.
24
+ * @param {Object[]} affinityResults
25
+ * @returns {Map<string, Object>}
26
+ */
27
+ function _buildAffinityMap(affinityResults) {
28
+ const map = new Map();
29
+ for (const r of affinityResults) {
30
+ const key = _pairKey(r.sourceThreadId, r.targetThreadId);
31
+ const existing = map.get(key);
32
+ if (!existing || r.compositeScore > existing.compositeScore) {
33
+ map.set(key, r);
34
+ }
35
+ }
36
+ return map;
37
+ }
38
+
39
+ /**
40
+ * Create a canonical pair key from two thread IDs (lexicographically smaller ID first).
41
+ * @param {string} tidA
42
+ * @param {string} tidB
43
+ * @returns {string}
44
+ */
45
+ function _pairKey(tidA, tidB) {
46
+ return tidA < tidB ? `${tidA}|${tidB}` : `${tidB}|${tidA}`;
47
+ }
48
+
49
+ /**
50
+ * Compute average pairwise affinity for cluster members.
51
+ * @param {string[]} members - Thread IDs
52
+ * @param {Map<string, Object>} affinityMap
53
+ * @returns {number}
54
+ */
55
+ function _computeAvgAffinity(members, affinityMap) {
56
+ if (members.length < 2) return 0;
57
+
58
+ let total = 0;
59
+ let count = 0;
60
+
61
+ for (let i = 0; i < members.length; i++) {
62
+ for (let j = i + 1; j < members.length; j++) {
63
+ const key = _pairKey(members[i], members[j]);
64
+ const result = affinityMap.get(key);
65
+ if (result) {
66
+ total += result.compositeScore;
67
+ count++;
68
+ }
69
+ }
70
+ }
71
+
72
+ return count > 0 ? total / count : 0;
73
+ }
74
+
75
+ /**
76
+ * Count dormant members in a cluster.
77
+ * @param {string[]} members - Thread IDs
78
+ * @param {Object} graph - MemoryGraph
79
+ * @returns {number}
80
+ */
81
+ function _countDormantMembers(members, graph) {
82
+ let count = 0;
83
+ for (const threadId of members) {
84
+ if (_isNodeDormant(threadId, graph)) count++;
85
+ }
86
+ return count;
87
+ }
88
+
89
+ /**
90
+ * Check if a thread node is marked dormant in the graph.
91
+ * @param {string} threadId - Thread ID
92
+ * @param {Object} graph - MemoryGraph
93
+ * @returns {boolean}
94
+ */
95
+ function _isNodeDormant(threadId, graph) {
96
+ if (!graph || !graph.nodes) return false;
97
+
98
+ for (const node of Object.values(graph.nodes)) {
99
+ if (node.type === 'thread' && node.metadata && node.metadata.threadId === threadId) {
100
+ return node.metadata.dormant === true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Count all dormant nodes in the graph.
108
+ * @param {Object} graph - MemoryGraph
109
+ * @returns {number}
110
+ */
111
+ function _countAllDormantNodes(graph) {
112
+ if (!graph || !graph.nodes) return 0;
113
+
114
+ let count = 0;
115
+ for (const node of Object.values(graph.nodes)) {
116
+ if (node.type === 'thread' && node.metadata && node.metadata.dormant === true) {
117
+ count++;
118
+ }
119
+ }
120
+ return count;
121
+ }
122
+
123
+ /**
124
+ * Get the date a thread joined a cluster (from graph metadata or cluster creation time).
125
+ * @param {string} threadId
126
+ * @param {Object} cluster - Object with createdAt fallback
127
+ * @param {Object} graph
128
+ * @returns {string} ISO date (YYYY-MM-DD) or 'unknown'
129
+ */
130
+ function _getJoinedDate(threadId, cluster, graph) {
131
+ // Try to get join date from graph node metadata
132
+ if (graph && graph.nodes) {
133
+ for (const node of Object.values(graph.nodes)) {
134
+ if (node.type === 'thread' && node.metadata && node.metadata.threadId === threadId) {
135
+ if (node.metadata.cluster && node.metadata.cluster.joinedAt) {
136
+ return node.metadata.cluster.joinedAt.slice(0, 10);
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ // Fallback to cluster creation time
143
+ if (cluster.createdAt) {
144
+ return cluster.createdAt.slice(0, 10);
145
+ }
146
+
147
+ return 'unknown';
148
+ }
149
+
150
+ /**
151
+ * Build pairwise affinity rows for cluster members. Sorted by score descending.
152
+ * @param {string[]} members - Thread IDs
153
+ * @param {Object[]} affinityResults - All affinity results
154
+ * @returns {PairwiseRow[]}
155
+ */
156
+ function _buildPairwiseRows(members, affinityResults) {
157
+ const memberSet = new Set(members);
158
+ const rows = [];
159
+
160
+ for (const r of affinityResults) {
161
+ if (memberSet.has(r.sourceThreadId) && memberSet.has(r.targetThreadId)) {
162
+ // Find strongest signal
163
+ let strongestSignal = 'composite';
164
+ let strongestScore = r.compositeScore;
165
+
166
+ if (r.signals && r.signals.length > 0) {
167
+ const best = r.signals.reduce((a, b) => (b.score > a.score ? b : a), r.signals[0]);
168
+ strongestSignal = `${best.name} (${best.score.toFixed(2)})`;
169
+ strongestScore = r.compositeScore;
170
+ }
171
+
172
+ rows.push({
173
+ threadA: r.sourceThreadId,
174
+ threadB: r.targetThreadId,
175
+ score: strongestScore,
176
+ strongestSignal,
177
+ });
178
+ }
179
+ }
180
+
181
+ // Sort by score descending
182
+ rows.sort((a, b) => b.score - a.score);
183
+ return rows;
184
+ }
185
+
186
+ /**
187
+ * Extract shared concepts across cluster member threads using keyword overlap (>= 2 threads).
188
+ * @param {string[]} members - Thread IDs
189
+ * @param {Object[]} threads - All thread objects
190
+ * @returns {string[]} Top 10 shared concepts/keywords sorted by frequency descending
191
+ */
192
+ function _extractSharedConcepts(members, threads) {
193
+ const memberSet = new Set(members);
194
+ const memberThreads = threads.filter(t => memberSet.has(t.id));
195
+
196
+ if (memberThreads.length < 2) return [];
197
+
198
+ // Count keyword frequency across member threads
199
+ const kwFreq = new Map();
200
+ for (const t of memberThreads) {
201
+ const keywords = t.keywords || [];
202
+ for (const kw of keywords) {
203
+ kwFreq.set(kw, (kwFreq.get(kw) || 0) + 1);
204
+ }
205
+ }
206
+
207
+ // Keep keywords appearing in at least 2 member threads
208
+ const shared = [];
209
+ for (const [kw, count] of kwFreq) {
210
+ if (count >= 2) {
211
+ shared.push(kw);
212
+ }
213
+ }
214
+
215
+ // Sort by frequency descending, take top 10
216
+ shared.sort((a, b) => (kwFreq.get(b) || 0) - (kwFreq.get(a) || 0));
217
+ return shared.slice(0, 10);
218
+ }
219
+
220
+ /**
221
+ * Compute drift status for a cluster by checking affinity edge decay in the graph.
222
+ * @param {string[]} members - Thread IDs
223
+ * @param {Object} graph - MemoryGraph
224
+ * @returns {string} Human-readable drift status
225
+ */
226
+ function _computeDriftStatus(members, graph) {
227
+ if (!graph || !graph.edges || members.length < 2) {
228
+ return 'stable (insufficient data)';
229
+ }
230
+
231
+ // Find thread node IDs for members
232
+ const memberNodeIds = new Set();
233
+
234
+ for (const [nodeId, node] of Object.entries(graph.nodes || {})) {
235
+ if (node.type === 'thread' && node.metadata && node.metadata.threadId) {
236
+ if (members.includes(node.metadata.threadId)) {
237
+ memberNodeIds.add(nodeId);
238
+ }
239
+ }
240
+ }
241
+
242
+ // Check affinity edges between cluster members for decay signals
243
+ let decayedCount = 0;
244
+ let totalEdges = 0;
245
+
246
+ for (const edge of graph.edges) {
247
+ if (edge.type !== 'affinity' || !edge.active) continue;
248
+ if (memberNodeIds.has(edge.source) && memberNodeIds.has(edge.target)) {
249
+ totalEdges++;
250
+ if (edge.metadata && edge.metadata.decayApplied) {
251
+ decayedCount++;
252
+ }
253
+ }
254
+ }
255
+
256
+ if (totalEdges === 0) return 'stable (no edges)';
257
+ if (decayedCount === 0) return 'stable (no divergence detected)';
258
+
259
+ const decayRatio = decayedCount / totalEdges;
260
+ if (decayRatio > 0.5) return `diverging (${decayedCount}/${totalEdges} edges decayed)`;
261
+ if (decayRatio > 0) return `minor drift (${decayedCount}/${totalEdges} edges decayed)`;
262
+
263
+ return 'stable (no divergence detected)';
264
+ }
265
+
266
+ /**
267
+ * Find the highest-scoring affinity pair across all results.
268
+ * @param {Object[]} affinityResults
269
+ * @returns {Object|null} The highest-scoring AffinityResult, or null
270
+ */
271
+ function _findHighestAffinityPair(affinityResults) {
272
+ if (!affinityResults || affinityResults.length === 0) return null;
273
+
274
+ let best = affinityResults[0];
275
+ for (let i = 1; i < affinityResults.length; i++) {
276
+ if (affinityResults[i].compositeScore > best.compositeScore) {
277
+ best = affinityResults[i];
278
+ }
279
+ }
280
+ return best;
281
+ }
282
+
283
+ module.exports = {
284
+ _buildAffinityMap,
285
+ _pairKey,
286
+ _computeAvgAffinity,
287
+ _countDormantMembers,
288
+ _isNodeDormant,
289
+ _countAllDormantNodes,
290
+ _getJoinedDate,
291
+ _buildPairwiseRows,
292
+ _extractSharedConcepts,
293
+ _computeDriftStatus,
294
+ _findHighestAffinityPair,
295
+ };