claude-code-workflow 6.1.4 → 6.2.2

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 (437) hide show
  1. package/.claude/CLAUDE.md +10 -0
  2. package/.claude/agents/action-planning-agent.md +857 -778
  3. package/.claude/agents/cli-execution-agent.md +266 -269
  4. package/.claude/agents/cli-explore-agent.md +2 -2
  5. package/.claude/agents/cli-lite-planning-agent.md +142 -92
  6. package/.claude/agents/cli-planning-agent.md +4 -4
  7. package/.claude/agents/code-developer.md +7 -6
  8. package/.claude/agents/conceptual-planning-agent.md +2 -2
  9. package/.claude/agents/context-search-agent.md +31 -32
  10. package/.claude/agents/doc-generator.md +4 -4
  11. package/.claude/agents/memory-bridge.md +93 -93
  12. package/.claude/agents/test-context-search-agent.md +8 -7
  13. package/.claude/agents/test-fix-agent.md +7 -6
  14. package/.claude/commands/clean.md +516 -0
  15. package/.claude/commands/memory/compact.md +383 -0
  16. package/.claude/commands/memory/docs-full-cli.md +471 -471
  17. package/.claude/commands/memory/docs-related-cli.md +386 -386
  18. package/.claude/commands/memory/docs.md +615 -615
  19. package/.claude/commands/memory/load.md +5 -5
  20. package/.claude/commands/memory/tech-research-rules.md +310 -0
  21. package/.claude/commands/memory/update-full.md +332 -332
  22. package/.claude/commands/memory/workflow-skill-memory.md +4 -4
  23. package/.claude/commands/task/create.md +151 -151
  24. package/.claude/commands/version.md +254 -254
  25. package/.claude/commands/workflow/brainstorm/api-designer.md +587 -585
  26. package/.claude/commands/workflow/brainstorm/artifacts.md +1 -0
  27. package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -443
  28. package/.claude/commands/workflow/brainstorm/data-architect.md +220 -220
  29. package/.claude/commands/workflow/brainstorm/product-manager.md +200 -200
  30. package/.claude/commands/workflow/brainstorm/product-owner.md +200 -200
  31. package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -200
  32. package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -200
  33. package/.claude/commands/workflow/brainstorm/system-architect.md +389 -387
  34. package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -221
  35. package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -221
  36. package/.claude/commands/workflow/debug.md +321 -0
  37. package/.claude/commands/workflow/execute.md +13 -0
  38. package/.claude/commands/workflow/init.md +165 -164
  39. package/.claude/commands/workflow/lite-execute.md +119 -13
  40. package/.claude/commands/workflow/lite-fix.md +623 -621
  41. package/.claude/commands/workflow/lite-plan.md +610 -592
  42. package/.claude/commands/workflow/plan.md +5 -5
  43. package/.claude/commands/workflow/review-module-cycle.md +2 -0
  44. package/.claude/commands/workflow/review-session-cycle.md +2 -0
  45. package/.claude/commands/workflow/review.md +297 -291
  46. package/.claude/commands/workflow/session/complete.md +153 -500
  47. package/.claude/commands/workflow/session/list.md +95 -95
  48. package/.claude/commands/workflow/session/resume.md +60 -60
  49. package/.claude/commands/workflow/session/start.md +199 -199
  50. package/.claude/commands/workflow/tdd-plan.md +3 -3
  51. package/.claude/commands/workflow/tdd-verify.md +23 -9
  52. package/.claude/commands/workflow/test-cycle-execute.md +2 -0
  53. package/.claude/commands/workflow/test-fix-gen.md +699 -699
  54. package/.claude/commands/workflow/tools/conflict-resolution.md +104 -18
  55. package/.claude/commands/workflow/tools/context-gather.md +436 -434
  56. package/.claude/commands/workflow/tools/task-generate-agent.md +490 -291
  57. package/.claude/commands/workflow/tools/task-generate-tdd.md +18 -10
  58. package/.claude/commands/workflow/tools/test-concept-enhanced.md +2 -1
  59. package/.claude/commands/workflow/tools/test-context-gather.md +1 -0
  60. package/.claude/commands/workflow/tools/test-task-generate.md +1 -0
  61. package/.claude/commands/workflow/ui-design/import-from-code.md +9 -6
  62. package/.claude/skills/command-guide/SKILL.md +5 -5
  63. package/.claude/skills/command-guide/index/all-commands.json +1 -1
  64. package/.claude/skills/command-guide/index/by-category.json +1 -1
  65. package/.claude/skills/command-guide/index/by-use-case.json +1 -1
  66. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +857 -778
  67. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +266 -269
  68. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +2 -2
  69. package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +142 -92
  70. package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +4 -4
  71. package/.claude/skills/command-guide/reference/agents/code-developer.md +7 -6
  72. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +2 -2
  73. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +31 -32
  74. package/.claude/skills/command-guide/reference/agents/doc-generator.md +4 -4
  75. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
  76. package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +8 -7
  77. package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +7 -6
  78. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
  79. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
  80. package/.claude/skills/command-guide/reference/commands/memory/docs.md +17 -16
  81. package/.claude/skills/command-guide/reference/commands/memory/load.md +5 -5
  82. package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +194 -357
  83. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
  84. package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +4 -4
  85. package/.claude/skills/command-guide/reference/commands/task/create.md +151 -151
  86. package/.claude/skills/command-guide/reference/commands/version.md +254 -254
  87. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -585
  88. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +443 -443
  89. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -220
  90. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -200
  91. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -200
  92. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -200
  93. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -200
  94. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -387
  95. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -221
  96. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -221
  97. package/.claude/skills/command-guide/reference/commands/workflow/execute.md +25 -20
  98. package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -164
  99. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +748 -686
  100. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +664 -621
  101. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +645 -592
  102. package/.claude/skills/command-guide/reference/commands/workflow/plan.md +5 -5
  103. package/.claude/skills/command-guide/reference/commands/workflow/review.md +25 -18
  104. package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +547 -500
  105. package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +45 -27
  106. package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +35 -19
  107. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +90 -33
  108. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +3 -3
  109. package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +23 -9
  110. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -699
  111. package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +103 -17
  112. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
  113. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +487 -291
  114. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +17 -10
  115. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +1 -1
  116. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +6 -6
  117. package/.claude/workflows/chinese-response.md +38 -0
  118. package/.claude/workflows/cli-templates/prompts/rules/rule-api.txt +122 -0
  119. package/.claude/workflows/cli-templates/prompts/rules/rule-components.txt +122 -0
  120. package/.claude/workflows/cli-templates/prompts/rules/rule-config.txt +89 -0
  121. package/.claude/workflows/cli-templates/prompts/rules/rule-core.txt +60 -0
  122. package/.claude/workflows/cli-templates/prompts/rules/rule-patterns.txt +70 -0
  123. package/.claude/workflows/cli-templates/prompts/rules/rule-testing.txt +81 -0
  124. package/.claude/workflows/cli-templates/prompts/rules/tech-rules-agent-prompt.txt +89 -0
  125. package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -131
  126. package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +5 -9
  127. package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +5 -9
  128. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +112 -0
  129. package/.claude/workflows/cli-templates/protocols/write-protocol.md +201 -0
  130. package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +137 -0
  131. package/.claude/workflows/cli-templates/schemas/debug-log-json-schema.json +127 -0
  132. package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +25 -0
  133. package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +25 -0
  134. package/.claude/workflows/cli-tools-usage.md +526 -0
  135. package/{CLAUDE.md → .claude/workflows/coding-philosophy.md} +24 -45
  136. package/.claude/workflows/context-tools.md +84 -0
  137. package/.claude/workflows/file-modification.md +64 -0
  138. package/.claude/workflows/tool-strategy.md +216 -79
  139. package/.claude/workflows/windows-platform.md +16 -0
  140. package/.claude/workflows/workflow-architecture.md +942 -942
  141. package/.codex/AGENTS.md +63 -330
  142. package/.codex/prompts/debug.md +318 -0
  143. package/.codex/prompts/execute.md +273 -0
  144. package/.codex/prompts/lite-execute.md +164 -0
  145. package/.codex/prompts/lite-plan.md +469 -0
  146. package/.codex/prompts.zip +0 -0
  147. package/.gemini/GEMINI.md +25 -164
  148. package/.qwen/QWEN.md +0 -139
  149. package/README.md +29 -9
  150. package/ccw/README.md +30 -6
  151. package/ccw/bin/ccw-mcp.js +7 -0
  152. package/ccw/bin/ccw.js +9 -9
  153. package/ccw/package.json +65 -47
  154. package/ccw/src/.workflow/.cli-history/history.db +0 -0
  155. package/ccw/src/.workflow/.cli-history/history.db-shm +0 -0
  156. package/ccw/src/.workflow/.cli-history/history.db-wal +0 -0
  157. package/ccw/src/cli.ts +244 -0
  158. package/ccw/src/commands/cli.ts +740 -0
  159. package/ccw/src/commands/core-memory.ts +770 -0
  160. package/ccw/src/commands/hook.ts +315 -0
  161. package/ccw/src/commands/install.ts +519 -0
  162. package/ccw/src/commands/{list.js → list.ts} +1 -1
  163. package/ccw/src/commands/memory.ts +1090 -0
  164. package/ccw/src/commands/{serve.js → serve.ts} +14 -5
  165. package/ccw/src/commands/session-path-resolver.ts +372 -0
  166. package/ccw/src/commands/session.ts +1141 -0
  167. package/ccw/src/commands/{stop.js → stop.ts} +16 -6
  168. package/ccw/src/commands/tool.ts +201 -0
  169. package/ccw/src/commands/{uninstall.js → uninstall.ts} +89 -40
  170. package/ccw/src/commands/{upgrade.js → upgrade.ts} +68 -23
  171. package/ccw/src/commands/{view.js → view.ts} +22 -8
  172. package/ccw/src/config/storage-paths.ts +670 -0
  173. package/ccw/src/core/cache-manager.ts +294 -0
  174. package/ccw/src/core/claude-freshness.ts +319 -0
  175. package/ccw/src/core/core-memory-store.ts +1528 -0
  176. package/ccw/src/core/{dashboard-generator-patch.js → dashboard-generator-patch.ts} +18 -0
  177. package/ccw/src/core/{dashboard-generator.js → dashboard-generator.ts} +69 -12
  178. package/ccw/src/core/data-aggregator.ts +584 -0
  179. package/ccw/src/core/history-importer.ts +625 -0
  180. package/ccw/src/core/{lite-scanner.js → lite-scanner-complete.ts} +162 -66
  181. package/ccw/src/core/lite-scanner.ts +469 -0
  182. package/ccw/src/core/{manifest.js → manifest.ts} +104 -34
  183. package/ccw/src/core/memory-embedder-bridge.ts +262 -0
  184. package/ccw/src/core/memory-store.ts +978 -0
  185. package/ccw/src/core/routes/ccw-routes.ts +96 -0
  186. package/ccw/src/core/routes/claude-routes.ts +1183 -0
  187. package/ccw/src/core/routes/cli-routes.ts +561 -0
  188. package/ccw/src/core/routes/codexlens-routes.ts +806 -0
  189. package/ccw/src/core/routes/core-memory-routes.ts +605 -0
  190. package/ccw/src/core/routes/files-routes.ts +428 -0
  191. package/ccw/src/core/routes/graph-routes.md +164 -0
  192. package/ccw/src/core/routes/graph-routes.ts +626 -0
  193. package/ccw/src/core/routes/help-routes.ts +308 -0
  194. package/ccw/src/core/routes/hooks-routes.ts +405 -0
  195. package/ccw/src/core/routes/mcp-routes.ts +1271 -0
  196. package/ccw/src/core/routes/mcp-routes.ts.backup +550 -0
  197. package/ccw/src/core/routes/mcp-templates-db.ts +268 -0
  198. package/ccw/src/core/routes/memory-routes.ts +1206 -0
  199. package/ccw/src/core/routes/rules-routes.ts +526 -0
  200. package/ccw/src/core/routes/session-routes.ts +467 -0
  201. package/ccw/src/core/routes/skills-routes.ts +599 -0
  202. package/ccw/src/core/routes/status-routes.ts +57 -0
  203. package/ccw/src/core/routes/system-routes.ts +427 -0
  204. package/ccw/src/core/server.ts +431 -0
  205. package/ccw/src/core/session-clustering-service.ts +1258 -0
  206. package/ccw/src/core/session-scanner.ts +283 -0
  207. package/ccw/src/core/websocket.ts +190 -0
  208. package/ccw/src/{index.js → index.ts} +1 -0
  209. package/ccw/src/mcp-server/index.ts +186 -0
  210. package/ccw/src/templates/assets/css/github-dark.min.css +10 -0
  211. package/ccw/src/templates/assets/css/github.min.css +10 -0
  212. package/ccw/src/templates/assets/js/cytoscape.min.js +32 -0
  213. package/ccw/src/templates/assets/js/d3.min.js +2 -0
  214. package/ccw/src/templates/assets/js/highlight.min.js +1244 -0
  215. package/ccw/src/templates/assets/js/lucide.min.js +12 -0
  216. package/ccw/src/templates/assets/js/marked.min.js +69 -0
  217. package/ccw/src/templates/assets/js/tailwind.js +83 -0
  218. package/ccw/src/templates/dashboard-css/01-base.css +11 -0
  219. package/ccw/src/templates/dashboard-css/02-session.css +22 -0
  220. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +10 -0
  221. package/ccw/src/templates/dashboard-css/06-cards.css +10 -4
  222. package/ccw/src/templates/dashboard-css/07-managers.css +1178 -7
  223. package/ccw/src/templates/dashboard-css/09-explorer.css +23 -12
  224. package/ccw/src/templates/dashboard-css/10-cli-status.css +337 -0
  225. package/ccw/src/templates/dashboard-css/11-cli-history.css +271 -0
  226. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +796 -0
  227. package/ccw/src/templates/dashboard-css/13-cli-ccw.css +199 -0
  228. package/ccw/src/templates/dashboard-css/14-cli-modals.css +258 -0
  229. package/ccw/src/templates/dashboard-css/15-cli-endpoints.css +305 -0
  230. package/ccw/src/templates/dashboard-css/16-cli-session.css +241 -0
  231. package/ccw/src/templates/dashboard-css/17-cli-conversation.css +283 -0
  232. package/ccw/src/templates/dashboard-css/18-cli-settings.css +160 -0
  233. package/ccw/src/templates/dashboard-css/19-cli-native-session.css +496 -0
  234. package/ccw/src/templates/dashboard-css/20-cli-taskqueue.css +188 -0
  235. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +310 -0
  236. package/ccw/src/templates/dashboard-css/22-cli-semantic.css +240 -0
  237. package/ccw/src/templates/dashboard-css/23-memory.css +2390 -0
  238. package/ccw/src/templates/dashboard-css/24-prompt-history.css +1089 -0
  239. package/ccw/src/templates/dashboard-css/25-skills-rules.css +326 -0
  240. package/ccw/src/templates/dashboard-css/26-claude-manager.css +908 -0
  241. package/ccw/src/templates/dashboard-css/27-graph-explorer.css +1678 -0
  242. package/ccw/src/templates/dashboard-css/28-mcp-manager.css +748 -0
  243. package/ccw/src/templates/dashboard-css/29-help.css +264 -0
  244. package/ccw/src/templates/dashboard-css/30-core-memory.css +1700 -0
  245. package/ccw/src/templates/dashboard-js/api.js +162 -142
  246. package/ccw/src/templates/dashboard-js/components/carousel.js +4 -4
  247. package/ccw/src/templates/dashboard-js/components/cli-history.js +876 -0
  248. package/ccw/src/templates/dashboard-js/components/cli-status.js +978 -0
  249. package/ccw/src/templates/dashboard-js/components/global-notifications.js +508 -219
  250. package/ccw/src/templates/dashboard-js/components/hook-manager.js +1277 -282
  251. package/ccw/src/templates/dashboard-js/components/index-manager.js +302 -0
  252. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +718 -27
  253. package/ccw/src/templates/dashboard-js/components/modals.js +66 -0
  254. package/ccw/src/templates/dashboard-js/components/navigation.js +80 -12
  255. package/ccw/src/templates/dashboard-js/components/notifications.js +758 -194
  256. package/ccw/src/templates/dashboard-js/components/storage-manager.js +478 -0
  257. package/ccw/src/templates/dashboard-js/components/tabs-other.js +157 -6
  258. package/ccw/src/templates/dashboard-js/components/task-queue-sidebar.js +716 -0
  259. package/ccw/src/templates/dashboard-js/help-i18n.js +272 -0
  260. package/ccw/src/templates/dashboard-js/i18n.js +2807 -0
  261. package/ccw/src/templates/dashboard-js/main.js +15 -0
  262. package/ccw/src/templates/dashboard-js/state.js +243 -42
  263. package/ccw/src/templates/dashboard-js/utils.js +47 -1
  264. package/ccw/src/templates/dashboard-js/views/claude-manager.js +912 -0
  265. package/ccw/src/templates/dashboard-js/views/cli-manager.js +2272 -0
  266. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +964 -0
  267. package/ccw/src/templates/dashboard-js/views/core-memory-clusters.js +503 -0
  268. package/ccw/src/templates/dashboard-js/views/core-memory.js +782 -0
  269. package/ccw/src/templates/dashboard-js/views/explorer.js +888 -852
  270. package/ccw/src/templates/dashboard-js/views/graph-explorer.js +1157 -0
  271. package/ccw/src/templates/dashboard-js/views/help.js +856 -0
  272. package/ccw/src/templates/dashboard-js/views/history.js +337 -0
  273. package/ccw/src/templates/dashboard-js/views/home.js +61 -15
  274. package/ccw/src/templates/dashboard-js/views/hook-manager.js +311 -43
  275. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +204 -28
  276. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +2187 -411
  277. package/ccw/src/templates/dashboard-js/views/mcp-manager.js.backup +1729 -0
  278. package/ccw/src/templates/dashboard-js/views/mcp-manager.js.new +928 -0
  279. package/ccw/src/templates/dashboard-js/views/memory.js +1221 -0
  280. package/ccw/src/templates/dashboard-js/views/prompt-history.js +713 -0
  281. package/ccw/src/templates/dashboard-js/views/rules-manager.js +828 -0
  282. package/ccw/src/templates/dashboard-js/views/session-detail.js +54 -53
  283. package/ccw/src/templates/dashboard-js/views/skills-manager.js +819 -0
  284. package/ccw/src/templates/dashboard.html +185 -85
  285. package/ccw/src/templates/hooks-config-example.json +60 -0
  286. package/ccw/src/tools/classify-folders.ts +245 -0
  287. package/ccw/src/tools/cli-config-manager.ts +268 -0
  288. package/ccw/src/tools/cli-executor.ts +2014 -0
  289. package/ccw/src/tools/cli-history-store.ts +1195 -0
  290. package/ccw/src/tools/codex-lens.ts +1141 -0
  291. package/ccw/src/tools/{convert-tokens-to-css.js → convert-tokens-to-css.ts} +73 -23
  292. package/ccw/src/tools/core-memory.ts +444 -0
  293. package/ccw/src/tools/detect-changed-modules.ts +325 -0
  294. package/ccw/src/tools/{discover-design-files.js → discover-design-files.ts} +74 -24
  295. package/ccw/src/tools/edit-file.ts +568 -0
  296. package/ccw/src/tools/{generate-module-docs.js → generate-module-docs.ts} +207 -185
  297. package/ccw/src/tools/{get-modules-by-depth.js → get-modules-by-depth.ts} +120 -79
  298. package/ccw/src/tools/index.ts +370 -0
  299. package/ccw/src/tools/native-session-discovery.ts +795 -0
  300. package/ccw/src/tools/notifier.ts +129 -0
  301. package/ccw/src/tools/read-file.ts +410 -0
  302. package/ccw/src/tools/resume-strategy.ts +345 -0
  303. package/ccw/src/tools/session-content-parser.ts +619 -0
  304. package/ccw/src/tools/session-manager.ts +1026 -0
  305. package/ccw/src/tools/smart-context.ts +228 -0
  306. package/ccw/src/tools/smart-search.ts +2065 -0
  307. package/ccw/src/tools/smart-search.ts.backup +1233 -0
  308. package/ccw/src/tools/storage-manager.ts +455 -0
  309. package/ccw/src/tools/write-file.ts +222 -0
  310. package/ccw/src/types/config.ts +11 -0
  311. package/ccw/src/types/index.ts +3 -0
  312. package/ccw/src/types/session.ts +25 -0
  313. package/ccw/src/types/tool.ts +41 -0
  314. package/ccw/src/utils/{browser-launcher.js → browser-launcher.ts} +10 -8
  315. package/ccw/src/utils/file-utils.ts +48 -0
  316. package/ccw/src/utils/{path-resolver.js → path-resolver.ts} +114 -78
  317. package/ccw/src/utils/path-validator.ts +153 -0
  318. package/ccw/src/utils/{ui.js → ui.ts} +32 -25
  319. package/codex-lens/pyproject.toml +48 -0
  320. package/codex-lens/src/codexlens/.workflow/.cli-history/history.db +0 -0
  321. package/codex-lens/src/codexlens/__init__.py +28 -0
  322. package/codex-lens/src/codexlens/__main__.py +14 -0
  323. package/codex-lens/src/codexlens/__pycache__/__init__.cpython-313.pyc +0 -0
  324. package/codex-lens/src/codexlens/__pycache__/__main__.cpython-313.pyc +0 -0
  325. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  326. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  327. package/codex-lens/src/codexlens/__pycache__/errors.cpython-313.pyc +0 -0
  328. package/codex-lens/src/codexlens/cli/__init__.py +27 -0
  329. package/codex-lens/src/codexlens/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  330. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  331. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  332. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  333. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  334. package/codex-lens/src/codexlens/cli/commands.py +1931 -0
  335. package/codex-lens/src/codexlens/cli/embedding_manager.py +620 -0
  336. package/codex-lens/src/codexlens/cli/model_manager.py +289 -0
  337. package/codex-lens/src/codexlens/cli/output.py +124 -0
  338. package/codex-lens/src/codexlens/config.py +201 -0
  339. package/codex-lens/src/codexlens/entities.py +121 -0
  340. package/codex-lens/src/codexlens/errors.py +55 -0
  341. package/codex-lens/src/codexlens/indexing/README.md +77 -0
  342. package/codex-lens/src/codexlens/indexing/__init__.py +4 -0
  343. package/codex-lens/src/codexlens/indexing/__pycache__/__init__.cpython-313.pyc +0 -0
  344. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  345. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -0
  346. package/codex-lens/src/codexlens/parsers/__init__.py +8 -0
  347. package/codex-lens/src/codexlens/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  348. package/codex-lens/src/codexlens/parsers/__pycache__/encoding.cpython-313.pyc +0 -0
  349. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  350. package/codex-lens/src/codexlens/parsers/__pycache__/tokenizer.cpython-313.pyc +0 -0
  351. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  352. package/codex-lens/src/codexlens/parsers/encoding.py +202 -0
  353. package/codex-lens/src/codexlens/parsers/factory.py +256 -0
  354. package/codex-lens/src/codexlens/parsers/tokenizer.py +98 -0
  355. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -0
  356. package/codex-lens/src/codexlens/search/__init__.py +15 -0
  357. package/codex-lens/src/codexlens/search/__pycache__/__init__.cpython-313.pyc +0 -0
  358. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  359. package/codex-lens/src/codexlens/search/__pycache__/enrichment.cpython-313.pyc +0 -0
  360. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  361. package/codex-lens/src/codexlens/search/__pycache__/query_parser.cpython-313.pyc +0 -0
  362. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  363. package/codex-lens/src/codexlens/search/chain_search.py +647 -0
  364. package/codex-lens/src/codexlens/search/enrichment.py +150 -0
  365. package/codex-lens/src/codexlens/search/hybrid_search.py +313 -0
  366. package/codex-lens/src/codexlens/search/query_parser.py +242 -0
  367. package/codex-lens/src/codexlens/search/ranking.py +274 -0
  368. package/codex-lens/src/codexlens/semantic/__init__.py +39 -0
  369. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  370. package/codex-lens/src/codexlens/semantic/__pycache__/ann_index.cpython-313.pyc +0 -0
  371. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  372. package/codex-lens/src/codexlens/semantic/__pycache__/code_extractor.cpython-313.pyc +0 -0
  373. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  374. package/codex-lens/src/codexlens/semantic/__pycache__/graph_analyzer.cpython-313.pyc +0 -0
  375. package/codex-lens/src/codexlens/semantic/__pycache__/llm_enhancer.cpython-313.pyc +0 -0
  376. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  377. package/codex-lens/src/codexlens/semantic/ann_index.py +414 -0
  378. package/codex-lens/src/codexlens/semantic/chunker.py +448 -0
  379. package/codex-lens/src/codexlens/semantic/code_extractor.py +274 -0
  380. package/codex-lens/src/codexlens/semantic/embedder.py +185 -0
  381. package/codex-lens/src/codexlens/semantic/vector_store.py +955 -0
  382. package/codex-lens/src/codexlens/storage/__init__.py +29 -0
  383. package/codex-lens/src/codexlens/storage/__pycache__/__init__.cpython-313.pyc +0 -0
  384. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  385. package/codex-lens/src/codexlens/storage/__pycache__/file_cache.cpython-313.pyc +0 -0
  386. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  387. package/codex-lens/src/codexlens/storage/__pycache__/migration_manager.cpython-313.pyc +0 -0
  388. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  389. package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
  390. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  391. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_utils.cpython-313.pyc +0 -0
  392. package/codex-lens/src/codexlens/storage/dir_index.py +1850 -0
  393. package/codex-lens/src/codexlens/storage/file_cache.py +32 -0
  394. package/codex-lens/src/codexlens/storage/index_tree.py +776 -0
  395. package/codex-lens/src/codexlens/storage/migration_manager.py +154 -0
  396. package/codex-lens/src/codexlens/storage/migrations/__init__.py +1 -0
  397. package/codex-lens/src/codexlens/storage/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  398. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_001_normalize_keywords.cpython-313.pyc +0 -0
  399. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_002_add_token_metadata.cpython-313.pyc +0 -0
  400. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_003_code_relationships.cpython-313.pyc +0 -0
  401. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  402. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_005_cleanup_unused_fields.cpython-313.pyc +0 -0
  403. package/codex-lens/src/codexlens/storage/migrations/migration_001_normalize_keywords.py +123 -0
  404. package/codex-lens/src/codexlens/storage/migrations/migration_002_add_token_metadata.py +48 -0
  405. package/codex-lens/src/codexlens/storage/migrations/migration_004_dual_fts.py +232 -0
  406. package/codex-lens/src/codexlens/storage/migrations/migration_005_cleanup_unused_fields.py +196 -0
  407. package/codex-lens/src/codexlens/storage/path_mapper.py +274 -0
  408. package/codex-lens/src/codexlens/storage/registry.py +670 -0
  409. package/codex-lens/src/codexlens/storage/sqlite_store.py +576 -0
  410. package/codex-lens/src/codexlens/storage/sqlite_utils.py +64 -0
  411. package/package.json +4 -1
  412. package/.claude/commands/memory/tech-research.md +0 -477
  413. package/.claude/scripts/classify-folders.sh +0 -39
  414. package/.claude/scripts/convert_tokens_to_css.sh +0 -229
  415. package/.claude/scripts/detect_changed_modules.sh +0 -161
  416. package/.claude/scripts/discover-design-files.sh +0 -87
  417. package/.claude/scripts/extract-animations.js +0 -243
  418. package/.claude/scripts/extract-computed-styles.js +0 -118
  419. package/.claude/scripts/extract-layout-structure.js +0 -411
  420. package/.claude/scripts/generate_module_docs.sh +0 -717
  421. package/.claude/scripts/get_modules_by_depth.sh +0 -170
  422. package/.claude/scripts/ui-generate-preview.sh +0 -395
  423. package/.claude/scripts/ui-instantiate-prototypes.sh +0 -815
  424. package/.claude/scripts/update_module_claude.sh +0 -337
  425. package/.claude/workflows/context-search-strategy.md +0 -77
  426. package/.claude/workflows/intelligent-tools-strategy.md +0 -662
  427. package/ccw/src/cli.js +0 -119
  428. package/ccw/src/commands/install.js +0 -324
  429. package/ccw/src/commands/tool.js +0 -138
  430. package/ccw/src/core/data-aggregator.js +0 -409
  431. package/ccw/src/core/server.js +0 -2063
  432. package/ccw/src/core/session-scanner.js +0 -235
  433. package/ccw/src/tools/classify-folders.js +0 -204
  434. package/ccw/src/tools/detect-changed-modules.js +0 -288
  435. package/ccw/src/tools/edit-file.js +0 -266
  436. package/ccw/src/tools/index.js +0 -176
  437. package/ccw/src/utils/file-utils.js +0 -48
