claude-code-workflow 6.2.2 → 6.2.4

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