modular-studio 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +122 -122
  2. package/dist/assets/Badge-Bsy2H_p2.js +1 -0
  3. package/dist/assets/GraphPanel-D4X3faxA.js +47 -0
  4. package/dist/assets/{Input-Bgp734xs.js → Input-Dyb88Erk.js} +1 -1
  5. package/dist/assets/KnowledgeTab-BccWz7Np.js +5 -0
  6. package/dist/assets/MemoryTab-Y_66cE01.js +16 -0
  7. package/dist/assets/QualificationTab-Dm9dEIpM.js +1 -0
  8. package/dist/assets/ReviewTab-BrfXSyyf.js +104 -0
  9. package/dist/assets/{Section-DoJrmytO.js → Section-68XDCFTl.js} +1 -1
  10. package/dist/assets/TestTab-CLKRT63X.js +42 -0
  11. package/dist/assets/ToolsTab-xumi9Uds.js +1 -0
  12. package/dist/assets/icons-CS8RUPBi.js +1 -0
  13. package/dist/assets/index-B2bm0161.css +1 -0
  14. package/dist/assets/index-C626nWuA.js +422 -0
  15. package/dist/assets/services-BDk6yY4o.js +369 -0
  16. package/dist/index.html +18 -18
  17. package/dist-server/bin/modular-mcp.js +1 -0
  18. package/dist-server/server/index.d.ts.map +1 -1
  19. package/dist-server/server/index.js +34 -0
  20. package/dist-server/server/mcp/manager.d.ts +3 -0
  21. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  22. package/dist-server/server/mcp/manager.js +80 -5
  23. package/dist-server/server/migrations/index.d.ts +11 -0
  24. package/dist-server/server/migrations/index.d.ts.map +1 -0
  25. package/dist-server/server/migrations/index.js +57 -0
  26. package/dist-server/server/routes/agents.d.ts.map +1 -1
  27. package/dist-server/server/routes/agents.js +27 -0
  28. package/dist-server/server/routes/analytics.d.ts +3 -0
  29. package/dist-server/server/routes/analytics.d.ts.map +1 -0
  30. package/dist-server/server/routes/analytics.js +24 -0
  31. package/dist-server/server/routes/cache.d.ts +3 -0
  32. package/dist-server/server/routes/cache.d.ts.map +1 -0
  33. package/dist-server/server/routes/cache.js +55 -0
  34. package/dist-server/server/routes/connectors/airtable.d.ts +7 -0
  35. package/dist-server/server/routes/connectors/airtable.d.ts.map +1 -0
  36. package/dist-server/server/routes/connectors/airtable.js +119 -0
  37. package/dist-server/server/routes/connectors/confluence.d.ts +7 -0
  38. package/dist-server/server/routes/connectors/confluence.d.ts.map +1 -0
  39. package/dist-server/server/routes/connectors/confluence.js +176 -0
  40. package/dist-server/server/routes/connectors/github.d.ts +7 -0
  41. package/dist-server/server/routes/connectors/github.d.ts.map +1 -0
  42. package/dist-server/server/routes/connectors/github.js +195 -0
  43. package/dist-server/server/routes/connectors/gmail.d.ts +7 -0
  44. package/dist-server/server/routes/connectors/gmail.d.ts.map +1 -0
  45. package/dist-server/server/routes/connectors/gmail.js +115 -0
  46. package/dist-server/server/routes/connectors/google-docs.d.ts +10 -0
  47. package/dist-server/server/routes/connectors/google-docs.d.ts.map +1 -0
  48. package/dist-server/server/routes/connectors/google-docs.js +165 -0
  49. package/dist-server/server/routes/connectors/google-drive.d.ts +7 -0
  50. package/dist-server/server/routes/connectors/google-drive.d.ts.map +1 -0
  51. package/dist-server/server/routes/connectors/google-drive.js +163 -0
  52. package/dist-server/server/routes/connectors/google-sheets.d.ts +7 -0
  53. package/dist-server/server/routes/connectors/google-sheets.d.ts.map +1 -0
  54. package/dist-server/server/routes/connectors/google-sheets.js +90 -0
  55. package/dist-server/server/routes/connectors/hubspot.d.ts +7 -0
  56. package/dist-server/server/routes/connectors/hubspot.d.ts.map +1 -0
  57. package/dist-server/server/routes/connectors/hubspot.js +134 -0
  58. package/dist-server/server/routes/connectors/index.d.ts +6 -0
  59. package/dist-server/server/routes/connectors/index.d.ts.map +1 -0
  60. package/dist-server/server/routes/connectors/index.js +38 -0
  61. package/dist-server/server/routes/connectors/jira.d.ts +7 -0
  62. package/dist-server/server/routes/connectors/jira.d.ts.map +1 -0
  63. package/dist-server/server/routes/connectors/jira.js +151 -0
  64. package/dist-server/server/routes/connectors/linear.d.ts +7 -0
  65. package/dist-server/server/routes/connectors/linear.d.ts.map +1 -0
  66. package/dist-server/server/routes/connectors/linear.js +154 -0
  67. package/dist-server/server/routes/connectors/notion.d.ts +10 -0
  68. package/dist-server/server/routes/connectors/notion.d.ts.map +1 -0
  69. package/dist-server/server/routes/connectors/notion.js +201 -0
  70. package/dist-server/server/routes/connectors/plane.d.ts +10 -0
  71. package/dist-server/server/routes/connectors/plane.d.ts.map +1 -0
  72. package/dist-server/server/routes/connectors/plane.js +189 -0
  73. package/dist-server/server/routes/connectors/shared.d.ts +25 -0
  74. package/dist-server/server/routes/connectors/shared.d.ts.map +1 -0
  75. package/dist-server/server/routes/connectors/shared.js +202 -0
  76. package/dist-server/server/routes/connectors/slack.d.ts +7 -0
  77. package/dist-server/server/routes/connectors/slack.d.ts.map +1 -0
  78. package/dist-server/server/routes/connectors/slack.js +153 -0
  79. package/dist-server/server/routes/connectors.d.ts.map +1 -1
  80. package/dist-server/server/routes/connectors.js +47 -17
  81. package/dist-server/server/routes/cost.d.ts +3 -0
  82. package/dist-server/server/routes/cost.d.ts.map +1 -0
  83. package/dist-server/server/routes/cost.js +113 -0
  84. package/dist-server/server/routes/graph.d.ts +11 -0
  85. package/dist-server/server/routes/graph.d.ts.map +1 -0
  86. package/dist-server/server/routes/graph.js +213 -0
  87. package/dist-server/server/routes/lessons.d.ts +3 -0
  88. package/dist-server/server/routes/lessons.d.ts.map +1 -0
  89. package/dist-server/server/routes/lessons.js +160 -0
  90. package/dist-server/server/routes/llm.d.ts.map +1 -1
  91. package/dist-server/server/routes/llm.js +85 -18
  92. package/dist-server/server/routes/memory.d.ts.map +1 -1
  93. package/dist-server/server/routes/memory.js +31 -0
  94. package/dist-server/server/routes/metaprompt-v2.d.ts +3 -0
  95. package/dist-server/server/routes/metaprompt-v2.d.ts.map +1 -0
  96. package/dist-server/server/routes/metaprompt-v2.js +104 -0
  97. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  98. package/dist-server/server/routes/qualification.js +342 -334
  99. package/dist-server/server/routes/repo-index.d.ts.map +1 -1
  100. package/dist-server/server/routes/repo-index.js +7 -0
  101. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  102. package/dist-server/server/routes/skills-search.js +192 -26
  103. package/dist-server/server/routes/tool-analytics.d.ts +3 -0
  104. package/dist-server/server/routes/tool-analytics.d.ts.map +1 -0
  105. package/dist-server/server/routes/tool-analytics.js +47 -0
  106. package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
  107. package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
  108. package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
  109. package/dist-server/server/services/adapters/postgresAdapter.js +30 -30
  110. package/dist-server/server/services/adapters/sqliteAdapter.d.ts +1 -0
  111. package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -1
  112. package/dist-server/server/services/adapters/sqliteAdapter.js +66 -36
  113. package/dist-server/server/services/agentStore.d.ts +2 -1
  114. package/dist-server/server/services/agentStore.d.ts.map +1 -1
  115. package/dist-server/server/services/agentStore.js +2 -1
  116. package/dist-server/server/services/correctionDetector.d.ts +22 -0
  117. package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
  118. package/dist-server/server/services/correctionDetector.js +91 -0
  119. package/dist-server/server/services/credentialStore.d.ts +10 -0
  120. package/dist-server/server/services/credentialStore.d.ts.map +1 -0
  121. package/dist-server/server/services/credentialStore.js +123 -0
  122. package/dist-server/server/services/hindsightClient.d.ts +15 -0
  123. package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
  124. package/dist-server/server/services/hindsightClient.js +48 -0
  125. package/dist-server/server/services/lessonExtractor.d.ts +21 -0
  126. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
  127. package/dist-server/server/services/lessonExtractor.js +92 -0
  128. package/dist-server/server/services/repoIndexer.d.ts +7 -1
  129. package/dist-server/server/services/repoIndexer.d.ts.map +1 -1
  130. package/dist-server/server/services/repoIndexer.js +295 -94
  131. package/dist-server/server/services/responseCache.d.ts +24 -0
  132. package/dist-server/server/services/responseCache.d.ts.map +1 -0
  133. package/dist-server/server/services/responseCache.js +163 -0
  134. package/dist-server/server/services/sqliteStore.d.ts +72 -0
  135. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  136. package/dist-server/server/services/sqliteStore.js +291 -13
  137. package/dist-server/src/config.d.ts +2 -0
  138. package/dist-server/src/config.d.ts.map +1 -0
  139. package/dist-server/src/config.js +3 -0
  140. package/dist-server/src/graph/db.d.ts +46 -0
  141. package/dist-server/src/graph/db.d.ts.map +1 -0
  142. package/dist-server/src/graph/db.js +241 -0
  143. package/dist-server/src/graph/extractors/code.d.ts +12 -0
  144. package/dist-server/src/graph/extractors/code.d.ts.map +1 -0
  145. package/dist-server/src/graph/extractors/code.js +239 -0
  146. package/dist-server/src/graph/extractors/cross-type.d.ts +16 -0
  147. package/dist-server/src/graph/extractors/cross-type.d.ts.map +1 -0
  148. package/dist-server/src/graph/extractors/cross-type.js +67 -0
  149. package/dist-server/src/graph/extractors/markdown.d.ts +29 -0
  150. package/dist-server/src/graph/extractors/markdown.d.ts.map +1 -0
  151. package/dist-server/src/graph/extractors/markdown.js +224 -0
  152. package/dist-server/src/graph/extractors/yaml.d.ts +15 -0
  153. package/dist-server/src/graph/extractors/yaml.d.ts.map +1 -0
  154. package/dist-server/src/graph/extractors/yaml.js +104 -0
  155. package/dist-server/src/graph/index.d.ts +62 -0
  156. package/dist-server/src/graph/index.d.ts.map +1 -0
  157. package/dist-server/src/graph/index.js +67 -0
  158. package/dist-server/src/graph/packer.d.ts +19 -0
  159. package/dist-server/src/graph/packer.d.ts.map +1 -0
  160. package/dist-server/src/graph/packer.js +134 -0
  161. package/dist-server/src/graph/resolver.d.ts +12 -0
  162. package/dist-server/src/graph/resolver.d.ts.map +1 -0
  163. package/dist-server/src/graph/resolver.js +81 -0
  164. package/dist-server/src/graph/scanner.d.ts +34 -0
  165. package/dist-server/src/graph/scanner.d.ts.map +1 -0
  166. package/dist-server/src/graph/scanner.js +252 -0
  167. package/dist-server/src/graph/traverser.d.ts +17 -0
  168. package/dist-server/src/graph/traverser.d.ts.map +1 -0
  169. package/dist-server/src/graph/traverser.js +185 -0
  170. package/dist-server/src/graph/types.d.ts +117 -0
  171. package/dist-server/src/graph/types.d.ts.map +1 -0
  172. package/dist-server/src/graph/types.js +63 -0
  173. package/dist-server/src/metaprompt/v2/assembler.d.ts +3 -0
  174. package/dist-server/src/metaprompt/v2/assembler.d.ts.map +1 -0
  175. package/dist-server/src/metaprompt/v2/assembler.js +261 -0
  176. package/dist-server/src/metaprompt/v2/context-strategist.d.ts +3 -0
  177. package/dist-server/src/metaprompt/v2/context-strategist.d.ts.map +1 -0
  178. package/dist-server/src/metaprompt/v2/context-strategist.js +173 -0
  179. package/dist-server/src/metaprompt/v2/evaluator.d.ts +3 -0
  180. package/dist-server/src/metaprompt/v2/evaluator.d.ts.map +1 -0
  181. package/dist-server/src/metaprompt/v2/evaluator.js +281 -0
  182. package/dist-server/src/metaprompt/v2/index.d.ts +41 -0
  183. package/dist-server/src/metaprompt/v2/index.d.ts.map +1 -0
  184. package/dist-server/src/metaprompt/v2/index.js +90 -0
  185. package/dist-server/src/metaprompt/v2/parser.d.ts +3 -0
  186. package/dist-server/src/metaprompt/v2/parser.d.ts.map +1 -0
  187. package/dist-server/src/metaprompt/v2/parser.js +138 -0
  188. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts +3 -0
  189. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts.map +1 -0
  190. package/dist-server/src/metaprompt/v2/pattern-selector.js +154 -0
  191. package/dist-server/src/metaprompt/v2/researcher.d.ts +3 -0
  192. package/dist-server/src/metaprompt/v2/researcher.d.ts.map +1 -0
  193. package/dist-server/src/metaprompt/v2/researcher.js +194 -0
  194. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts +74 -0
  195. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts.map +1 -0
  196. package/dist-server/src/metaprompt/v2/tool-discovery.js +290 -0
  197. package/dist-server/src/metaprompt/v2/types.d.ts +154 -0
  198. package/dist-server/src/metaprompt/v2/types.d.ts.map +1 -0
  199. package/dist-server/src/metaprompt/v2/types.js +2 -0
  200. package/dist-server/src/services/contradictionDetector.js +1 -1
  201. package/dist-server/src/services/llmService.d.ts +61 -0
  202. package/dist-server/src/services/llmService.d.ts.map +1 -0
  203. package/dist-server/src/services/llmService.js +222 -0
  204. package/dist-server/src/store/knowledgeBase.d.ts +6 -1
  205. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  206. package/dist-server/src/store/knowledgeBase.js +0 -1
  207. package/dist-server/src/store/lessonStore.d.ts +26 -0
  208. package/dist-server/src/store/lessonStore.d.ts.map +1 -0
  209. package/dist-server/src/store/lessonStore.js +64 -0
  210. package/dist-server/src/store/mcp-registry.d.ts +29 -0
  211. package/dist-server/src/store/mcp-registry.d.ts.map +1 -0
  212. package/dist-server/src/store/mcp-registry.js +1303 -0
  213. package/dist-server/src/store/memoryStore.d.ts +12 -1
  214. package/dist-server/src/store/memoryStore.d.ts.map +1 -1
  215. package/dist-server/src/store/memoryStore.js +9 -0
  216. package/dist-server/src/types/registry.types.d.ts +13 -0
  217. package/dist-server/src/types/registry.types.d.ts.map +1 -0
  218. package/dist-server/src/types/registry.types.js +2 -0
  219. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  220. package/package.json +15 -1
  221. package/scripts/cleanup-worktrees.ps1 +29 -0
  222. package/dist/assets/Badge-22Ai0eyi.js +0 -1
  223. package/dist/assets/KnowledgeTab-DABxirZh.js +0 -4
  224. package/dist/assets/MemoryTab-DZeYElIT.js +0 -16
  225. package/dist/assets/QualificationTab-Dfpy3J30.js +0 -1
  226. package/dist/assets/ReviewTab-SD8lQuCc.js +0 -103
  227. package/dist/assets/TestTab-PDyMF8Fw.js +0 -33
  228. package/dist/assets/ToolsTab-B83qGCmG.js +0 -1
  229. package/dist/assets/icons-C2EV-le6.js +0 -1
  230. package/dist/assets/index-DkpMAxX7.css +0 -1
  231. package/dist/assets/index-q24ug5Qs.js +0 -143
  232. package/dist/assets/services-BaKotDf0.js +0 -343