@@ -0,0 +1,1258 @@
1
+ /**
2
+ * Session Clustering Service
3
+ * Intelligently groups related sessions into clusters using multi-dimensional similarity analysis
4
+ */
5
+
6
+ import { CoreMemoryStore, SessionCluster, ClusterMember, SessionMetadataCache } from './core-memory-store.js';
7
+ import { CliHistoryStore } from '../tools/cli-history-store.js';
8
+ import { StoragePaths } from '../config/storage-paths.js';
9
+ import { readdirSync, readFileSync, statSync, existsSync } from 'fs';
10
+ import { join } from 'path';
11
+
12
+ // Clustering dimension weights
13
+ const WEIGHTS = {
14
+ fileOverlap: 0.2,
15
+ temporalProximity: 0.15,
16
+ keywordSimilarity: 0.15,
17
+ vectorSimilarity: 0.3,
18
+ intentAlignment: 0.2,
19
+ };
20
+
21
+ // Clustering threshold (0.4 = moderate similarity required)
22
+ const CLUSTER_THRESHOLD = 0.4;
23
+
24
+ export interface ClusteringOptions {
25
+ scope?: 'all' | 'recent' | 'unclustered';
26
+ timeRange?: { start: string; end: string };
27
+ minClusterSize?: number;
28
+ }
29
+
30
+ export interface ClusteringResult {
31
+ clustersCreated: number;
32
+ sessionsProcessed: number;
33
+ sessionsClustered: number;
34
+ }
35
+
36
+ export class SessionClusteringService {
37
+ private coreMemoryStore: CoreMemoryStore;
38
+ private cliHistoryStore: CliHistoryStore;
39
+ private projectPath: string;
40
+
41
+ constructor(projectPath: string) {
42
+ this.projectPath = projectPath;
43
+ this.coreMemoryStore = new CoreMemoryStore(projectPath);
44
+ this.cliHistoryStore = new CliHistoryStore(projectPath);
45
+ }
46
+
47
+ /**
48
+ * Collect all session sources
49
+ */
50
+ async collectSessions(options?: ClusteringOptions): Promise<SessionMetadataCache[]> {
51
+ const sessions: SessionMetadataCache[] = [];
52
+
53
+ // 1. Core Memories
54
+ const memories = this.coreMemoryStore.getMemories({ archived: false, limit: 1000 });
55
+ for (const memory of memories) {
56
+ const cached = this.coreMemoryStore.getSessionMetadata(memory.id);
57
+ if (cached) {
58
+ sessions.push(cached);
59
+ } else {
60
+ const metadata = this.extractMetadata(memory, 'core_memory');
61
+ sessions.push(metadata);
62
+ }
63
+ }
64
+
65
+ // 2. CLI History
66
+ const history = this.cliHistoryStore.getHistory({ limit: 1000 });
67
+ for (const exec of history.executions) {
68
+ const cached = this.coreMemoryStore.getSessionMetadata(exec.id);
69
+ if (cached) {
70
+ sessions.push(cached);
71
+ } else {
72
+ const conversation = this.cliHistoryStore.getConversation(exec.id);
73
+ if (conversation) {
74
+ const metadata = this.extractMetadata(conversation, 'cli_history');
75
+ sessions.push(metadata);
76
+ }
77
+ }
78
+ }
79
+
80
+ // 3. Workflow Sessions (WFS-*)
81
+ const workflowSessions = await this.parseWorkflowSessions();
82
+ sessions.push(...workflowSessions);
83
+
84
+ // Apply scope filter
85
+ if (options?.scope === 'recent') {
86
+ // Last 30 days
87
+ const cutoff = new Date();
88
+ cutoff.setDate(cutoff.getDate() - 30);
89
+ const cutoffStr = cutoff.toISOString();
90
+ return sessions.filter(s => (s.created_at || '') >= cutoffStr);
91
+ } else if (options?.scope === 'unclustered') {
92
+ // Only sessions not in any cluster
93
+ return sessions.filter(s => {
94
+ const clusters = this.coreMemoryStore.getSessionClusters(s.session_id);
95
+ return clusters.length === 0;
96
+ });
97
+ }
98
+
99
+ return sessions;
100
+ }
101
+
102
+ /**
103
+ * Extract metadata from a session
104
+ */
105
+ extractMetadata(session: any, type: 'core_memory' | 'workflow' | 'cli_history' | 'native'): SessionMetadataCache {
106
+ let content = '';
107
+ let title = '';
108
+ let created_at = '';
109
+
110
+ if (type === 'core_memory') {
111
+ content = session.content || '';
112
+ created_at = session.created_at;
113
+ // Extract title from first line
114
+ const lines = content.split('\n');
115
+ title = lines[0].replace(/^#+\s*/, '').trim().substring(0, 100);
116
+ } else if (type === 'cli_history') {
117
+ // Extract from conversation turns
118
+ const turns = session.turns || [];
119
+ if (turns.length > 0) {
120
+ content = turns.map((t: any) => t.prompt).join('\n');
121
+ title = turns[0].prompt.substring(0, 100);
122
+ created_at = session.created_at || turns[0].timestamp;
123
+ }
124
+ } else if (type === 'workflow') {
125
+ content = session.content || '';
126
+ title = session.title || 'Workflow Session';
127
+ created_at = session.created_at || '';
128
+ }
129
+
130
+ const summary = content.substring(0, 200).trim();
131
+ const keywords = this.extractKeywords(content);
132
+ const file_patterns = this.extractFilePatterns(content);
133
+ const token_estimate = Math.ceil(content.length / 4);
134
+
135
+ return {
136
+ session_id: session.id,
137
+ session_type: type,
138
+ title,
139
+ summary,
140
+ keywords,
141
+ token_estimate,
142
+ file_patterns,
143
+ created_at,
144
+ last_accessed: new Date().toISOString(),
145
+ access_count: 0
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Extract keywords from content
151
+ */
152
+ private extractKeywords(content: string): string[] {
153
+ const keywords = new Set<string>();
154
+
155
+ // 1. File paths (src/xxx, .ts, .js, etc)
156
+ const filePathRegex = /(?:^|\s|["'`])((?:\.\/|\.\.\/|\/)?[\w-]+(?:\/[\w-]+)*\.[\w]+)(?:\s|["'`]|$)/g;
157
+ let match;
158
+ while ((match = filePathRegex.exec(content)) !== null) {
159
+ keywords.add(match[1]);
160
+ }
161
+
162
+ // 2. Function/Class names (camelCase, PascalCase)
163
+ const camelCaseRegex = /\b([A-Z][a-z]+(?:[A-Z][a-z]+)+|[a-z]+[A-Z][a-z]+(?:[A-Z][a-z]+)*)\b/g;
164
+ while ((match = camelCaseRegex.exec(content)) !== null) {
165
+ keywords.add(match[1]);
166
+ }
167
+
168
+ // 3. Technical terms (common frameworks/libraries/concepts)
169
+ const techTerms = [
170
+ // Frameworks
171
+ 'react', 'vue', 'angular', 'typescript', 'javascript', 'node', 'express',
172
+ // Auth
173
+ 'auth', 'authentication', 'jwt', 'oauth', 'session', 'token',
174
+ // Data
175
+ 'api', 'rest', 'graphql', 'database', 'sql', 'mongodb', 'redis',
176
+ // Testing
177
+ 'test', 'testing', 'jest', 'mocha', 'vitest',
178
+ // Development
179
+ 'refactor', 'refactoring', 'optimization', 'performance',
180
+ 'bug', 'fix', 'error', 'issue', 'debug',
181
+ // CCW-specific terms
182
+ 'cluster', 'clustering', 'memory', 'hook', 'service', 'context',
183
+ 'workflow', 'skill', 'prompt', 'embedding', 'vector', 'semantic',
184
+ 'dashboard', 'view', 'route', 'command', 'cli', 'mcp'
185
+ ];
186
+
187
+ const lowerContent = content.toLowerCase();
188
+ for (const term of techTerms) {
189
+ if (lowerContent.includes(term)) {
190
+ keywords.add(term);
191
+ }
192
+ }
193
+
194
+ // 4. Generic word extraction (words >= 4 chars, not stopwords)
195
+ const stopwords = new Set([
196
+ 'the', 'and', 'for', 'that', 'this', 'with', 'from', 'have', 'will',
197
+ 'are', 'was', 'were', 'been', 'being', 'what', 'when', 'where', 'which',
198
+ 'there', 'their', 'they', 'them', 'then', 'than', 'into', 'some', 'such',
199
+ 'only', 'also', 'just', 'more', 'most', 'other', 'after', 'before'
200
+ ]);
201
+
202
+ const wordRegex = /\b([a-z]{4,})\b/g;
203
+ let wordMatch;
204
+ while ((wordMatch = wordRegex.exec(lowerContent)) !== null) {
205
+ const word = wordMatch[1];
206
+ if (!stopwords.has(word)) {
207
+ keywords.add(word);
208
+ }
209
+ }
210
+
211
+ // Return top 20 keywords
212
+ return Array.from(keywords).slice(0, 20);
213
+ }
214
+
215
+ /**
216
+ * Extract file patterns from content
217
+ */
218
+ private extractFilePatterns(content: string): string[] {
219
+ const patterns = new Set<string>();
220
+
221
+ // Extract directory patterns (src/xxx/, lib/xxx/)
222
+ const dirRegex = /\b((?:src|lib|test|dist|build|public|components|utils|services|config|core|tools)(?:\/[\w-]+)*)\//g;
223
+ let match;
224
+ while ((match = dirRegex.exec(content)) !== null) {
225
+ patterns.add(match[1] + '/**');
226
+ }
227
+
228
+ // Extract file extension patterns
229
+ const extRegex = /\.(\w+)(?:\s|$|["'`])/g;
230
+ const extensions = new Set<string>();
231
+ while ((match = extRegex.exec(content)) !== null) {
232
+ extensions.add(match[1]);
233
+ }
234
+
235
+ // Add extension patterns
236
+ if (extensions.size > 0) {
237
+ patterns.add(`**/*.{${Array.from(extensions).join(',')}}`);
238
+ }
239
+
240
+ return Array.from(patterns).slice(0, 10);
241
+ }
242
+
243
+ /**
244
+ * Calculate relevance score between two sessions
245
+ */
246
+ calculateRelevance(session1: SessionMetadataCache, session2: SessionMetadataCache): number {
247
+ const fileScore = this.calculateFileOverlap(session1, session2);
248
+ const temporalScore = this.calculateTemporalProximity(session1, session2);
249
+ const keywordScore = this.calculateSemanticSimilarity(session1, session2);
250
+ const vectorScore = this.calculateVectorSimilarity(session1, session2);
251
+ const intentScore = this.calculateIntentAlignment(session1, session2);
252
+
253
+ return (
254
+ fileScore * WEIGHTS.fileOverlap +
255
+ temporalScore * WEIGHTS.temporalProximity +
256
+ keywordScore * WEIGHTS.keywordSimilarity +
257
+ vectorScore * WEIGHTS.vectorSimilarity +
258
+ intentScore * WEIGHTS.intentAlignment
259
+ );
260
+ }
261
+
262
+ /**
263
+ * Calculate file path overlap score (Jaccard similarity)
264
+ */
265
+ private calculateFileOverlap(s1: SessionMetadataCache, s2: SessionMetadataCache): number {
266
+ const files1 = new Set(s1.file_patterns || []);
267
+ const files2 = new Set(s2.file_patterns || []);
268
+
269
+ if (files1.size === 0 || files2.size === 0) return 0;
270
+
271
+ const intersection = new Set([...files1].filter(f => files2.has(f)));
272
+ const union = new Set([...files1, ...files2]);
273
+
274
+ return intersection.size / union.size;
275
+ }
276
+
277
+ /**
278
+ * Calculate temporal proximity score
279
+ * 24h: 1.0, 7d: 0.7, 30d: 0.4, >30d: 0.1
280
+ */
281
+ private calculateTemporalProximity(s1: SessionMetadataCache, s2: SessionMetadataCache): number {
282
+ if (!s1.created_at || !s2.created_at) return 0.1;
283
+
284
+ const t1 = new Date(s1.created_at).getTime();
285
+ const t2 = new Date(s2.created_at).getTime();
286
+ const diffMs = Math.abs(t1 - t2);
287
+ const diffHours = diffMs / (1000 * 60 * 60);
288
+
289
+ if (diffHours <= 24) return 1.0;
290
+ if (diffHours <= 24 * 7) return 0.7;
291
+ if (diffHours <= 24 * 30) return 0.4;
292
+ return 0.1;
293
+ }
294
+
295
+ /**
296
+ * Calculate semantic similarity using keyword overlap (Jaccard similarity)
297
+ */
298
+ private calculateSemanticSimilarity(s1: SessionMetadataCache, s2: SessionMetadataCache): number {
299
+ const kw1 = new Set(s1.keywords || []);
300
+ const kw2 = new Set(s2.keywords || []);
301
+
302
+ if (kw1.size === 0 || kw2.size === 0) return 0;
303
+
304
+ const intersection = new Set([...kw1].filter(k => kw2.has(k)));
305
+ const union = new Set([...kw1, ...kw2]);
306
+
307
+ return intersection.size / union.size;
308
+ }
309
+
310
+ /**
311
+ * Calculate intent alignment score
312
+ * Based on title/summary keyword matching
313
+ */
314
+ private calculateIntentAlignment(s1: SessionMetadataCache, s2: SessionMetadataCache): number {
315
+ const text1 = ((s1.title || '') + ' ' + (s1.summary || '')).toLowerCase();
316
+ const text2 = ((s2.title || '') + ' ' + (s2.summary || '')).toLowerCase();
317
+
318
+ if (!text1 || !text2) return 0;
319
+
320
+ // Simple word-based TF-IDF approximation
321
+ const words1 = text1.split(/\s+/).filter(w => w.length > 3);
322
+ const words2 = text2.split(/\s+/).filter(w => w.length > 3);
323
+
324
+ const set1 = new Set(words1);
325
+ const set2 = new Set(words2);
326
+
327
+ const intersection = new Set([...set1].filter(w => set2.has(w)));
328
+ const union = new Set([...set1, ...set2]);
329
+
330
+ return intersection.size / union.size;
331
+ }
332
+
333
+ /**
334
+ * Calculate vector similarity using pre-computed embeddings from memory_chunks
335
+ * Returns average cosine similarity of chunk embeddings
336
+ */
337
+ private calculateVectorSimilarity(s1: SessionMetadataCache, s2: SessionMetadataCache): number {
338
+ const embedding1 = this.getSessionEmbedding(s1.session_id);
339
+ const embedding2 = this.getSessionEmbedding(s2.session_id);
340
+
341
+ // Graceful fallback if no embeddings available
342
+ if (!embedding1 || !embedding2) {
343
+ return 0;
344
+ }
345
+
346
+ return this.cosineSimilarity(embedding1, embedding2);
347
+ }
348
+
349
+ /**
350
+ * Get session embedding by averaging all chunk embeddings
351
+ */
352
+ private getSessionEmbedding(sessionId: string): number[] | null {
353
+ const chunks = this.coreMemoryStore.getChunks(sessionId);
354
+
355
+ if (chunks.length === 0) {
356
+ return null;
357
+ }
358
+
359
+ // Filter chunks that have embeddings
360
+ const embeddedChunks = chunks.filter(chunk => chunk.embedding && chunk.embedding.length > 0);
361
+
362
+ if (embeddedChunks.length === 0) {
363
+ return null;
364
+ }
365
+
366
+ // Convert Buffer embeddings to number arrays and calculate average
367
+ const embeddings = embeddedChunks.map(chunk => {
368
+ // Convert Buffer to Float32Array
369
+ const buffer = chunk.embedding!;
370
+ const float32Array = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
371
+ return Array.from(float32Array);
372
+ });
373
+
374
+ // Check all embeddings have same dimension
375
+ const dimension = embeddings[0].length;
376
+ if (!embeddings.every(emb => emb.length === dimension)) {
377
+ console.warn(`[VectorSimilarity] Inconsistent embedding dimensions for session ${sessionId}`);
378
+ return null;
379
+ }
380
+
381
+ // Calculate average embedding
382
+ const avgEmbedding = new Array(dimension).fill(0);
383
+ for (const embedding of embeddings) {
384
+ for (let i = 0; i < dimension; i++) {
385
+ avgEmbedding[i] += embedding[i];
386
+ }
387
+ }
388
+
389
+ for (let i = 0; i < dimension; i++) {
390
+ avgEmbedding[i] /= embeddings.length;
391
+ }
392
+
393
+ return avgEmbedding;
394
+ }
395
+
396
+ /**
397
+ * Calculate cosine similarity between two vectors
398
+ */
399
+ private cosineSimilarity(a: number[], b: number[]): number {
400
+ if (a.length !== b.length) {
401
+ console.warn('[VectorSimilarity] Vector dimension mismatch');
402
+ return 0;
403
+ }
404
+
405
+ let dotProduct = 0;
406
+ let normA = 0;
407
+ let normB = 0;
408
+
409
+ for (let i = 0; i < a.length; i++) {
410
+ dotProduct += a[i] * b[i];
411
+ normA += a[i] * a[i];
412
+ normB += b[i] * b[i];
413
+ }
414
+
415
+ normA = Math.sqrt(normA);
416
+ normB = Math.sqrt(normB);
417
+
418
+ if (normA === 0 || normB === 0) {
419
+ return 0;
420
+ }
421
+
422
+ return dotProduct / (normA * normB);
423
+ }
424
+
425
+ /**
426
+ * Find the most relevant existing cluster for a set of session IDs
427
+ * Returns the cluster with highest session overlap
428
+ */
429
+ private findExistingClusterForSessions(sessionIds: string[]): SessionCluster | null {
430
+ if (sessionIds.length === 0) return null;
431
+
432
+ const clusterCounts = new Map<string, number>();
433
+ let maxCount = 0;
434
+ let bestClusterId: string | null = null;
435
+
436
+ for (const sessionId of sessionIds) {
437
+ const clusters = this.coreMemoryStore.getSessionClusters(sessionId);
438
+ for (const cluster of clusters) {
439
+ if (cluster.status !== 'active') continue;
440
+
441
+ const count = (clusterCounts.get(cluster.id) || 0) + 1;
442
+ clusterCounts.set(cluster.id, count);
443
+
444
+ if (count > maxCount) {
445
+ maxCount = count;
446
+ bestClusterId = cluster.id;
447
+ }
448
+ }
449
+ }
450
+
451
+ if (bestClusterId) {
452
+ return this.coreMemoryStore.getCluster(bestClusterId);
453
+ }
454
+ return null;
455
+ }
456
+
457
+ /**
458
+ * Determine if a new cluster should merge with an existing one
459
+ * Based on 70% session overlap threshold
460
+ */
461
+ private shouldMergeWithExisting(newClusterSessions: SessionMetadataCache[], existingCluster: SessionCluster): boolean {
462
+ const MERGE_THRESHOLD = 0.7;
463
+
464
+ const existingMembers = this.coreMemoryStore.getClusterMembers(existingCluster.id);
465
+ const newSessionIds = new Set(newClusterSessions.map(s => s.session_id));
466
+ const existingSessionIds = new Set(existingMembers.map(m => m.session_id));
467
+
468
+ if (newSessionIds.size === 0) return false;
469
+
470
+ const intersection = new Set([...newSessionIds].filter(id => existingSessionIds.has(id)));
471
+ const overlapRatio = intersection.size / newSessionIds.size;
472
+
473
+ return overlapRatio > MERGE_THRESHOLD;
474
+ }
475
+
476
+ /**
477
+ * Run auto-clustering algorithm
478
+ * Optimized to prevent duplicate clusters by checking existing clusters first
479
+ */
480
+ async autocluster(options?: ClusteringOptions): Promise<ClusteringResult> {
481
+ // 1. Collect sessions based on user-specified scope (default: 'recent')
482
+ const allSessions = await this.collectSessions(options);
483
+ console.log(`[Clustering] Collected ${allSessions.length} sessions (scope: ${options?.scope || 'recent'})`);
484
+
485
+ // 2. Filter out already-clustered sessions to prevent duplicates
486
+ const sessions = allSessions.filter(s => {
487
+ const clusters = this.coreMemoryStore.getSessionClusters(s.session_id);
488
+ return clusters.length === 0;
489
+ });
490
+ console.log(`[Clustering] ${sessions.length} unclustered sessions after filtering`);
491
+
492
+ // 3. Update metadata cache
493
+ for (const session of sessions) {
494
+ this.coreMemoryStore.upsertSessionMetadata(session);
495
+ }
496
+
497
+ // 4. Calculate relevance matrix
498
+ const n = sessions.length;
499
+ const relevanceMatrix: number[][] = Array(n).fill(0).map(() => Array(n).fill(0));
500
+
501
+ let maxScore = 0;
502
+ let avgScore = 0;
503
+ let pairCount = 0;
504
+
505
+ for (let i = 0; i < n; i++) {
506
+ for (let j = i + 1; j < n; j++) {
507
+ const score = this.calculateRelevance(sessions[i], sessions[j]);
508
+ relevanceMatrix[i][j] = score;
509
+ relevanceMatrix[j][i] = score;
510
+
511
+ if (score > maxScore) maxScore = score;
512
+ avgScore += score;
513
+ pairCount++;
514
+ }
515
+ }
516
+
517
+ if (pairCount > 0) {
518
+ avgScore = avgScore / pairCount;
519
+ console.log(`[Clustering] Relevance stats: max=${maxScore.toFixed(3)}, avg=${avgScore.toFixed(3)}, pairs=${pairCount}, threshold=${CLUSTER_THRESHOLD}`);
520
+ }
521
+
522
+ // 5. Agglomerative clustering
523
+ const minClusterSize = options?.minClusterSize || 2;
524
+
525
+ // Early return if not enough sessions
526
+ if (sessions.length < minClusterSize) {
527
+ console.log('[Clustering] Not enough unclustered sessions to form new clusters');
528
+ return { clustersCreated: 0, sessionsProcessed: allSessions.length, sessionsClustered: 0 };
529
+ }
530
+
531
+ const newPotentialClusters = this.agglomerativeClustering(sessions, relevanceMatrix, CLUSTER_THRESHOLD);
532
+ console.log(`[Clustering] Generated ${newPotentialClusters.length} potential clusters`);
533
+
534
+ // 6. Process clusters: create new or merge with existing
535
+ let clustersCreated = 0;
536
+ let clustersMerged = 0;
537
+ let sessionsClustered = 0;
538
+
539
+ for (const clusterSessions of newPotentialClusters) {
540
+ if (clusterSessions.length < minClusterSize) {
541
+ continue; // Skip small clusters
542
+ }
543
+
544
+ const sessionIds = clusterSessions.map(s => s.session_id);
545
+ const existingCluster = this.findExistingClusterForSessions(sessionIds);
546
+
547
+ // Check if we should merge with an existing cluster
548
+ if (existingCluster && this.shouldMergeWithExisting(clusterSessions, existingCluster)) {
549
+ const existingMembers = this.coreMemoryStore.getClusterMembers(existingCluster.id);
550
+ const existingSessionIds = new Set(existingMembers.map(m => m.session_id));
551
+
552
+ // Only add sessions not already in the cluster
553
+ const newSessions = clusterSessions.filter(s => !existingSessionIds.has(s.session_id));
554
+
555
+ if (newSessions.length > 0) {
556
+ newSessions.forEach((session, index) => {
557
+ this.coreMemoryStore.addClusterMember({
558
+ cluster_id: existingCluster.id,
559
+ session_id: session.session_id,
560
+ session_type: session.session_type as 'core_memory' | 'workflow' | 'cli_history' | 'native',
561
+ sequence_order: existingMembers.length + index + 1,
562
+ relevance_score: 1.0
563
+ });
564
+ });
565
+
566
+ // Update cluster description
567
+ this.coreMemoryStore.updateCluster(existingCluster.id, {
568
+ description: `Auto-generated cluster with ${existingMembers.length + newSessions.length} sessions`
569
+ });
570
+
571
+ clustersMerged++;
572
+ sessionsClustered += newSessions.length;
573
+ console.log(`[Clustering] Merged ${newSessions.length} sessions into existing cluster '${existingCluster.name}'`);
574
+ }
575
+ } else {
576
+ // Create new cluster
577
+ const clusterName = this.generateClusterName(clusterSessions);
578
+ const clusterIntent = this.generateClusterIntent(clusterSessions);
579
+
580
+ const clusterRecord = this.coreMemoryStore.createCluster({
581
+ name: clusterName,
582
+ description: `Auto-generated cluster with ${clusterSessions.length} sessions`,
583
+ intent: clusterIntent,
584
+ status: 'active'
585
+ });
586
+
587
+ // Add members
588
+ clusterSessions.forEach((session, index) => {
589
+ this.coreMemoryStore.addClusterMember({
590
+ cluster_id: clusterRecord.id,
591
+ session_id: session.session_id,
592
+ session_type: session.session_type as 'core_memory' | 'workflow' | 'cli_history' | 'native',
593
+ sequence_order: index + 1,
594
+ relevance_score: 1.0
595
+ });
596
+ });
597
+
598
+ clustersCreated++;
599
+ sessionsClustered += clusterSessions.length;
600
+ }
601
+ }
602
+
603
+ console.log(`[Clustering] Summary: ${clustersCreated} created, ${clustersMerged} merged, ${allSessions.length - sessions.length} already clustered`);
604
+
605
+ return {
606
+ clustersCreated,
607
+ sessionsProcessed: allSessions.length,
608
+ sessionsClustered
609
+ };
610
+ }
611
+
612
+ /**
613
+ * Deduplicate clusters by merging similar ones
614
+ * Clusters with same name or >50% member overlap are merged
615
+ * @returns Statistics about deduplication
616
+ */
617
+ async deduplicateClusters(): Promise<{ merged: number; deleted: number; remaining: number }> {
618
+ const clusters = this.coreMemoryStore.listClusters('active');
619
+ console.log(`[Dedup] Analyzing ${clusters.length} active clusters`);
620
+
621
+ if (clusters.length < 2) {
622
+ return { merged: 0, deleted: 0, remaining: clusters.length };
623
+ }
624
+
625
+ // Group clusters by name (case-insensitive)
626
+ const byName = new Map<string, typeof clusters>();
627
+ for (const cluster of clusters) {
628
+ const key = cluster.name.toLowerCase().trim();
629
+ if (!byName.has(key)) {
630
+ byName.set(key, []);
631
+ }
632
+ byName.get(key)!.push(cluster);
633
+ }
634
+
635
+ let merged = 0;
636
+ let deleted = 0;
637
+
638
+ // Merge clusters with same name
639
+ for (const [name, group] of byName) {
640
+ if (group.length < 2) continue;
641
+
642
+ // Sort by created_at (oldest first) to keep the original
643
+ group.sort((a, b) => a.created_at.localeCompare(b.created_at));
644
+ const target = group[0];
645
+ const sources = group.slice(1).map(c => c.id);
646
+
647
+ console.log(`[Dedup] Merging ${sources.length} duplicate clusters named '${name}' into ${target.id}`);
648
+
649
+ try {
650
+ const membersMoved = this.coreMemoryStore.mergeClusters(target.id, sources);
651
+ merged += sources.length;
652
+ console.log(`[Dedup] Moved ${membersMoved} members, deleted ${sources.length} clusters`);
653
+ } catch (error) {
654
+ console.warn(`[Dedup] Failed to merge: ${(error as Error).message}`);
655
+ }
656
+ }
657
+
658
+ // Check for clusters with high member overlap
659
+ const remainingClusters = this.coreMemoryStore.listClusters('active');
660
+ const clusterMembers = new Map<string, Set<string>>();
661
+
662
+ for (const cluster of remainingClusters) {
663
+ const members = this.coreMemoryStore.getClusterMembers(cluster.id);
664
+ clusterMembers.set(cluster.id, new Set(members.map(m => m.session_id)));
665
+ }
666
+
667
+ // Find and merge overlapping clusters
668
+ const processed = new Set<string>();
669
+ for (let i = 0; i < remainingClusters.length; i++) {
670
+ const clusterA = remainingClusters[i];
671
+ if (processed.has(clusterA.id)) continue;
672
+
673
+ const membersA = clusterMembers.get(clusterA.id)!;
674
+ const toMerge: string[] = [];
675
+
676
+ for (let j = i + 1; j < remainingClusters.length; j++) {
677
+ const clusterB = remainingClusters[j];
678
+ if (processed.has(clusterB.id)) continue;
679
+
680
+ const membersB = clusterMembers.get(clusterB.id)!;
681
+ const intersection = new Set([...membersA].filter(m => membersB.has(m)));
682
+
683
+ // Calculate overlap ratio (based on smaller cluster)
684
+ const minSize = Math.min(membersA.size, membersB.size);
685
+ if (minSize > 0 && intersection.size / minSize >= 0.5) {
686
+ toMerge.push(clusterB.id);
687
+ processed.add(clusterB.id);
688
+ }
689
+ }
690
+
691
+ if (toMerge.length > 0) {
692
+ console.log(`[Dedup] Merging ${toMerge.length} overlapping clusters into ${clusterA.id}`);
693
+ try {
694
+ this.coreMemoryStore.mergeClusters(clusterA.id, toMerge);
695
+ merged += toMerge.length;
696
+ } catch (error) {
697
+ console.warn(`[Dedup] Failed to merge overlapping: ${(error as Error).message}`);
698
+ }
699
+ }
700
+ }
701
+
702
+ // Delete empty clusters
703
+ const finalClusters = this.coreMemoryStore.listClusters('active');
704
+ for (const cluster of finalClusters) {
705
+ const members = this.coreMemoryStore.getClusterMembers(cluster.id);
706
+ if (members.length === 0) {
707
+ this.coreMemoryStore.deleteCluster(cluster.id);
708
+ deleted++;
709
+ console.log(`[Dedup] Deleted empty cluster: ${cluster.id}`);
710
+ }
711
+ }
712
+
713
+ const remaining = this.coreMemoryStore.listClusters('active').length;
714
+ console.log(`[Dedup] Complete: ${merged} merged, ${deleted} deleted, ${remaining} remaining`);
715
+
716
+ return { merged, deleted, remaining };
717
+ }
718
+
719
+ /**
720
+ * Agglomerative clustering algorithm
721
+ * Returns array of clusters (each cluster is array of sessions)
722
+ */
723
+ private agglomerativeClustering(
724
+ sessions: SessionMetadataCache[],
725
+ relevanceMatrix: number[][],
726
+ threshold: number
727
+ ): SessionMetadataCache[][] {
728
+ const n = sessions.length;
729
+
730
+ // Initialize: each session is its own cluster
731
+ const clusters: Set<number>[] = sessions.map((_, i) => new Set([i]));
732
+
733
+ while (true) {
734
+ let maxScore = -1;
735
+ let mergeI = -1;
736
+ let mergeJ = -1;
737
+
738
+ // Find pair of clusters with highest average linkage
739
+ for (let i = 0; i < clusters.length; i++) {
740
+ for (let j = i + 1; j < clusters.length; j++) {
741
+ const score = this.averageLinkage(clusters[i], clusters[j], relevanceMatrix);
742
+ if (score > maxScore) {
743
+ maxScore = score;
744
+ mergeI = i;
745
+ mergeJ = j;
746
+ }
747
+ }
748
+ }
749
+
750
+ // Stop if no pair exceeds threshold
751
+ if (maxScore < threshold) break;
752
+
753
+ // Merge clusters
754
+ const merged = new Set([...clusters[mergeI], ...clusters[mergeJ]]);
755
+ clusters.splice(mergeJ, 1); // Remove j first (higher index)
756
+ clusters.splice(mergeI, 1);
757
+ clusters.push(merged);
758
+ }
759
+
760
+ // Convert cluster indices to sessions
761
+ return clusters.map(cluster =>
762
+ Array.from(cluster).map(i => sessions[i])
763
+ );
764
+ }
765
+
766
+ /**
767
+ * Calculate average linkage between two clusters
768
+ */
769
+ private averageLinkage(
770
+ cluster1: Set<number>,
771
+ cluster2: Set<number>,
772
+ relevanceMatrix: number[][]
773
+ ): number {
774
+ let sum = 0;
775
+ let count = 0;
776
+
777
+ for (const i of cluster1) {
778
+ for (const j of cluster2) {
779
+ sum += relevanceMatrix[i][j];
780
+ count++;
781
+ }
782
+ }
783
+
784
+ return count > 0 ? sum / count : 0;
785
+ }
786
+
787
+ /**
788
+ * Generate cluster name from members
789
+ */
790
+ private generateClusterName(members: SessionMetadataCache[]): string {
791
+ // Count keyword frequency
792
+ const keywordFreq = new Map<string, number>();
793
+ for (const member of members) {
794
+ for (const keyword of member.keywords || []) {
795
+ keywordFreq.set(keyword, (keywordFreq.get(keyword) || 0) + 1);
796
+ }
797
+ }
798
+
799
+ // Get top 2 keywords
800
+ const sorted = Array.from(keywordFreq.entries())
801
+ .sort((a, b) => b[1] - a[1])
802
+ .map(([kw]) => kw);
803
+
804
+ if (sorted.length >= 2) {
805
+ return `${sorted[0]}-${sorted[1]}`;
806
+ } else if (sorted.length === 1) {
807
+ return sorted[0];
808
+ } else {
809
+ return 'unnamed-cluster';
810
+ }
811
+ }
812
+
813
+ /**
814
+ * Generate cluster intent from members
815
+ */
816
+ private generateClusterIntent(members: SessionMetadataCache[]): string {
817
+ // Extract common action words from titles
818
+ const actionWords = ['implement', 'refactor', 'fix', 'add', 'create', 'update', 'optimize'];
819
+ const titles = members.map(m => (m.title || '').toLowerCase());
820
+
821
+ for (const action of actionWords) {
822
+ const count = titles.filter(t => t.includes(action)).length;
823
+ if (count >= members.length / 2) {
824
+ const topic = this.generateClusterName(members);
825
+ return `${action.charAt(0).toUpperCase() + action.slice(1)} ${topic}`;
826
+ }
827
+ }
828
+
829
+ return `Work on ${this.generateClusterName(members)}`;
830
+ }
831
+
832
+ /**
833
+ * Get progressive disclosure index for hook
834
+ * @param options - Configuration options
835
+ * @param options.type - 'session-start' returns recent sessions, 'context' returns intent-matched sessions
836
+ * @param options.sessionId - Current session ID (optional)
837
+ * @param options.prompt - User prompt for intent matching (required for 'context' type)
838
+ */
839
+ async getProgressiveIndex(options: {
840
+ type: 'session-start' | 'context';
841
+ sessionId?: string;
842
+ prompt?: string;
843
+ }): Promise<string> {
844
+ const { type, sessionId, prompt } = options;
845
+
846
+ // For session-start: return recent sessions by time
847
+ if (type === 'session-start') {
848
+ return this.getRecentSessionsIndex();
849
+ }
850
+
851
+ // For context: return intent-matched sessions based on prompt
852
+ if (type === 'context' && prompt) {
853
+ return this.getIntentMatchedIndex(prompt, sessionId);
854
+ }
855
+
856
+ // Fallback to recent sessions
857
+ return this.getRecentSessionsIndex();
858
+ }
859
+
860
+ /**
861
+ * Get recent sessions index (for session-start)
862
+ * Shows sessions grouped by clusters with progressive disclosure
863
+ */
864
+ private async getRecentSessionsIndex(): Promise<string> {
865
+ // 1. Get all active clusters
866
+ const allClusters = this.coreMemoryStore.listClusters('active');
867
+
868
+ // Sort clusters by most recent activity (based on member last_accessed)
869
+ const clustersWithActivity = allClusters.map(cluster => {
870
+ const members = this.coreMemoryStore.getClusterMembers(cluster.id);
871
+ const memberMetadata = members
872
+ .map(m => this.coreMemoryStore.getSessionMetadata(m.session_id))
873
+ .filter((m): m is SessionMetadataCache => m !== null);
874
+
875
+ const lastActivity = memberMetadata.reduce((latest, m) => {
876
+ const accessed = m.last_accessed || m.created_at || '';
877
+ return accessed > latest ? accessed : latest;
878
+ }, '');
879
+
880
+ return { cluster, members, memberMetadata, lastActivity };
881
+ }).sort((a, b) => b.lastActivity.localeCompare(a.lastActivity));
882
+
883
+ // 2. Get unclustered recent sessions
884
+ const allSessions = await this.collectSessions({ scope: 'recent' });
885
+ const clusteredSessionIds = new Set<string>();
886
+ clustersWithActivity.forEach(c => {
887
+ c.members.forEach(m => clusteredSessionIds.add(m.session_id));
888
+ });
889
+
890
+ const unclusteredSessions = allSessions
891
+ .filter(s => !clusteredSessionIds.has(s.session_id))
892
+ .sort((a, b) => (b.created_at || '').localeCompare(a.created_at || ''))
893
+ .slice(0, 3);
894
+
895
+ // 3. Build output
896
+ let output = `<ccw-session-context>\n## 📋 Session Context (Progressive Disclosure)\n\n`;
897
+
898
+ // Show top 2 active clusters
899
+ const topClusters = clustersWithActivity.slice(0, 2);
900
+ if (topClusters.length > 0) {
901
+ output += `### 🔗 Active Clusters\n\n`;
902
+
903
+ for (const { cluster, memberMetadata } of topClusters) {
904
+ output += `**${cluster.name}** (${memberMetadata.length} sessions)\n`;
905
+ if (cluster.intent) {
906
+ output += `> Intent: ${cluster.intent}\n`;
907
+ }
908
+ output += `\n| Session | Type | Title |\n|---------|------|-------|\n`;
909
+
910
+ // Show top 3 members per cluster
911
+ const displayMembers = memberMetadata.slice(0, 3);
912
+ for (const m of displayMembers) {
913
+ const type = m.session_type === 'core_memory' ? 'Core' :
914
+ m.session_type === 'workflow' ? 'Workflow' : 'CLI';
915
+ const title = (m.title || '').substring(0, 35);
916
+ output += `| ${m.session_id} | ${type} | ${title} |\n`;
917
+ }
918
+
919
+ if (memberMetadata.length > 3) {
920
+ output += `| ... | ... | +${memberMetadata.length - 3} more |\n`;
921
+ }
922
+ output += `\n`;
923
+ }
924
+ }
925
+
926
+ // Show unclustered recent sessions
927
+ if (unclusteredSessions.length > 0) {
928
+ output += `### 📝 Recent Sessions (Unclustered)\n\n`;
929
+ output += `| Session | Type | Title | Date |\n`;
930
+ output += `|---------|------|-------|------|\n`;
931
+
932
+ for (const s of unclusteredSessions) {
933
+ const type = s.session_type === 'core_memory' ? 'Core' :
934
+ s.session_type === 'workflow' ? 'Workflow' : 'CLI';
935
+ const title = (s.title || '').substring(0, 30);
936
+ const date = s.created_at ? new Date(s.created_at).toLocaleDateString() : '';
937
+ output += `| ${s.session_id} | ${type} | ${title} | ${date} |\n`;
938
+ }
939
+ output += `\n`;
940
+ }
941
+
942
+ // If nothing found
943
+ if (topClusters.length === 0 && unclusteredSessions.length === 0) {
944
+ output += `No recent sessions found. Start a new workflow to begin tracking.\n\n`;
945
+ }
946
+
947
+ // Add MCP tools reference
948
+ const topSession = topClusters[0]?.memberMetadata[0] || unclusteredSessions[0];
949
+ const topClusterId = topClusters[0]?.cluster.id;
950
+
951
+ output += `**MCP Tools**:\n\`\`\`\n`;
952
+ if (topSession) {
953
+ output += `# Resume session\nmcp__ccw-tools__core_memory({ "operation": "export", "id": "${topSession.session_id}" })\n\n`;
954
+ }
955
+ if (topClusterId) {
956
+ output += `# Load cluster context\nmcp__ccw-tools__core_memory({ "operation": "search", "query": "cluster:${topClusterId}" })\n`;
957
+ }
958
+ output += `\`\`\`\n</ccw-session-context>`;
959
+
960
+ return output;
961
+ }
962
+
963
+ /**
964
+ * Get intent-matched sessions index (for context with prompt)
965
+ * Shows sessions grouped by clusters and ranked by relevance
966
+ */
967
+ private async getIntentMatchedIndex(prompt: string, sessionId?: string): Promise<string> {
968
+ const sessions = await this.collectSessions({ scope: 'all' });
969
+
970
+ if (sessions.length === 0) {
971
+ return `<ccw-session-context>
972
+ ## 📋 Related Sessions
973
+
974
+ No sessions available for intent matching.
975
+ </ccw-session-context>`;
976
+ }
977
+
978
+ // Create a virtual session from the prompt for similarity calculation
979
+ const promptSession: SessionMetadataCache = {
980
+ session_id: 'prompt-virtual',
981
+ session_type: 'native',
982
+ title: prompt.substring(0, 100),
983
+ summary: prompt.substring(0, 200),
984
+ keywords: this.extractKeywords(prompt),
985
+ token_estimate: Math.ceil(prompt.length / 4),
986
+ file_patterns: this.extractFilePatterns(prompt),
987
+ created_at: new Date().toISOString(),
988
+ last_accessed: new Date().toISOString(),
989
+ access_count: 0
990
+ };
991
+
992
+ // Build session-to-cluster mapping
993
+ const sessionClusterMap = new Map<string, SessionCluster[]>();
994
+ const allClusters = this.coreMemoryStore.listClusters('active');
995
+ for (const cluster of allClusters) {
996
+ const members = this.coreMemoryStore.getClusterMembers(cluster.id);
997
+ for (const member of members) {
998
+ const existing = sessionClusterMap.get(member.session_id) || [];
999
+ existing.push(cluster);
1000
+ sessionClusterMap.set(member.session_id, existing);
1001
+ }
1002
+ }
1003
+
1004
+ // Calculate relevance scores for all sessions
1005
+ const scoredSessions = sessions
1006
+ .filter(s => s.session_id !== sessionId) // Exclude current session
1007
+ .map(s => ({
1008
+ session: s,
1009
+ score: this.calculateRelevance(promptSession, s),
1010
+ clusters: sessionClusterMap.get(s.session_id) || []
1011
+ }))
1012
+ .filter(item => item.score >= 0.15) // Minimum relevance threshold (lowered for file-path-based keywords)
1013
+ .sort((a, b) => b.score - a.score)
1014
+ .slice(0, 8); // Top 8 relevant sessions
1015
+
1016
+ if (scoredSessions.length === 0) {
1017
+ return `<ccw-session-context>
1018
+ ## 📋 Related Sessions
1019
+
1020
+ No sessions match current intent. Consider:
1021
+ - Starting fresh with a new approach
1022
+ - Using \`search\` to find sessions by keyword
1023
+
1024
+ **MCP Tools**:
1025
+ \`\`\`
1026
+ mcp__ccw-tools__core_memory({ "operation": "search", "query": "<keyword>" })
1027
+ \`\`\`
1028
+ </ccw-session-context>`;
1029
+ }
1030
+
1031
+ // Group sessions by cluster
1032
+ const clusterGroups = new Map<string, { cluster: SessionCluster; sessions: typeof scoredSessions }>();
1033
+ const unclusteredSessions: typeof scoredSessions = [];
1034
+
1035
+ for (const item of scoredSessions) {
1036
+ if (item.clusters.length > 0) {
1037
+ // Add to the highest-priority cluster
1038
+ const primaryCluster = item.clusters[0];
1039
+ const existing = clusterGroups.get(primaryCluster.id) || { cluster: primaryCluster, sessions: [] };
1040
+ existing.sessions.push(item);
1041
+ clusterGroups.set(primaryCluster.id, existing);
1042
+ } else {
1043
+ unclusteredSessions.push(item);
1044
+ }
1045
+ }
1046
+
1047
+ // Sort cluster groups by best session score
1048
+ const sortedGroups = Array.from(clusterGroups.values())
1049
+ .sort((a, b) => Math.max(...b.sessions.map(s => s.score)) - Math.max(...a.sessions.map(s => s.score)));
1050
+
1051
+ // Generate output
1052
+ let output = `<ccw-session-context>\n## 📋 Intent-Matched Sessions\n\n`;
1053
+ output += `**Detected Intent**: ${(promptSession.keywords || []).slice(0, 5).join(', ') || 'General'}\n\n`;
1054
+
1055
+ // Show clustered sessions
1056
+ if (sortedGroups.length > 0) {
1057
+ output += `### 🔗 Matched Clusters\n\n`;
1058
+
1059
+ for (const { cluster, sessions: clusterSessions } of sortedGroups.slice(0, 2)) {
1060
+ const avgScore = Math.round(clusterSessions.reduce((sum, s) => sum + s.score, 0) / clusterSessions.length * 100);
1061
+ output += `**${cluster.name}** (${avgScore}% avg match)\n`;
1062
+ if (cluster.intent) {
1063
+ output += `> ${cluster.intent}\n`;
1064
+ }
1065
+ output += `\n| Session | Match | Title |\n|---------|-------|-------|\n`;
1066
+
1067
+ for (const item of clusterSessions.slice(0, 3)) {
1068
+ const matchPct = Math.round(item.score * 100);
1069
+ const title = (item.session.title || '').substring(0, 35);
1070
+ output += `| ${item.session.session_id} | ${matchPct}% | ${title} |\n`;
1071
+ }
1072
+ output += `\n`;
1073
+ }
1074
+ }
1075
+
1076
+ // Show unclustered sessions
1077
+ if (unclusteredSessions.length > 0) {
1078
+ output += `### 📝 Individual Matches\n\n`;
1079
+ output += `| Session | Type | Match | Title |\n`;
1080
+ output += `|---------|------|-------|-------|\n`;
1081
+
1082
+ for (const item of unclusteredSessions.slice(0, 4)) {
1083
+ const type = item.session.session_type === 'core_memory' ? 'Core' :
1084
+ item.session.session_type === 'workflow' ? 'Workflow' : 'CLI';
1085
+ const matchPct = Math.round(item.score * 100);
1086
+ const title = (item.session.title || '').substring(0, 30);
1087
+ output += `| ${item.session.session_id} | ${type} | ${matchPct}% | ${title} |\n`;
1088
+ }
1089
+ output += `\n`;
1090
+ }
1091
+
1092
+ // Add MCP tools reference
1093
+ const topSession = scoredSessions[0];
1094
+ const topCluster = sortedGroups[0]?.cluster;
1095
+
1096
+ output += `**MCP Tools**:\n\`\`\`\n`;
1097
+ output += `# Resume top match\nmcp__ccw-tools__core_memory({ "operation": "export", "id": "${topSession.session.session_id}" })\n`;
1098
+ if (topCluster) {
1099
+ output += `\n# Load cluster context\nmcp__ccw-tools__core_memory({ "operation": "search", "query": "cluster:${topCluster.id}" })\n`;
1100
+ }
1101
+ output += `\`\`\`\n</ccw-session-context>`;
1102
+
1103
+ return output;
1104
+ }
1105
+
1106
+ /**
1107
+ * Legacy method for backward compatibility
1108
+ * @deprecated Use getProgressiveIndex({ type, sessionId, prompt }) instead
1109
+ */
1110
+ async getProgressiveIndexLegacy(sessionId?: string): Promise<string> {
1111
+ let activeCluster: SessionCluster | null = null;
1112
+ let members: SessionMetadataCache[] = [];
1113
+
1114
+ if (sessionId) {
1115
+ const clusters = this.coreMemoryStore.getSessionClusters(sessionId);
1116
+ if (clusters.length > 0) {
1117
+ activeCluster = clusters[0];
1118
+ const clusterMembers = this.coreMemoryStore.getClusterMembers(activeCluster.id);
1119
+ members = clusterMembers
1120
+ .map(m => this.coreMemoryStore.getSessionMetadata(m.session_id))
1121
+ .filter((m): m is SessionMetadataCache => m !== null)
1122
+ .sort((a, b) => (a.created_at || '').localeCompare(b.created_at || ''));
1123
+ }
1124
+ }
1125
+
1126
+ if (!activeCluster || members.length === 0) {
1127
+ return `<ccw-session-context>
1128
+ ## 📋 Related Sessions Index
1129
+
1130
+ No active cluster found. Start a new workflow or continue from recent sessions.
1131
+
1132
+ **MCP Tools**:
1133
+ \`\`\`
1134
+ # Search sessions
1135
+ Use tool: mcp__ccw-tools__core_memory
1136
+ Parameters: { "action": "search", "query": "<keyword>" }
1137
+
1138
+ # Trigger clustering
1139
+ Parameters: { "action": "cluster", "scope": "auto" }
1140
+ \`\`\`
1141
+ </ccw-session-context>`;
1142
+ }
1143
+
1144
+ // Generate table
1145
+ let table = `| # | Session | Type | Summary | Tokens |\n`;
1146
+ table += `|---|---------|------|---------|--------|\n`;
1147
+
1148
+ members.forEach((m, idx) => {
1149
+ const type = m.session_type === 'core_memory' ? 'Core' :
1150
+ m.session_type === 'workflow' ? 'Workflow' : 'CLI';
1151
+ const summary = (m.summary || '').substring(0, 40);
1152
+ const token = `~${m.token_estimate || 0}`;
1153
+ table += `| ${idx + 1} | ${m.session_id} | ${type} | ${summary} | ${token} |\n`;
1154
+ });
1155
+
1156
+ // Generate timeline - show multiple recent sessions
1157
+ let timeline = '';
1158
+ if (members.length > 0) {
1159
+ const timelineEntries: string[] = [];
1160
+ const displayCount = Math.min(members.length, 3); // Show last 3 sessions
1161
+
1162
+ for (let i = members.length - displayCount; i < members.length; i++) {
1163
+ const member = members[i];
1164
+ const date = member.created_at ? new Date(member.created_at).toLocaleDateString() : '';
1165
+ const title = member.title?.substring(0, 30) || 'Untitled';
1166
+ const isCurrent = i === members.length - 1;
1167
+ const marker = isCurrent ? ' ← Current' : '';
1168
+ timelineEntries.push(`${date} ─●─ ${member.session_id} (${title})${marker}`);
1169
+ }
1170
+
1171
+ timeline = `\`\`\`\n${timelineEntries.join('\n │\n')}\n\`\`\``;
1172
+ }
1173
+
1174
+ return `<ccw-session-context>
1175
+ ## 📋 Related Sessions Index
1176
+
1177
+ ### 🔗 Active Cluster: ${activeCluster.name} (${members.length} sessions)
1178
+ **Intent**: ${activeCluster.intent || 'No intent specified'}
1179
+
1180
+ ${table}
1181
+
1182
+ **Resume via MCP**:
1183
+ \`\`\`
1184
+ Use tool: mcp__ccw-tools__core_memory
1185
+ Parameters: { "action": "load", "id": "${members[members.length - 1].session_id}" }
1186
+
1187
+ Or load entire cluster:
1188
+ { "action": "load-cluster", "clusterId": "${activeCluster.id}" }
1189
+ \`\`\`
1190
+
1191
+ ### 📊 Timeline
1192
+ ${timeline}
1193
+
1194
+ ---
1195
+ **Tip**: Use \`mcp__ccw-tools__core_memory({ action: "search", query: "<keyword>" })\` to find more sessions
1196
+ </ccw-session-context>`;
1197
+ }
1198
+
1199
+ /**
1200
+ * Parse workflow session files
1201
+ */
1202
+ private async parseWorkflowSessions(): Promise<SessionMetadataCache[]> {
1203
+ const sessions: SessionMetadataCache[] = [];
1204
+ const workflowDir = join(this.projectPath, '.workflow', 'sessions');
1205
+
1206
+ if (!existsSync(workflowDir)) {
1207
+ return sessions;
1208
+ }
1209
+
1210
+ try {
1211
+ const sessionDirs = readdirSync(workflowDir).filter(d => d.startsWith('WFS-'));
1212
+
1213
+ for (const sessionDir of sessionDirs) {
1214
+ const sessionFile = join(workflowDir, sessionDir, 'session.json');
1215
+ if (!existsSync(sessionFile)) continue;
1216
+
1217
+ try {
1218
+ const content = readFileSync(sessionFile, 'utf8');
1219
+ const sessionData = JSON.parse(content);
1220
+
1221
+ const metadata: SessionMetadataCache = {
1222
+ session_id: sessionDir,
1223
+ session_type: 'workflow',
1224
+ title: sessionData.title || sessionDir,
1225
+ summary: (sessionData.description || '').substring(0, 200),
1226
+ keywords: this.extractKeywords(JSON.stringify(sessionData)),
1227
+ token_estimate: Math.ceil(JSON.stringify(sessionData).length / 4),
1228
+ file_patterns: this.extractFilePatterns(JSON.stringify(sessionData)),
1229
+ created_at: sessionData.created_at || statSync(sessionFile).mtime.toISOString(),
1230
+ last_accessed: new Date().toISOString(),
1231
+ access_count: 0
1232
+ };
1233
+
1234
+ sessions.push(metadata);
1235
+ } catch (err) {
1236
+ console.warn(`[Clustering] Failed to parse ${sessionFile}:`, err);
1237
+ }
1238
+ }
1239
+ } catch (err) {
1240
+ console.warn('[Clustering] Failed to read workflow sessions:', err);
1241
+ }
1242
+
1243
+ return sessions;
1244
+ }
1245
+
1246
+ /**
1247
+ * Update metadata cache for all sessions
1248
+ */
1249
+ async refreshMetadataCache(): Promise<number> {
1250
+ const sessions = await this.collectSessions({ scope: 'all' });
1251
+
1252
+ for (const session of sessions) {
1253
+ this.coreMemoryStore.upsertSessionMetadata(session);
1254
+ }
1255
+
1256
+ return sessions.length;
1257
+ }
1258
+ }