@@ -1 +1 @@
1
- {"version":3,"file":"repoIndexer.d.ts","sourceRoot":"","sources":["../../../server/services/repoIndexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,OAAO,GACP,SAAS,GACT,MAAM,GACN,OAAO,GACP,QAAQ,GACR,MAAM,GACN,MAAM,GACN,OAAO,GACP,KAAK,GACL,QAAQ,GACR,OAAO,CAAC;AAEZ,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AA6RD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAoErD;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAsD1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CA+D/E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAezE"}
1
+ {"version":3,"file":"repoIndexer.d.ts","sourceRoot":"","sources":["../../../server/services/repoIndexer.ts"],"names":[],"mappings":"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"}
@@ -34,6 +34,22 @@ const CODE_EXTENSIONS = new Set([
34
34
  ]);
35
35
  const MAX_FILE_SIZE = 100_000; // 100KB
36
36
  const MAX_FILES = 2000;
37
+ // ── Feature Clustering Constants ──
38
+ const GENERIC_DIRS = new Set([
39
+ 'src', 'lib', 'app', 'server', 'client', 'api',
40
+ 'services', 'service', 'store', 'stores', 'state',
41
+ 'components', 'component', 'pages', 'page', 'views', 'view',
42
+ 'routes', 'route', 'controllers', 'controller',
43
+ 'utils', 'util', 'helpers', 'helper', 'common', 'shared',
44
+ 'models', 'model', 'types', 'interfaces',
45
+ 'panels', 'panel', 'tabs', 'tab', 'layouts', 'layout',
46
+ 'middleware', 'middlewares', 'guards', 'pipes',
47
+ 'config', 'configs', 'constants',
48
+ 'hooks', 'composables', 'providers',
49
+ 'assets', 'static', 'public',
50
+ '__tests__', '__mocks__', 'test', 'tests', 'spec', 'specs',
51
+ ]);
52
+ const FUNCTIONAL_SUFFIXES = /(?:Service|Store|Controller|Route|Router|Form|Panel|Component|View|Page|Tab|Utils?|Helpers?|Handler|Manager|Provider|Factory|Adapter|Middleware|Guard|Pipe|Module|Config|Spec|Test|Mock|Fixture|Schema|Model|Entity|DTO|Repository|Gateway|Client|Api|Hook|Composable|Plugin|Decorator|Interceptor|Filter|Resolver|Validator|Transformer|Mapper|Builder|Strategy|Observer|Subscriber|Listener|Worker|Job|Task|Queue|Cache|Logger|Monitor|Migration|Seed|Index)$/;
37
53
  // ── Scanner ──
38
54
  function categorizeFile(path, content) {
39
55
  const base = basename(path).toLowerCase();
@@ -218,62 +234,259 @@ function detectConventions(files) {
218
234
  }
219
235
  return conventions;
220
236
  }
221
- /**
222
- * Cluster files into feature groups based on import relationships and directory structure.
223
- */
224
- function clusterFeatures(files, modules) {
225
- const features = [];
226
- // Group by top-level directory as initial clusters
227
- const dirGroups = new Map();
237
+ // ── Feature Clustering Helpers ──
238
+ function extractDomainStem(filePath) {
239
+ const parts = filePath.replace(/\\/g, '/').split('/');
240
+ const fileName = parts[parts.length - 1];
241
+ const baseName = fileName.replace(/\.[^.]+$/, ''); // strip extension
242
+ // Find last non-generic directory
243
+ let specificDir = '';
244
+ for (let i = parts.length - 2; i >= 0; i--) {
245
+ const dir = parts[i].toLowerCase().replace(/[-_]/g, '');
246
+ if (!GENERIC_DIRS.has(dir) && dir.length > 1) {
247
+ specificDir = parts[i];
248
+ break;
249
+ }
250
+ }
251
+ // Strip functional suffix from filename
252
+ const strippedName = baseName.replace(FUNCTIONAL_SUFFIXES, '');
253
+ // If filename is generic (index, main, app, types, utils), use directory
254
+ const GENERIC_NAMES = new Set(['index', 'main', 'app', 'types', 'utils', 'helpers', 'constants', 'config', 'mod']);
255
+ const nameToUse = GENERIC_NAMES.has(strippedName.toLowerCase()) && specificDir
256
+ ? specificDir
257
+ : strippedName;
258
+ // Normalize: camelCase/PascalCase → kebab → lower
259
+ return nameToUse
260
+ .replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase → kebab
261
+ .replace(/[-_]+/g, '-')
262
+ .replace(/\bv\d+$/, '') // strip version suffix (v2, v3)
263
+ .toLowerCase()
264
+ .replace(/-+$/, '');
265
+ }
266
+ function resolveImportPath(fromFile, importPath, allFiles) {
267
+ if (!importPath.startsWith('.'))
268
+ return null; // skip node_modules
269
+ const fromDir = fromFile.split('/').slice(0, -1).join('/');
270
+ let resolved = importPath;
271
+ // Resolve relative path
272
+ if (resolved.startsWith('./'))
273
+ resolved = resolved.slice(2);
274
+ if (resolved.startsWith('../')) {
275
+ const parts = fromDir.split('/');
276
+ for (const seg of resolved.split('/')) {
277
+ if (seg === '..')
278
+ parts.pop();
279
+ else if (seg !== '.')
280
+ parts.push(seg);
281
+ }
282
+ resolved = parts.join('/');
283
+ }
284
+ else {
285
+ resolved = fromDir ? fromDir + '/' + resolved : resolved;
286
+ }
287
+ // Try exact match, then with extensions
288
+ const candidates = [resolved, resolved + '.ts', resolved + '.tsx', resolved + '.js', resolved + '.jsx', resolved + '/index.ts', resolved + '/index.js'];
289
+ for (const c of candidates) {
290
+ if (allFiles.some(f => f.path === c))
291
+ return c;
292
+ }
293
+ return null;
294
+ }
295
+ function humanizeStem(stem) {
296
+ return stem
297
+ .replace(/-/g, ' ')
298
+ .replace(/\b\w/g, c => c.toUpperCase());
299
+ }
300
+ function clusterByDomainAndImports(files) {
301
+ // A) Seed clusters by domain stem
302
+ const stemMap = new Map();
228
303
  for (const f of files) {
229
- if (f.category === 'test' || f.category === 'config' || f.category === 'style')
304
+ if (f.category === 'config' || f.category === 'style')
230
305
  continue;
231
- const topDir = f.path.split('/')[0] || 'root';
232
- if (!dirGroups.has(topDir))
233
- dirGroups.set(topDir, []);
234
- dirGroups.get(topDir).push(f);
306
+ const stem = extractDomainStem(f.path);
307
+ if (!stemMap.has(stem))
308
+ stemMap.set(stem, []);
309
+ stemMap.get(stem).push(f);
235
310
  }
236
- // For each meaningful group, create a feature
237
- for (const [dir, groupFiles] of dirGroups) {
238
- if (groupFiles.length < 2)
311
+ // B) Build reverse import index: file path → which files import it
312
+ const importedBy = new Map();
313
+ for (const f of files) {
314
+ for (const imp of f.imports) {
315
+ const resolved = resolveImportPath(f.path, imp, files);
316
+ if (!resolved)
317
+ continue;
318
+ if (!importedBy.has(resolved))
319
+ importedBy.set(resolved, new Set());
320
+ importedBy.get(resolved).add(f.path);
321
+ }
322
+ }
323
+ // C) Absorb orphans: files in clusters of size 1
324
+ // If imported by ≥2 files from the same cluster → join that cluster
325
+ const orphanStems = [...stemMap.entries()].filter(([, fs]) => fs.length === 1);
326
+ for (const [stem, [orphan]] of orphanStems) {
327
+ const importers = importedBy.get(orphan.path);
328
+ if (!importers)
239
329
  continue;
240
- const stores = groupFiles.filter(f => f.category === 'store');
241
- const routes = groupFiles.filter(f => f.category === 'route');
242
- const components = groupFiles.filter(f => f.category === 'component');
243
- // const services = groupFiles.filter(f => f.category === 'service');
244
- // Build import graph within feature
330
+ // Count how many files from each OTHER cluster import this orphan
331
+ const clusterHits = new Map();
332
+ for (const importerPath of importers) {
333
+ for (const [otherStem, otherFiles] of stemMap) {
334
+ if (otherStem === stem)
335
+ continue;
336
+ if (otherFiles.some(f => f.path === importerPath)) {
337
+ clusterHits.set(otherStem, (clusterHits.get(otherStem) ?? 0) + 1);
338
+ }
339
+ }
340
+ }
341
+ // Join the cluster with most imports (≥2 threshold)
342
+ let bestCluster = '';
343
+ let bestCount = 1; // threshold: needs ≥2
344
+ for (const [cs, count] of clusterHits) {
345
+ if (count > bestCount) {
346
+ bestCluster = cs;
347
+ bestCount = count;
348
+ }
349
+ }
350
+ if (bestCluster) {
351
+ stemMap.get(bestCluster).push(orphan);
352
+ stemMap.delete(stem);
353
+ }
354
+ }
355
+ return stemMap;
356
+ }
357
+ function buildFeatures(stemMap, allFiles) {
358
+ const features = [];
359
+ const sharedFiles = [];
360
+ // A) Merge micro-clusters (<3 files)
361
+ const microStems = [...stemMap.entries()].filter(([, fs]) => fs.length < 3);
362
+ for (const [stem, fs] of microStems) {
363
+ // Find most-connected larger cluster
364
+ let bestTarget = '';
365
+ let bestConnections = 0;
366
+ for (const [otherStem, otherFiles] of stemMap) {
367
+ if (otherStem === stem || otherFiles.length < 3)
368
+ continue;
369
+ let connections = 0;
370
+ for (const f of fs) {
371
+ for (const imp of f.imports) {
372
+ const resolved = resolveImportPath(f.path, imp, allFiles);
373
+ if (resolved && otherFiles.some(of => of.path === resolved))
374
+ connections++;
375
+ }
376
+ }
377
+ if (connections > bestConnections) {
378
+ bestTarget = otherStem;
379
+ bestConnections = connections;
380
+ }
381
+ }
382
+ if (bestTarget) {
383
+ stemMap.get(bestTarget).push(...fs);
384
+ }
385
+ else {
386
+ sharedFiles.push(...fs);
387
+ }
388
+ stemMap.delete(stem);
389
+ }
390
+ // Add "shared" cluster if any orphans remain
391
+ if (sharedFiles.length > 0) {
392
+ stemMap.set('shared', sharedFiles);
393
+ }
394
+ // B) Build RepoFeature for each cluster
395
+ for (const [stem, clusterFiles] of stemMap) {
396
+ if (clusterFiles.length === 0)
397
+ continue;
398
+ // Compute import fan-in within cluster
399
+ const fanIn = new Map();
400
+ for (const f of clusterFiles) {
401
+ for (const imp of f.imports) {
402
+ const resolved = resolveImportPath(f.path, imp, allFiles);
403
+ if (resolved && clusterFiles.some(cf => cf.path === resolved)) {
404
+ fanIn.set(resolved, (fanIn.get(resolved) ?? 0) + 1);
405
+ }
406
+ }
407
+ }
408
+ const keyFiles = [...clusterFiles]
409
+ .sort((a, b) => (fanIn.get(b.path) ?? 0) - (fanIn.get(a.path) ?? 0))
410
+ .slice(0, 5)
411
+ .map(f => f.path);
412
+ // Internal imports
245
413
  const internalImports = new Map();
246
- for (const f of groupFiles) {
247
- const deps = f.imports.filter(imp => !imp.startsWith('@') && !imp.startsWith('node:') && imp.startsWith('.'));
414
+ for (const f of clusterFiles) {
415
+ const deps = f.imports
416
+ .map(imp => resolveImportPath(f.path, imp, allFiles))
417
+ .filter((r) => r !== null && clusterFiles.some(cf => cf.path === r));
248
418
  if (deps.length > 0)
249
419
  internalImports.set(f.path, deps);
250
420
  }
251
- // Find key files (most imported within the group)
252
- const importCounts = new Map();
253
- for (const f of groupFiles) {
421
+ // Cross-feature deps
422
+ const crossDeps = new Set();
423
+ for (const f of clusterFiles) {
254
424
  for (const imp of f.imports) {
255
- const resolved = imp.replace(/^\.\//, `${dir}/`);
256
- importCounts.set(resolved, (importCounts.get(resolved) || 0) + 1);
425
+ const resolved = resolveImportPath(f.path, imp, allFiles);
426
+ if (!resolved)
427
+ continue;
428
+ if (clusterFiles.some(cf => cf.path === resolved))
429
+ continue; // internal
430
+ // Find which feature owns this file
431
+ for (const [otherStem, otherFiles] of stemMap) {
432
+ if (otherStem === stem)
433
+ continue;
434
+ if (otherFiles.some(of => of.path === resolved)) {
435
+ crossDeps.add(humanizeStem(otherStem));
436
+ }
437
+ }
257
438
  }
258
439
  }
259
- const keyFiles = groupFiles
260
- .sort((a, b) => (importCounts.get(b.path) || 0) - (importCounts.get(a.path) || 0))
261
- .slice(0, 10)
262
- .map(f => f.path);
263
- const feature = {
264
- name: humanizeDirName(dir),
265
- description: '', // filled by LLM later
266
- modules: modules.filter(m => m.path.startsWith(dir)).map(m => m.path),
440
+ features.push({
441
+ name: humanizeStem(stem),
442
+ domainStem: stem,
443
+ description: '',
444
+ files: clusterFiles.map(f => f.path),
267
445
  keyFiles,
268
- stores: stores.map(f => f.path),
269
- routes: routes.map(f => f.path),
270
- components: components.map(f => f.path),
446
+ stores: clusterFiles.filter(f => f.category === 'store').map(f => f.path),
447
+ routes: clusterFiles.filter(f => f.category === 'route').map(f => f.path),
448
+ components: clusterFiles.filter(f => f.category === 'component').map(f => f.path),
449
+ services: clusterFiles.filter(f => f.category === 'service').map(f => f.path),
450
+ tests: clusterFiles.filter(f => f.category === 'test').map(f => f.path),
271
451
  imports: internalImports,
452
+ crossFeatureDeps: [...crossDeps],
453
+ fileCount: clusterFiles.length,
454
+ tokenCount: clusterFiles.reduce((sum, f) => sum + f.tokens, 0),
455
+ });
456
+ }
457
+ // Cap at 50 features, merge smallest
458
+ features.sort((a, b) => b.fileCount - a.fileCount);
459
+ if (features.length > 50) {
460
+ const kept = features.slice(0, 49);
461
+ const merged = features.slice(49);
462
+ const overflow = {
463
+ name: 'Other',
464
+ domainStem: 'other',
465
+ description: `${merged.length} small feature clusters merged`,
466
+ files: merged.flatMap(f => f.files),
467
+ keyFiles: merged.flatMap(f => f.keyFiles).slice(0, 5),
468
+ stores: merged.flatMap(f => f.stores),
469
+ routes: merged.flatMap(f => f.routes),
470
+ components: merged.flatMap(f => f.components),
471
+ services: merged.flatMap(f => f.services),
472
+ tests: merged.flatMap(f => f.tests),
473
+ imports: new Map(),
474
+ crossFeatureDeps: [],
475
+ fileCount: merged.reduce((s, f) => s + f.fileCount, 0),
476
+ tokenCount: merged.reduce((s, f) => s + f.tokenCount, 0),
272
477
  };
273
- features.push(feature);
478
+ kept.push(overflow);
479
+ return kept;
274
480
  }
275
481
  return features;
276
482
  }
483
+ /**
484
+ * Cluster files into feature groups using a 3-pass domain+import algorithm.
485
+ */
486
+ function clusterFeatures(files, _modules) {
487
+ const stemMap = clusterByDomainAndImports(files);
488
+ return buildFeatures(stemMap, files);
489
+ }
277
490
  function humanizeDirName(dir) {
278
491
  return dir
279
492
  .replace(/^src\//, '')
@@ -415,16 +628,15 @@ export function generateOverviewDoc(scan) {
415
628
  }
416
629
  }
417
630
  // Feature list
418
- lines.push(`## Features`);
631
+ lines.push(`## Features (${scan.features.length} clusters)`);
632
+ lines.push('');
419
633
  for (const f of scan.features) {
420
- lines.push(`### ${f.name}`);
421
- lines.push(`Key files: ${f.keyFiles.slice(0, 5).join(', ')}`);
422
- if (f.stores.length)
423
- lines.push(`Stores: ${f.stores.join(', ')}`);
424
- if (f.components.length)
425
- lines.push(`Components: ${f.components.length} files`);
426
- if (f.routes.length)
427
- lines.push(`Routes: ${f.routes.join(', ')}`);
634
+ const tokK = (f.tokenCount / 1000).toFixed(1);
635
+ lines.push(`### ${f.name} (${f.fileCount} files · ~${tokK}K tokens)`);
636
+ if (f.keyFiles.length > 0)
637
+ lines.push(`Key: ${f.keyFiles.map(p => p.split('/').pop()).join(', ')}`);
638
+ if (f.crossFeatureDeps.length > 0)
639
+ lines.push(`Deps: ${f.crossFeatureDeps.join(', ')}`);
428
640
  lines.push('');
429
641
  }
430
642
  return lines.join('\n');
@@ -434,59 +646,48 @@ export function generateOverviewDoc(scan) {
434
646
  */
435
647
  export function generateFeatureDoc(scan, feature) {
436
648
  const lines = [];
649
+ const tokK = (feature.tokenCount / 1000).toFixed(1);
437
650
  lines.push(`# Feature: ${feature.name}`);
651
+ lines.push(`${feature.fileCount} files · ~${tokK}K tokens`);
438
652
  lines.push('');
653
+ // Key Files
654
+ if (feature.keyFiles.length > 0) {
655
+ lines.push(`## Key Files`);
656
+ for (const fp of feature.keyFiles) {
657
+ const file = scan.files.find(f => f.path === fp);
658
+ if (file) {
659
+ const symbols = [...file.exports, ...file.functions.slice(0, 5)].filter(Boolean).slice(0, 8);
660
+ const symStr = symbols.length > 0 ? ` — ${symbols.join(', ')}` : '';
661
+ lines.push(`- ${basename(fp)} (${file.category})${symStr}`);
662
+ }
663
+ }
664
+ lines.push('');
665
+ }
666
+ // Architecture
439
667
  lines.push(`## Architecture`);
440
- lines.push(`This feature spans ${feature.keyFiles.length} key files across ${feature.modules.length} module(s).`);
668
+ if (feature.stores.length > 0)
669
+ lines.push(`- Stores: ${feature.stores.map(p => basename(p)).join(', ')}`);
670
+ if (feature.routes.length > 0)
671
+ lines.push(`- Routes: ${feature.routes.map(p => basename(p)).join(', ')}`);
672
+ if (feature.services.length > 0)
673
+ lines.push(`- Services: ${feature.services.map(p => basename(p)).join(', ')}`);
674
+ if (feature.components.length > 0)
675
+ lines.push(`- Components: ${feature.components.map(p => basename(p)).join(', ')}`);
676
+ if (feature.tests.length > 0)
677
+ lines.push(`- Tests: ${feature.tests.map(p => basename(p)).join(', ')}`);
441
678
  lines.push('');
442
- lines.push(`## Key Files`);
443
- for (const fp of feature.keyFiles) {
444
- const file = scan.files.find(f => f.path === fp);
445
- if (file) {
446
- const symbols = [...file.exports, ...file.functions.slice(0, 5)].slice(0, 8);
447
- lines.push(`### ${fp}`);
448
- lines.push(`- Category: ${file.category}`);
449
- lines.push(`- Size: ${file.size} bytes (~${file.tokens} tokens)`);
450
- if (symbols.length > 0)
451
- lines.push(`- Exports: \`${symbols.join('`, `')}\``);
452
- if (file.classes.length > 0)
453
- lines.push(`- Classes: \`${file.classes.join('`, `')}\``);
454
- if (file.types.length > 0)
455
- lines.push(`- Types: \`${file.types.join('`, `')}\``);
456
- lines.push('');
457
- }
679
+ // Dependencies
680
+ if (feature.crossFeatureDeps.length > 0) {
681
+ lines.push(`## Dependencies`);
682
+ lines.push(`- Imports from: ${feature.crossFeatureDeps.join(', ')}`);
683
+ lines.push('');
458
684
  }
459
- // Import graph
685
+ // Internal Data Flow
460
686
  if (feature.imports.size > 0) {
461
- lines.push(`## Data Flow`);
462
- lines.push('Internal import relationships:');
463
- lines.push('');
687
+ lines.push(`## Internal Data Flow`);
464
688
  for (const [file, deps] of feature.imports) {
465
- lines.push(`- \`${file}\` ${deps.map(d => `\`${d}\``).join(', ')}`);
466
- }
467
- lines.push('');
468
- }
469
- // Stores
470
- if (feature.stores.length > 0) {
471
- lines.push(`## State Management`);
472
- for (const sp of feature.stores) {
473
- const file = scan.files.find(f => f.path === sp);
474
- if (file) {
475
- lines.push(`### ${basename(sp)}`);
476
- lines.push(`- Path: ${sp}`);
477
- if (file.exports.length > 0)
478
- lines.push(`- Actions/Selectors: \`${file.exports.join('`, `')}\``);
479
- lines.push('');
480
- }
481
- }
482
- }
483
- // Components
484
- if (feature.components.length > 0) {
485
- lines.push(`## Components`);
486
- for (const cp of feature.components.slice(0, 10)) {
487
- const file = scan.files.find(f => f.path === cp);
488
- if (file) {
489
- lines.push(`- \`${cp}\` — exports: ${file.exports.slice(0, 3).map(e => `\`${e}\``).join(', ') || 'default'}`);
689
+ for (const dep of deps) {
690
+ lines.push(`${basename(file)} → ${basename(dep)}`);
490
691
  }
491
692
  }
492
693
  lines.push('');
@@ -0,0 +1,24 @@
1
+ export interface CachedResponse {
2
+ id: string;
3
+ queryHash: string;
4
+ queryEmbedding: number[] | null;
5
+ query: string;
6
+ response: string;
7
+ model: string;
8
+ agentId: string;
9
+ systemPromptHash: string;
10
+ createdAt: number;
11
+ hitCount: number;
12
+ ttl: number;
13
+ }
14
+ export declare function checkCache(query: string, agentId: string, model: string, systemPromptHash: string, _ttlSeconds?: number): Promise<CachedResponse | null>;
15
+ export declare function storeResponse(query: string, response: string, agentId: string, model: string, systemPromptHash: string, ttlSeconds?: number): Promise<void>;
16
+ export declare function evictExpired(): Promise<void>;
17
+ export declare function evictLRU(maxEntries?: number): Promise<void>;
18
+ export declare function getCacheStats(): Promise<{
19
+ totalEntries: number;
20
+ hitRate: number;
21
+ estimatedSavings: number;
22
+ }>;
23
+ export declare function purgeCache(agentId?: string): Promise<void>;
24
+ //# sourceMappingURL=responseCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseCache.d.ts","sourceRoot":"","sources":["../../../server/services/responseCache.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAwDD,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,WAAW,SAAO,GAC1F,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAgEhC;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAC/D,gBAAgB,EAAE,MAAM,EAAE,UAAU,SAAO,GAC1C,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAKlD;AAED,wBAAsB,QAAQ,CAAC,UAAU,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAa/D;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,CAUlH;AAED,wBAAsB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhE"}
@@ -0,0 +1,163 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { getDb, saveDb } from './sqliteStore.js';
3
+ import embeddingService from './embeddingService.js';
4
+ function toStr(v) { return typeof v === 'string' ? v : ''; }
5
+ function toNum(v) { return typeof v === 'number' ? v : 0; }
6
+ function normalizeQuery(q) {
7
+ return q.trim().toLowerCase().replace(/\s+/g, ' ');
8
+ }
9
+ function sha256(text) {
10
+ return createHash('sha256').update(text, 'utf8').digest('hex');
11
+ }
12
+ function levenshtein(a, b) {
13
+ const prev = Array.from({ length: b.length + 1 }, (_, j) => j);
14
+ const curr = new Array(b.length + 1).fill(0);
15
+ for (let i = 1; i <= a.length; i++) {
16
+ curr[0] = i;
17
+ for (let j = 1; j <= b.length; j++) {
18
+ curr[j] = a[i - 1] === b[j - 1] ? prev[j - 1] : 1 + Math.min(prev[j], curr[j - 1], prev[j - 1]);
19
+ }
20
+ prev.splice(0, b.length + 1, ...curr);
21
+ }
22
+ return prev[b.length];
23
+ }
24
+ function blobToVec(v) {
25
+ if (!(v instanceof Uint8Array))
26
+ return null;
27
+ return Array.from(new Float32Array(v.buffer, v.byteOffset, v.byteLength / 4));
28
+ }
29
+ function rowToCache(row) {
30
+ return {
31
+ id: toStr(row[0]),
32
+ queryHash: toStr(row[1]),
33
+ queryEmbedding: blobToVec(row[2]),
34
+ query: toStr(row[3]),
35
+ response: toStr(row[4]),
36
+ model: toStr(row[5]),
37
+ agentId: toStr(row[6]),
38
+ systemPromptHash: toStr(row[7]),
39
+ createdAt: toNum(row[8]),
40
+ hitCount: toNum(row[9]),
41
+ ttl: toNum(row[10]),
42
+ };
43
+ }
44
+ async function markHit(id) {
45
+ const d = await getDb();
46
+ const now = Math.floor(Date.now() / 1000);
47
+ d.run(`UPDATE response_cache SET hit_count = hit_count + 1, last_hit_at = ? WHERE id = ?`, [now, id]);
48
+ saveDb();
49
+ }
50
+ export async function checkCache(query, agentId, model, systemPromptHash, _ttlSeconds = 3600) {
51
+ const d = await getDb();
52
+ const now = Math.floor(Date.now() / 1000);
53
+ const norm = normalizeQuery(query);
54
+ const qHash = sha256(norm + agentId + model + systemPromptHash);
55
+ // Tier 1: exact hash match
56
+ const exact = d.exec(`SELECT id,query_hash,query_embedding,query,response,model,agent_id,system_prompt_hash,created_at,hit_count,ttl
57
+ FROM response_cache WHERE query_hash=? AND agent_id=? AND model=? AND system_prompt_hash=?
58
+ AND (ttl=0 OR created_at+ttl>?) LIMIT 1`, [qHash, agentId, model, systemPromptHash, now]);
59
+ if (exact.length > 0 && exact[0].values.length > 0) {
60
+ const hit = rowToCache(exact[0].values[0]);
61
+ await markHit(hit.id);
62
+ return hit;
63
+ }
64
+ // Tier 2: Levenshtein distance < 10% of query length
65
+ const threshold = Math.max(3, Math.floor(norm.length * 0.1));
66
+ const recent = d.exec(`SELECT id,query_hash,query_embedding,query,response,model,agent_id,system_prompt_hash,created_at,hit_count,ttl
67
+ FROM response_cache WHERE agent_id=? AND model=? AND system_prompt_hash=? AND (ttl=0 OR created_at+ttl>?)
68
+ ORDER BY created_at DESC LIMIT 200`, [agentId, model, systemPromptHash, now]);
69
+ if (recent.length > 0) {
70
+ for (const row of recent[0].values) {
71
+ if (levenshtein(norm, normalizeQuery(toStr(row[3]))) <= threshold) {
72
+ const hit = rowToCache(row);
73
+ await markHit(hit.id);
74
+ return hit;
75
+ }
76
+ }
77
+ }
78
+ // Tier 3: cosine similarity > 0.92 using embeddings
79
+ try {
80
+ const [queryVec] = await embeddingService.embedBatch([norm]);
81
+ const withEmb = d.exec(`SELECT id,query_hash,query_embedding,query,response,model,agent_id,system_prompt_hash,created_at,hit_count,ttl
82
+ FROM response_cache WHERE agent_id=? AND model=? AND system_prompt_hash=?
83
+ AND query_embedding IS NOT NULL AND (ttl=0 OR created_at+ttl>?) ORDER BY created_at DESC LIMIT 500`, [agentId, model, systemPromptHash, now]);
84
+ if (withEmb.length > 0) {
85
+ let bestScore = 0;
86
+ let bestRow = null;
87
+ for (const row of withEmb[0].values) {
88
+ const vec = blobToVec(row[2]);
89
+ if (!vec)
90
+ continue;
91
+ const score = embeddingService.similarity(queryVec, vec);
92
+ if (score > bestScore) {
93
+ bestScore = score;
94
+ bestRow = row;
95
+ }
96
+ }
97
+ if (bestScore > 0.92 && bestRow) {
98
+ const hit = rowToCache(bestRow);
99
+ await markHit(hit.id);
100
+ return hit;
101
+ }
102
+ }
103
+ }
104
+ catch { /* embedding service unavailable — skip tier 3 */ }
105
+ return null;
106
+ }
107
+ export async function storeResponse(query, response, agentId, model, systemPromptHash, ttlSeconds = 3600) {
108
+ const d = await getDb();
109
+ const norm = normalizeQuery(query);
110
+ const qHash = sha256(norm + agentId + model + systemPromptHash);
111
+ const id = `rc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
112
+ const now = Math.floor(Date.now() / 1000);
113
+ let embeddingBlob = null;
114
+ try {
115
+ const [vec] = await embeddingService.embedBatch([norm]);
116
+ embeddingBlob = Buffer.from(new Float32Array(vec).buffer);
117
+ }
118
+ catch { /* skip embedding if unavailable */ }
119
+ d.run(`INSERT OR REPLACE INTO response_cache
120
+ (id,query_hash,query_embedding,query,response,model,agent_id,system_prompt_hash,created_at,hit_count,last_hit_at,ttl)
121
+ VALUES (?,?,?,?,?,?,?,?,?,0,?,?)`, [id, qHash, embeddingBlob, norm, response, model, agentId, systemPromptHash, now, now, ttlSeconds]);
122
+ saveDb();
123
+ }
124
+ export async function evictExpired() {
125
+ const d = await getDb();
126
+ const now = Math.floor(Date.now() / 1000);
127
+ d.run(`DELETE FROM response_cache WHERE ttl > 0 AND created_at + ttl <= ?`, [now]);
128
+ saveDb();
129
+ }
130
+ export async function evictLRU(maxEntries = 1000) {
131
+ const d = await getDb();
132
+ const agents = d.exec(`SELECT DISTINCT agent_id FROM response_cache`);
133
+ if (agents.length === 0)
134
+ return;
135
+ for (const row of agents[0].values) {
136
+ const aid = toStr(row[0]);
137
+ d.run(`DELETE FROM response_cache WHERE agent_id=? AND id NOT IN
138
+ (SELECT id FROM response_cache WHERE agent_id=? ORDER BY last_hit_at DESC LIMIT ?)`, [aid, aid, maxEntries]);
139
+ }
140
+ saveDb();
141
+ }
142
+ export async function getCacheStats() {
143
+ const d = await getDb();
144
+ const r = d.exec(`SELECT COUNT(*), COALESCE(SUM(hit_count),0), COALESCE(SUM(CAST(LENGTH(response) AS REAL)/4*hit_count),0) FROM response_cache`);
145
+ if (r.length === 0 || r[0].values.length === 0)
146
+ return { totalEntries: 0, hitRate: 0, estimatedSavings: 0 };
147
+ const row = r[0].values[0];
148
+ const totalEntries = toNum(row[0]);
149
+ const totalHits = toNum(row[1]);
150
+ const estimatedSavings = toNum(row[2]) * 0.000015;
151
+ const hitRate = totalHits + totalEntries > 0 ? totalHits / (totalHits + totalEntries) : 0;
152
+ return { totalEntries, hitRate, estimatedSavings };
153
+ }
154
+ export async function purgeCache(agentId) {
155
+ const d = await getDb();
156
+ if (agentId) {
157
+ d.run(`DELETE FROM response_cache WHERE agent_id = ?`, [agentId]);
158
+ }
159
+ else {
160
+ d.run(`DELETE FROM response_cache`);
161
+ }
162
+ saveDb();
163
+ }