@jagilber-org/index-server 1.19.1

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 (360) hide show
  1. package/CHANGELOG.md +1218 -0
  2. package/CODE_OF_CONDUCT.md +49 -0
  3. package/CONTRIBUTING.md +75 -0
  4. package/LICENSE +21 -0
  5. package/README.md +523 -0
  6. package/SECURITY.md +50 -0
  7. package/dist/config/configUtils.d.ts +11 -0
  8. package/dist/config/configUtils.js +87 -0
  9. package/dist/config/dashboardConfig.d.ts +45 -0
  10. package/dist/config/dashboardConfig.js +63 -0
  11. package/dist/config/defaultValues.d.ts +61 -0
  12. package/dist/config/defaultValues.js +70 -0
  13. package/dist/config/dirConstants.d.ts +17 -0
  14. package/dist/config/dirConstants.js +28 -0
  15. package/dist/config/featureConfig.d.ts +61 -0
  16. package/dist/config/featureConfig.js +121 -0
  17. package/dist/config/runtimeConfig.d.ts +145 -0
  18. package/dist/config/runtimeConfig.js +334 -0
  19. package/dist/config/serverConfig.d.ts +90 -0
  20. package/dist/config/serverConfig.js +164 -0
  21. package/dist/dashboard/analytics/AnalyticsEngine.d.ts +142 -0
  22. package/dist/dashboard/analytics/AnalyticsEngine.js +373 -0
  23. package/dist/dashboard/analytics/BusinessIntelligence.d.ts +187 -0
  24. package/dist/dashboard/analytics/BusinessIntelligence.js +594 -0
  25. package/dist/dashboard/client/admin.html +2150 -0
  26. package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/chunk-SP2CHFBE.mjs +1 -0
  27. package/dist/dashboard/client/chunks/mermaid-layout-elk.esm.min/render-T6MDALS3.mjs +27 -0
  28. package/dist/dashboard/client/css/admin.css +1466 -0
  29. package/dist/dashboard/client/js/admin.boot.js +359 -0
  30. package/dist/dashboard/client/js/admin.config.js +196 -0
  31. package/dist/dashboard/client/js/admin.embeddings.js +425 -0
  32. package/dist/dashboard/client/js/admin.graph.js +583 -0
  33. package/dist/dashboard/client/js/admin.instances.js +120 -0
  34. package/dist/dashboard/client/js/admin.instructions.js +552 -0
  35. package/dist/dashboard/client/js/admin.logs.js +113 -0
  36. package/dist/dashboard/client/js/admin.maintenance.js +354 -0
  37. package/dist/dashboard/client/js/admin.messaging.js +635 -0
  38. package/dist/dashboard/client/js/admin.monitor.js +181 -0
  39. package/dist/dashboard/client/js/admin.overview.js +221 -0
  40. package/dist/dashboard/client/js/admin.performance.js +61 -0
  41. package/dist/dashboard/client/js/admin.sessions.js +293 -0
  42. package/dist/dashboard/client/js/admin.sqlite.js +366 -0
  43. package/dist/dashboard/client/js/admin.utils.js +49 -0
  44. package/dist/dashboard/client/js/chart.umd.js +14 -0
  45. package/dist/dashboard/client/js/elk.bundled.js +6696 -0
  46. package/dist/dashboard/client/js/marked.umd.js +74 -0
  47. package/dist/dashboard/client/js/mermaid.min.js +3022 -0
  48. package/dist/dashboard/client/mermaid-layout-elk.esm.min.mjs +1 -0
  49. package/dist/dashboard/export/DataExporter.d.ts +169 -0
  50. package/dist/dashboard/export/DataExporter.js +737 -0
  51. package/dist/dashboard/export/exporters/csvExporter.d.ts +11 -0
  52. package/dist/dashboard/export/exporters/csvExporter.js +46 -0
  53. package/dist/dashboard/export/exporters/exportTypes.d.ts +89 -0
  54. package/dist/dashboard/export/exporters/exportTypes.js +5 -0
  55. package/dist/dashboard/export/exporters/jsonExporter.d.ts +7 -0
  56. package/dist/dashboard/export/exporters/jsonExporter.js +22 -0
  57. package/dist/dashboard/export/exporters/xmlExporter.d.ts +17 -0
  58. package/dist/dashboard/export/exporters/xmlExporter.js +175 -0
  59. package/dist/dashboard/integration/APIIntegration.d.ts +41 -0
  60. package/dist/dashboard/integration/APIIntegration.js +95 -0
  61. package/dist/dashboard/security/SecurityMonitor.d.ts +167 -0
  62. package/dist/dashboard/security/SecurityMonitor.js +559 -0
  63. package/dist/dashboard/server/AdminPanel.d.ts +183 -0
  64. package/dist/dashboard/server/AdminPanel.js +792 -0
  65. package/dist/dashboard/server/AdminPanelConfig.d.ts +42 -0
  66. package/dist/dashboard/server/AdminPanelConfig.js +80 -0
  67. package/dist/dashboard/server/AdminPanelState.d.ts +47 -0
  68. package/dist/dashboard/server/AdminPanelState.js +214 -0
  69. package/dist/dashboard/server/ApiRoutes.d.ts +17 -0
  70. package/dist/dashboard/server/ApiRoutes.js +149 -0
  71. package/dist/dashboard/server/DashboardServer.d.ts +49 -0
  72. package/dist/dashboard/server/DashboardServer.js +159 -0
  73. package/dist/dashboard/server/FileMetricsStorage.d.ts +49 -0
  74. package/dist/dashboard/server/FileMetricsStorage.js +195 -0
  75. package/dist/dashboard/server/HttpTransport.d.ts +23 -0
  76. package/dist/dashboard/server/HttpTransport.js +116 -0
  77. package/dist/dashboard/server/InstanceManager.d.ts +53 -0
  78. package/dist/dashboard/server/InstanceManager.js +284 -0
  79. package/dist/dashboard/server/KnowledgeStore.d.ts +35 -0
  80. package/dist/dashboard/server/KnowledgeStore.js +105 -0
  81. package/dist/dashboard/server/LeaderElection.d.ts +81 -0
  82. package/dist/dashboard/server/LeaderElection.js +268 -0
  83. package/dist/dashboard/server/MetricsCollector.d.ts +200 -0
  84. package/dist/dashboard/server/MetricsCollector.js +803 -0
  85. package/dist/dashboard/server/SessionPersistenceManager.d.ts +88 -0
  86. package/dist/dashboard/server/SessionPersistenceManager.js +457 -0
  87. package/dist/dashboard/server/ThinClient.d.ts +64 -0
  88. package/dist/dashboard/server/ThinClient.js +237 -0
  89. package/dist/dashboard/server/WebSocketManager.d.ts +161 -0
  90. package/dist/dashboard/server/WebSocketManager.js +463 -0
  91. package/dist/dashboard/server/httpLifecycle.d.ts +17 -0
  92. package/dist/dashboard/server/httpLifecycle.js +35 -0
  93. package/dist/dashboard/server/legacyDashboardHtml.d.ts +9 -0
  94. package/dist/dashboard/server/legacyDashboardHtml.js +618 -0
  95. package/dist/dashboard/server/legacyDashboardStyles.d.ts +5 -0
  96. package/dist/dashboard/server/legacyDashboardStyles.js +490 -0
  97. package/dist/dashboard/server/metricsAggregation.d.ts +252 -0
  98. package/dist/dashboard/server/metricsAggregation.js +206 -0
  99. package/dist/dashboard/server/metricsSerializer.d.ts +25 -0
  100. package/dist/dashboard/server/metricsSerializer.js +195 -0
  101. package/dist/dashboard/server/routes/admin.routes.d.ts +16 -0
  102. package/dist/dashboard/server/routes/admin.routes.js +596 -0
  103. package/dist/dashboard/server/routes/alerts.routes.d.ts +7 -0
  104. package/dist/dashboard/server/routes/alerts.routes.js +93 -0
  105. package/dist/dashboard/server/routes/api.feedback.routes.d.ts +73 -0
  106. package/dist/dashboard/server/routes/api.feedback.routes.js +171 -0
  107. package/dist/dashboard/server/routes/api.instructions.routes.d.ts +101 -0
  108. package/dist/dashboard/server/routes/api.instructions.routes.js +213 -0
  109. package/dist/dashboard/server/routes/api.usage.routes.d.ts +57 -0
  110. package/dist/dashboard/server/routes/api.usage.routes.js +374 -0
  111. package/dist/dashboard/server/routes/embeddings.routes.d.ts +6 -0
  112. package/dist/dashboard/server/routes/embeddings.routes.js +246 -0
  113. package/dist/dashboard/server/routes/graph.routes.d.ts +6 -0
  114. package/dist/dashboard/server/routes/graph.routes.js +280 -0
  115. package/dist/dashboard/server/routes/index.d.ts +38 -0
  116. package/dist/dashboard/server/routes/index.js +194 -0
  117. package/dist/dashboard/server/routes/instances.routes.d.ts +6 -0
  118. package/dist/dashboard/server/routes/instances.routes.js +35 -0
  119. package/dist/dashboard/server/routes/instructions.routes.d.ts +8 -0
  120. package/dist/dashboard/server/routes/instructions.routes.js +336 -0
  121. package/dist/dashboard/server/routes/knowledge.routes.d.ts +6 -0
  122. package/dist/dashboard/server/routes/knowledge.routes.js +82 -0
  123. package/dist/dashboard/server/routes/logs.routes.d.ts +6 -0
  124. package/dist/dashboard/server/routes/logs.routes.js +164 -0
  125. package/dist/dashboard/server/routes/messaging.routes.d.ts +16 -0
  126. package/dist/dashboard/server/routes/messaging.routes.js +293 -0
  127. package/dist/dashboard/server/routes/metrics.routes.d.ts +10 -0
  128. package/dist/dashboard/server/routes/metrics.routes.js +346 -0
  129. package/dist/dashboard/server/routes/scripts.routes.d.ts +9 -0
  130. package/dist/dashboard/server/routes/scripts.routes.js +84 -0
  131. package/dist/dashboard/server/routes/sqlite.routes.d.ts +9 -0
  132. package/dist/dashboard/server/routes/sqlite.routes.js +569 -0
  133. package/dist/dashboard/server/routes/status.routes.d.ts +7 -0
  134. package/dist/dashboard/server/routes/status.routes.js +183 -0
  135. package/dist/dashboard/server/routes/synthetic.routes.d.ts +7 -0
  136. package/dist/dashboard/server/routes/synthetic.routes.js +195 -0
  137. package/dist/dashboard/server/routes/tools.routes.d.ts +6 -0
  138. package/dist/dashboard/server/routes/tools.routes.js +46 -0
  139. package/dist/dashboard/server/routes/usage.routes.d.ts +6 -0
  140. package/dist/dashboard/server/routes/usage.routes.js +25 -0
  141. package/dist/dashboard/server/wsInit.d.ts +16 -0
  142. package/dist/dashboard/server/wsInit.js +35 -0
  143. package/dist/externalClientLib.d.ts +1 -0
  144. package/dist/externalClientLib.js +2 -0
  145. package/dist/minimal/index.d.ts +1 -0
  146. package/dist/minimal/index.js +140 -0
  147. package/dist/models/SessionPersistence.d.ts +115 -0
  148. package/dist/models/SessionPersistence.js +66 -0
  149. package/dist/models/instruction.d.ts +45 -0
  150. package/dist/models/instruction.js +2 -0
  151. package/dist/perf/benchmark.d.ts +1 -0
  152. package/dist/perf/benchmark.js +50 -0
  153. package/dist/portableClientWrapper.d.ts +1 -0
  154. package/dist/portableClientWrapper.js +2 -0
  155. package/dist/schemas/index.d.ts +128 -0
  156. package/dist/schemas/index.js +371 -0
  157. package/dist/scripts/runPerformanceBaseline.d.ts +1 -0
  158. package/dist/scripts/runPerformanceBaseline.js +17 -0
  159. package/dist/server/handshakeManager.d.ts +25 -0
  160. package/dist/server/handshakeManager.js +472 -0
  161. package/dist/server/index-server.d.ts +56 -0
  162. package/dist/server/index-server.js +822 -0
  163. package/dist/server/registry.d.ts +44 -0
  164. package/dist/server/registry.js +236 -0
  165. package/dist/server/sdkServer.d.ts +8 -0
  166. package/dist/server/sdkServer.js +299 -0
  167. package/dist/server/shutdownGuard.d.ts +41 -0
  168. package/dist/server/shutdownGuard.js +52 -0
  169. package/dist/server/thin-client.d.ts +22 -0
  170. package/dist/server/thin-client.js +111 -0
  171. package/dist/server/transport.d.ts +41 -0
  172. package/dist/server/transport.js +312 -0
  173. package/dist/server/transportFactory.d.ts +21 -0
  174. package/dist/server/transportFactory.js +429 -0
  175. package/dist/services/atomicFs.d.ts +22 -0
  176. package/dist/services/atomicFs.js +103 -0
  177. package/dist/services/auditLog.d.ts +38 -0
  178. package/dist/services/auditLog.js +142 -0
  179. package/dist/services/autoBackup.d.ts +14 -0
  180. package/dist/services/autoBackup.js +171 -0
  181. package/dist/services/autoSplit.d.ts +32 -0
  182. package/dist/services/autoSplit.js +113 -0
  183. package/dist/services/backupZip.d.ts +25 -0
  184. package/dist/services/backupZip.js +110 -0
  185. package/dist/services/bootstrapGating.d.ts +123 -0
  186. package/dist/services/bootstrapGating.js +221 -0
  187. package/dist/services/canonical.d.ts +23 -0
  188. package/dist/services/canonical.js +65 -0
  189. package/dist/services/categoryRules.d.ts +7 -0
  190. package/dist/services/categoryRules.js +37 -0
  191. package/dist/services/classificationService.d.ts +42 -0
  192. package/dist/services/classificationService.js +168 -0
  193. package/dist/services/embeddingService.d.ts +62 -0
  194. package/dist/services/embeddingService.js +259 -0
  195. package/dist/services/errors.d.ts +22 -0
  196. package/dist/services/errors.js +31 -0
  197. package/dist/services/featureFlags.d.ts +25 -0
  198. package/dist/services/featureFlags.js +89 -0
  199. package/dist/services/features.d.ts +13 -0
  200. package/dist/services/features.js +35 -0
  201. package/dist/services/handlers/instructions.add.d.ts +1 -0
  202. package/dist/services/handlers/instructions.add.js +496 -0
  203. package/dist/services/handlers/instructions.groom.d.ts +1 -0
  204. package/dist/services/handlers/instructions.groom.js +523 -0
  205. package/dist/services/handlers/instructions.import.d.ts +1 -0
  206. package/dist/services/handlers/instructions.import.js +173 -0
  207. package/dist/services/handlers/instructions.patch.d.ts +1 -0
  208. package/dist/services/handlers/instructions.patch.js +167 -0
  209. package/dist/services/handlers/instructions.query.d.ts +163 -0
  210. package/dist/services/handlers/instructions.query.js +522 -0
  211. package/dist/services/handlers/instructions.reload.d.ts +1 -0
  212. package/dist/services/handlers/instructions.reload.js +13 -0
  213. package/dist/services/handlers/instructions.remove.d.ts +1 -0
  214. package/dist/services/handlers/instructions.remove.js +118 -0
  215. package/dist/services/handlers/instructions.shared.d.ts +31 -0
  216. package/dist/services/handlers/instructions.shared.js +124 -0
  217. package/dist/services/handlers.activation.d.ts +1 -0
  218. package/dist/services/handlers.activation.js +203 -0
  219. package/dist/services/handlers.bootstrap.d.ts +1 -0
  220. package/dist/services/handlers.bootstrap.js +38 -0
  221. package/dist/services/handlers.dashboardConfig.d.ts +34 -0
  222. package/dist/services/handlers.dashboardConfig.js +108 -0
  223. package/dist/services/handlers.diagnostics.d.ts +1 -0
  224. package/dist/services/handlers.diagnostics.js +64 -0
  225. package/dist/services/handlers.feedback.d.ts +15 -0
  226. package/dist/services/handlers.feedback.js +378 -0
  227. package/dist/services/handlers.gates.d.ts +1 -0
  228. package/dist/services/handlers.gates.js +46 -0
  229. package/dist/services/handlers.graph.d.ts +53 -0
  230. package/dist/services/handlers.graph.js +231 -0
  231. package/dist/services/handlers.help.d.ts +1 -0
  232. package/dist/services/handlers.help.js +119 -0
  233. package/dist/services/handlers.instructionSchema.d.ts +1 -0
  234. package/dist/services/handlers.instructionSchema.js +227 -0
  235. package/dist/services/handlers.instructions.d.ts +8 -0
  236. package/dist/services/handlers.instructions.js +14 -0
  237. package/dist/services/handlers.instructionsDiagnostics.d.ts +1 -0
  238. package/dist/services/handlers.instructionsDiagnostics.js +14 -0
  239. package/dist/services/handlers.integrity.d.ts +1 -0
  240. package/dist/services/handlers.integrity.js +35 -0
  241. package/dist/services/handlers.manifest.d.ts +1 -0
  242. package/dist/services/handlers.manifest.js +24 -0
  243. package/dist/services/handlers.messaging.d.ts +12 -0
  244. package/dist/services/handlers.messaging.js +203 -0
  245. package/dist/services/handlers.metrics.d.ts +1 -0
  246. package/dist/services/handlers.metrics.js +43 -0
  247. package/dist/services/handlers.promote.d.ts +1 -0
  248. package/dist/services/handlers.promote.js +306 -0
  249. package/dist/services/handlers.prompt.d.ts +1 -0
  250. package/dist/services/handlers.prompt.js +7 -0
  251. package/dist/services/handlers.search.d.ts +69 -0
  252. package/dist/services/handlers.search.js +645 -0
  253. package/dist/services/handlers.testPrimitive.d.ts +1 -0
  254. package/dist/services/handlers.testPrimitive.js +5 -0
  255. package/dist/services/handlers.trace.d.ts +1 -0
  256. package/dist/services/handlers.trace.js +31 -0
  257. package/dist/services/handlers.usage.d.ts +1 -0
  258. package/dist/services/handlers.usage.js +11 -0
  259. package/dist/services/hotScore.d.ts +137 -0
  260. package/dist/services/hotScore.js +244 -0
  261. package/dist/services/indexContext.d.ts +117 -0
  262. package/dist/services/indexContext.js +968 -0
  263. package/dist/services/indexLoader.d.ts +44 -0
  264. package/dist/services/indexLoader.js +921 -0
  265. package/dist/services/indexRepository.d.ts +32 -0
  266. package/dist/services/indexRepository.js +71 -0
  267. package/dist/services/indexingService.d.ts +1 -0
  268. package/dist/services/indexingService.js +2 -0
  269. package/dist/services/instructions.dispatcher.d.ts +1 -0
  270. package/dist/services/instructions.dispatcher.js +231 -0
  271. package/dist/services/logPrefix.d.ts +1 -0
  272. package/dist/services/logPrefix.js +30 -0
  273. package/dist/services/logger.d.ts +52 -0
  274. package/dist/services/logger.js +268 -0
  275. package/dist/services/manifestManager.d.ts +82 -0
  276. package/dist/services/manifestManager.js +200 -0
  277. package/dist/services/messaging/agentMailbox.d.ts +60 -0
  278. package/dist/services/messaging/agentMailbox.js +353 -0
  279. package/dist/services/messaging/messagingPersistence.d.ts +20 -0
  280. package/dist/services/messaging/messagingPersistence.js +111 -0
  281. package/dist/services/messaging/messagingTypes.d.ts +150 -0
  282. package/dist/services/messaging/messagingTypes.js +66 -0
  283. package/dist/services/ownershipService.d.ts +1 -0
  284. package/dist/services/ownershipService.js +38 -0
  285. package/dist/services/performanceBaseline.d.ts +19 -0
  286. package/dist/services/performanceBaseline.js +210 -0
  287. package/dist/services/preflight.d.ts +12 -0
  288. package/dist/services/preflight.js +79 -0
  289. package/dist/services/promptReviewService.d.ts +44 -0
  290. package/dist/services/promptReviewService.js +101 -0
  291. package/dist/services/responseEnvelope.d.ts +6 -0
  292. package/dist/services/responseEnvelope.js +25 -0
  293. package/dist/services/seedBootstrap.d.ts +34 -0
  294. package/dist/services/seedBootstrap.js +427 -0
  295. package/dist/services/storage/factory.d.ts +17 -0
  296. package/dist/services/storage/factory.js +35 -0
  297. package/dist/services/storage/hashUtils.d.ts +11 -0
  298. package/dist/services/storage/hashUtils.js +35 -0
  299. package/dist/services/storage/index.d.ts +12 -0
  300. package/dist/services/storage/index.js +18 -0
  301. package/dist/services/storage/jsonFileStore.d.ts +32 -0
  302. package/dist/services/storage/jsonFileStore.js +241 -0
  303. package/dist/services/storage/migrationEngine.d.ts +35 -0
  304. package/dist/services/storage/migrationEngine.js +93 -0
  305. package/dist/services/storage/sqliteMessageStore.d.ts +53 -0
  306. package/dist/services/storage/sqliteMessageStore.js +146 -0
  307. package/dist/services/storage/sqliteSchema.d.ts +12 -0
  308. package/dist/services/storage/sqliteSchema.js +122 -0
  309. package/dist/services/storage/sqliteStore.d.ts +41 -0
  310. package/dist/services/storage/sqliteStore.js +339 -0
  311. package/dist/services/storage/sqliteUsageStore.d.ts +35 -0
  312. package/dist/services/storage/sqliteUsageStore.js +94 -0
  313. package/dist/services/storage/types.d.ts +171 -0
  314. package/dist/services/storage/types.js +12 -0
  315. package/dist/services/toolHandlers.d.ts +23 -0
  316. package/dist/services/toolHandlers.js +50 -0
  317. package/dist/services/toolRegistry.d.ts +20 -0
  318. package/dist/services/toolRegistry.js +490 -0
  319. package/dist/services/toolRegistry.zod.d.ts +10 -0
  320. package/dist/services/toolRegistry.zod.js +323 -0
  321. package/dist/services/tracing.d.ts +26 -0
  322. package/dist/services/tracing.js +260 -0
  323. package/dist/services/usageBuckets.d.ts +161 -0
  324. package/dist/services/usageBuckets.js +364 -0
  325. package/dist/services/validationService.d.ts +38 -0
  326. package/dist/services/validationService.js +125 -0
  327. package/dist/utils/BufferRing.d.ts +203 -0
  328. package/dist/utils/BufferRing.js +551 -0
  329. package/dist/utils/BufferRingExamples.d.ts +55 -0
  330. package/dist/utils/BufferRingExamples.js +188 -0
  331. package/dist/utils/envUtils.d.ts +42 -0
  332. package/dist/utils/envUtils.js +80 -0
  333. package/dist/utils/memoryMonitor.d.ts +83 -0
  334. package/dist/utils/memoryMonitor.js +275 -0
  335. package/dist/versioning/schemaVersion.d.ts +6 -0
  336. package/dist/versioning/schemaVersion.js +93 -0
  337. package/package.json +134 -0
  338. package/schemas/README.md +13 -0
  339. package/schemas/feedback-entry.schema.json +27 -0
  340. package/schemas/graph-export-v2.schema.json +60 -0
  341. package/schemas/index-server.code-schema.json +38477 -0
  342. package/schemas/instruction.schema.json +262 -0
  343. package/schemas/json-schema/SessionPersistence-persisted-admin-session.schema.json +54 -0
  344. package/schemas/json-schema/SessionPersistence-persisted-session-history-entry.schema.json +51 -0
  345. package/schemas/json-schema/SessionPersistence-persisted-web-socket-connection.schema.json +54 -0
  346. package/schemas/json-schema/SessionPersistence-session-persistence-config.schema.json +110 -0
  347. package/schemas/json-schema/SessionPersistence-session-persistence-data.schema.json +229 -0
  348. package/schemas/json-schema/SessionPersistence-session-persistence-manifest.schema.json +109 -0
  349. package/schemas/json-schema/SessionPersistence-session-persistence-metadata.schema.json +55 -0
  350. package/schemas/json-schema/instruction-audience-scope.schema.json +14 -0
  351. package/schemas/json-schema/instruction-content-type.schema.json +17 -0
  352. package/schemas/json-schema/instruction-instruction-entry.schema.json +206 -0
  353. package/schemas/json-schema/instruction-requirement-level.schema.json +16 -0
  354. package/schemas/manifest.json +78 -0
  355. package/schemas/manifest.schema.json +33 -0
  356. package/schemas/usage-batch.schema.json +16 -0
  357. package/schemas/usage-buckets.schema.json +30 -0
  358. package/schemas/usage-event.schema.json +17 -0
  359. package/scripts/copy-dashboard-assets.mjs +170 -0
  360. package/scripts/setup-hooks.cjs +28 -0
@@ -0,0 +1,645 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Instructions Search Handler
4
+ *
5
+ * Provides keyword-based search functionality for discovering instruction IDs.
6
+ * This is the PRIMARY discovery tool for MCP clients to find relevant instructions
7
+ * before retrieving detailed content via instructions/get or index_dispatch.
8
+ *
9
+ * Search Strategy:
10
+ * - Multi-keyword support with configurable matching
11
+ * - Searches instruction titles, bodies, and optionally categories
12
+ * - Returns lightweight ID list for efficient follow-up queries
13
+ * - Case-insensitive by default with case-sensitive option
14
+ * - Relevance scoring based on match frequency and location
15
+ *
16
+ * MCP Compliance:
17
+ * - Full JSON Schema validation
18
+ * - Structured error responses
19
+ * - Proper tool registration
20
+ * - Input sanitization and limits
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.handleInstructionsSearch = handleInstructionsSearch;
24
+ exports.buildAfterRetrievalMeta = buildAfterRetrievalMeta;
25
+ const registry_1 = require("../server/registry");
26
+ const logger_1 = require("./logger");
27
+ const indexContext_1 = require("./indexContext");
28
+ const errors_1 = require("./errors");
29
+ const runtimeConfig_1 = require("../config/runtimeConfig");
30
+ const embeddingService_1 = require("./embeddingService");
31
+ const SEARCH_SCHEMA = {
32
+ type: 'object',
33
+ required: ['keywords'],
34
+ properties: {
35
+ keywords: { type: 'array', items: { type: 'string', minLength: 1, maxLength: 100 }, minItems: 1, maxItems: 10, description: 'Array of search keywords (each word separately for best results)' },
36
+ mode: { type: 'string', enum: ['keyword', 'regex', 'semantic'], default: 'keyword', description: 'Search mode: keyword (substring match), regex (treat keywords as regex patterns), or semantic (embedding-based similarity). Default is semantic when INDEX_SERVER_SEMANTIC_ENABLED=1, otherwise keyword.' },
37
+ limit: { type: 'number', minimum: 1, maximum: 100, default: 50 },
38
+ includeCategories: { type: 'boolean', default: false },
39
+ caseSensitive: { type: 'boolean', default: false },
40
+ contentType: { type: 'string', enum: ['instruction', 'template', 'chat-session', 'reference', 'example', 'agent'] }
41
+ },
42
+ example: { keywords: ['build', 'validate', 'discipline'], limit: 10, includeCategories: true }
43
+ };
44
+ const VALID_MODES = ['keyword', 'regex', 'semantic'];
45
+ function normalizeSearchText(text, caseSensitive) {
46
+ const normalized = text
47
+ .normalize('NFD')
48
+ .replace(/[\u0300-\u036f]/g, '')
49
+ .replace(/[-_/\\.:]+/g, ' ')
50
+ .replace(/\s+/g, ' ')
51
+ .trim();
52
+ return caseSensitive ? normalized : normalized.toLowerCase();
53
+ }
54
+ function countSubstringMatches(text, searchTerm) {
55
+ if (!searchTerm || !text)
56
+ return 0;
57
+ let count = 0;
58
+ let offset = 0;
59
+ while (offset <= text.length) {
60
+ const index = text.indexOf(searchTerm, offset);
61
+ if (index === -1)
62
+ break;
63
+ count++;
64
+ offset = index + Math.max(searchTerm.length, 1);
65
+ }
66
+ return count;
67
+ }
68
+ function findOrderedKeywordSpan(text, keywords) {
69
+ if (!text || keywords.length < 2)
70
+ return undefined;
71
+ let searchFrom = 0;
72
+ let firstIndex;
73
+ let lastEnd = 0;
74
+ for (const keyword of keywords) {
75
+ const index = text.indexOf(keyword, searchFrom);
76
+ if (index === -1)
77
+ return undefined;
78
+ if (firstIndex === undefined)
79
+ firstIndex = index;
80
+ lastEnd = index + keyword.length;
81
+ searchFrom = index + keyword.length;
82
+ }
83
+ return firstIndex === undefined ? undefined : lastEnd - firstIndex;
84
+ }
85
+ function calculateOrderedProximityBonus(text, keywords, maxBonus) {
86
+ const span = findOrderedKeywordSpan(text, keywords);
87
+ if (span === undefined)
88
+ return 0;
89
+ const minimumSpan = keywords.reduce((total, keyword) => total + keyword.length, 0);
90
+ const gap = Math.max(0, span - minimumSpan);
91
+ return Math.max(0, maxBonus - Math.floor(gap / 6));
92
+ }
93
+ function buildInstructionSearchText(instruction, caseSensitive, includeCategories) {
94
+ const parts = [
95
+ instruction.id,
96
+ instruction.title,
97
+ instruction.semanticSummary || '',
98
+ instruction.body,
99
+ ];
100
+ if (includeCategories) {
101
+ parts.push((instruction.categories || []).join(' '));
102
+ }
103
+ return normalizeSearchText(parts.join(' '), caseSensitive);
104
+ }
105
+ function buildKeywordSearchContext(instructions, keywords, caseSensitive, includeCategories) {
106
+ const normalizedKeywords = [...new Set(keywords
107
+ .map(keyword => normalizeSearchText(keyword, caseSensitive))
108
+ .filter(keyword => keyword.length > 0))];
109
+ const keywordWeights = new Map();
110
+ if (normalizedKeywords.length === 0) {
111
+ return { normalizedKeywords, keywordWeights, totalKeywordWeight: 0 };
112
+ }
113
+ const searchableDocuments = instructions.map(instruction => buildInstructionSearchText(instruction, caseSensitive, includeCategories));
114
+ const documentCount = Math.max(searchableDocuments.length, 1);
115
+ for (const keyword of normalizedKeywords) {
116
+ const documentFrequency = searchableDocuments.reduce((count, documentText) => (documentText.includes(keyword) ? count + 1 : count), 0);
117
+ const weight = 1 + (Math.log((documentCount + 1) / (documentFrequency + 1)) * 1.5);
118
+ keywordWeights.set(keyword, Math.min(4, Math.max(1, weight)));
119
+ }
120
+ const totalKeywordWeight = normalizedKeywords.reduce((sum, keyword) => sum + (keywordWeights.get(keyword) ?? 1), 0);
121
+ return { normalizedKeywords, keywordWeights, totalKeywordWeight };
122
+ }
123
+ /**
124
+ * Calculate relevance score for an instruction based on keyword matches
125
+ */
126
+ function calculateRelevance(instruction, keywords, caseSensitive, includeCategories, mode = 'keyword', keywordContext) {
127
+ let score = 0;
128
+ const matchedFieldSet = new Set();
129
+ const prepareText = (text) => caseSensitive ? text : text.toLowerCase();
130
+ const preparedKeywords = keywords.map(k => mode === 'keyword' ? prepareText(k) : k);
131
+ const regexFlags = caseSensitive ? 'g' : 'gi';
132
+ // Build matchers: regex mode uses raw patterns, keyword mode uses substring/escaped regex
133
+ const testMatch = (text, keyword) => {
134
+ if (mode === 'regex') {
135
+ return new RegExp(keyword, caseSensitive ? '' : 'i').test(text);
136
+ }
137
+ return prepareText(text).includes(prepareText(keyword));
138
+ };
139
+ if (mode === 'keyword') {
140
+ const normalizedId = normalizeSearchText(instruction.id, caseSensitive);
141
+ const normalizedTitle = normalizeSearchText(instruction.title, caseSensitive);
142
+ const normalizedSummary = normalizeSearchText(instruction.semanticSummary || '', caseSensitive);
143
+ const normalizedBody = normalizeSearchText(instruction.body, caseSensitive);
144
+ const normalizedCategoryValues = (instruction.categories || []).map(category => normalizeSearchText(category, caseSensitive));
145
+ const normalizedCategoryText = normalizedCategoryValues.join(' ');
146
+ const normalizedKeywords = keywordContext?.normalizedKeywords ?? keywords
147
+ .map(keyword => normalizeSearchText(keyword, caseSensitive))
148
+ .filter(keyword => keyword.length > 0);
149
+ const keywordWeights = keywordContext?.keywordWeights ?? new Map();
150
+ const uniqueMatches = new Set();
151
+ for (const normalizedKeyword of normalizedKeywords) {
152
+ if (!normalizedKeyword)
153
+ continue;
154
+ const keywordWeight = keywordWeights.get(normalizedKeyword) ?? 1;
155
+ let keywordMatched = false;
156
+ if (normalizedId === normalizedKeyword) {
157
+ score += 40 * keywordWeight;
158
+ matchedFieldSet.add('id');
159
+ keywordMatched = true;
160
+ }
161
+ else if (normalizedId.startsWith(normalizedKeyword)) {
162
+ score += 28 * keywordWeight;
163
+ matchedFieldSet.add('id');
164
+ keywordMatched = true;
165
+ }
166
+ else if (normalizedId.includes(normalizedKeyword)) {
167
+ score += 18 * keywordWeight;
168
+ matchedFieldSet.add('id');
169
+ keywordMatched = true;
170
+ }
171
+ const titleMatches = countSubstringMatches(normalizedTitle, normalizedKeyword);
172
+ if (titleMatches > 0) {
173
+ score += (normalizedTitle === normalizedKeyword ? 20 : Math.min(titleMatches * 10, 20)) * keywordWeight;
174
+ matchedFieldSet.add('title');
175
+ keywordMatched = true;
176
+ }
177
+ const summaryMatches = countSubstringMatches(normalizedSummary, normalizedKeyword);
178
+ if (summaryMatches > 0) {
179
+ score += (normalizedSummary === normalizedKeyword ? 14 : Math.min(summaryMatches * 7, 14)) * keywordWeight;
180
+ matchedFieldSet.add('semanticSummary');
181
+ keywordMatched = true;
182
+ }
183
+ const bodyMatches = countSubstringMatches(normalizedBody, normalizedKeyword);
184
+ if (bodyMatches > 0) {
185
+ score += Math.min(bodyMatches * 2, 20) * keywordWeight;
186
+ matchedFieldSet.add('body');
187
+ keywordMatched = true;
188
+ }
189
+ if (includeCategories && normalizedCategoryValues.length > 0) {
190
+ const exactCategoryMatch = normalizedCategoryValues.some(category => category === normalizedKeyword);
191
+ const categoryMatches = exactCategoryMatch
192
+ ? 1
193
+ : countSubstringMatches(normalizedCategoryText, normalizedKeyword);
194
+ if (categoryMatches > 0) {
195
+ score += (exactCategoryMatch ? 8 : Math.min(categoryMatches * 3, 9)) * keywordWeight;
196
+ matchedFieldSet.add('categories');
197
+ keywordMatched = true;
198
+ }
199
+ }
200
+ if (keywordMatched)
201
+ uniqueMatches.add(normalizedKeyword);
202
+ }
203
+ if (uniqueMatches.size > 1) {
204
+ const matchedWeight = Array.from(uniqueMatches).reduce((sum, keyword) => sum + (keywordWeights.get(keyword) ?? 1), 0);
205
+ const strongestWeight = Math.max(...Array.from(uniqueMatches).map(keyword => keywordWeights.get(keyword) ?? 1));
206
+ score += (matchedWeight - strongestWeight) * 5;
207
+ if (keywordContext && keywordContext.totalKeywordWeight > 0) {
208
+ score += (matchedWeight / keywordContext.totalKeywordWeight) * 8;
209
+ }
210
+ }
211
+ if (normalizedKeywords.length > 1) {
212
+ score += calculateOrderedProximityBonus(normalizedId, normalizedKeywords, 18);
213
+ score += calculateOrderedProximityBonus(normalizedTitle, normalizedKeywords, 14);
214
+ score += calculateOrderedProximityBonus(normalizedSummary, normalizedKeywords, 10);
215
+ score += calculateOrderedProximityBonus(normalizedBody, normalizedKeywords, 8);
216
+ if (includeCategories) {
217
+ score += calculateOrderedProximityBonus(normalizedCategoryText, normalizedKeywords, 4);
218
+ }
219
+ }
220
+ return { score, matchedFields: Array.from(matchedFieldSet) };
221
+ }
222
+ const countMatches = (text, keyword) => {
223
+ if (mode === 'regex') {
224
+ return (text.match(new RegExp(keyword, regexFlags)) || []).length;
225
+ }
226
+ return (text.match(new RegExp(escapeRegex(keyword), regexFlags)) || []).length;
227
+ };
228
+ let idMatches = 0;
229
+ for (const keyword of preparedKeywords) {
230
+ if (testMatch(instruction.id, keyword)) {
231
+ idMatches += countMatches(instruction.id, keyword);
232
+ }
233
+ }
234
+ if (idMatches > 0) {
235
+ score += idMatches * 18;
236
+ matchedFieldSet.add('id');
237
+ }
238
+ let titleMatches = 0;
239
+ for (const keyword of preparedKeywords) {
240
+ titleMatches += countMatches(instruction.title, keyword);
241
+ }
242
+ if (titleMatches > 0) {
243
+ score += titleMatches * 10;
244
+ matchedFieldSet.add('title');
245
+ }
246
+ let summaryMatches = 0;
247
+ for (const keyword of preparedKeywords) {
248
+ summaryMatches += countMatches(instruction.semanticSummary || '', keyword);
249
+ }
250
+ if (summaryMatches > 0) {
251
+ score += Math.min(summaryMatches * 7, 14);
252
+ matchedFieldSet.add('semanticSummary');
253
+ }
254
+ let bodyMatches = 0;
255
+ for (const keyword of preparedKeywords) {
256
+ bodyMatches += countMatches(instruction.body, keyword);
257
+ }
258
+ if (bodyMatches > 0) {
259
+ score += Math.min(bodyMatches * 2, 20);
260
+ matchedFieldSet.add('body');
261
+ }
262
+ if (includeCategories && instruction.categories?.length) {
263
+ const categoryText = instruction.categories.join(' ');
264
+ let categoryMatches = 0;
265
+ for (const keyword of preparedKeywords) {
266
+ categoryMatches += countMatches(categoryText, keyword);
267
+ }
268
+ if (categoryMatches > 0) {
269
+ score += Math.min(categoryMatches * 3, 9);
270
+ matchedFieldSet.add('categories');
271
+ }
272
+ }
273
+ const uniqueMatches = new Set();
274
+ for (const keyword of preparedKeywords) {
275
+ if (testMatch(instruction.id, keyword) ||
276
+ testMatch(instruction.title, keyword) ||
277
+ testMatch(instruction.semanticSummary || '', keyword) ||
278
+ testMatch(instruction.body, keyword) ||
279
+ (includeCategories && instruction.categories?.some((cat) => testMatch(cat, keyword)))) {
280
+ uniqueMatches.add(keyword);
281
+ }
282
+ }
283
+ if (uniqueMatches.size > 1) {
284
+ score += (uniqueMatches.size - 1) * 5;
285
+ }
286
+ return { score, matchedFields: Array.from(matchedFieldSet) };
287
+ }
288
+ /**
289
+ * Escape special regex characters
290
+ */
291
+ function escapeRegex(string) {
292
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
293
+ }
294
+ /**
295
+ * Load and search instructions from the index
296
+ */
297
+ function performSearch(params) {
298
+ const startTime = performance.now();
299
+ // Load instruction index state
300
+ const state = (0, indexContext_1.ensureLoaded)();
301
+ if (!state || !state.list) {
302
+ throw new Error('instruction index not available');
303
+ }
304
+ // Ensure defaults are explicitly applied
305
+ const keywords = params.keywords;
306
+ const mode = params.mode ?? ((0, runtimeConfig_1.getRuntimeConfig)().semantic.enabled ? 'semantic' : 'keyword');
307
+ const limit = params.limit ?? 50;
308
+ const includeCategories = params.includeCategories ?? false;
309
+ const caseSensitive = params.caseSensitive ?? false;
310
+ const contentType = params.contentType;
311
+ // Validate contentType if provided
312
+ const validContentTypes = ['instruction', 'template', 'chat-session', 'reference', 'example', 'agent'];
313
+ if (contentType && !validContentTypes.includes(contentType)) {
314
+ throw new Error(`Invalid contentType: must be one of ${validContentTypes.join(', ')}`);
315
+ }
316
+ // Validate and sanitize keywords
317
+ const sanitizedKeywords = keywords
318
+ .filter(k => typeof k === 'string' && k.trim().length > 0)
319
+ .map(k => k.trim())
320
+ .slice(0, 10); // Enforce max 10 keywords
321
+ if (sanitizedKeywords.length === 0) {
322
+ throw new Error('At least one valid keyword is required');
323
+ }
324
+ const results = [];
325
+ const searchableInstructions = state.list.filter(instruction => {
326
+ if (!contentType)
327
+ return true;
328
+ const instrContentType = instruction.contentType || 'instruction';
329
+ return instrContentType === contentType;
330
+ });
331
+ const keywordContext = mode === 'keyword'
332
+ ? buildKeywordSearchContext(searchableInstructions, sanitizedKeywords, caseSensitive, includeCategories)
333
+ : undefined;
334
+ // Search through all instructions
335
+ for (const instruction of state.list) {
336
+ // Filter by contentType if specified
337
+ if (contentType) {
338
+ const instrContentType = instruction.contentType || 'instruction';
339
+ if (instrContentType !== contentType) {
340
+ continue; // Skip instructions that don't match the contentType filter
341
+ }
342
+ }
343
+ const { score, matchedFields } = calculateRelevance(instruction, sanitizedKeywords, caseSensitive, includeCategories, mode, keywordContext);
344
+ if (score > 0) {
345
+ results.push({
346
+ instructionId: instruction.id,
347
+ relevanceScore: score,
348
+ matchedFields
349
+ });
350
+ }
351
+ }
352
+ // Sort by relevance score (descending) and apply limit
353
+ results.sort((a, b) => b.relevanceScore - a.relevanceScore);
354
+ const limitedResults = results.slice(0, Math.min(limit, 100));
355
+ const executionTime = performance.now() - startTime;
356
+ (0, logger_1.logInfo)(`[search] Search completed: ${sanitizedKeywords.length} keywords, ${limitedResults.length}/${results.length} results, ${executionTime}ms`);
357
+ return {
358
+ results: limitedResults,
359
+ totalMatches: results.length,
360
+ query: {
361
+ keywords: sanitizedKeywords,
362
+ mode,
363
+ limit: Math.min(limit, 100),
364
+ includeCategories,
365
+ caseSensitive,
366
+ contentType
367
+ },
368
+ executionTimeMs: executionTime
369
+ };
370
+ }
371
+ /**
372
+ * Perform semantic (embedding-based) search.
373
+ * Computes cosine similarity between query embedding and instruction embeddings.
374
+ */
375
+ async function performSemanticSearch(params) {
376
+ const startTime = performance.now();
377
+ const state = (0, indexContext_1.ensureLoaded)();
378
+ if (!state || !state.list) {
379
+ throw new Error('instruction index not available');
380
+ }
381
+ const cfg = (0, runtimeConfig_1.getRuntimeConfig)().semantic;
382
+ const queryText = params.keywords.join(' ');
383
+ const limit = Math.min(params.limit ?? 50, 100);
384
+ const contentType = params.contentType;
385
+ (0, logger_1.logInfo)(`[search] Semantic search starting: query="${queryText}", device=${cfg.device}, model=${cfg.model}, index=${state.list.length} entries`);
386
+ // Get embeddings for query and index
387
+ const embedStart = performance.now();
388
+ const queryVec = await (0, embeddingService_1.embedText)(queryText, cfg.model, cfg.cacheDir, cfg.device, cfg.localOnly);
389
+ (0, logger_1.logDebug)(`[search] Query embedding computed in ${(performance.now() - embedStart).toFixed(1)}ms, dimensions=${queryVec.length}`);
390
+ const indexEmbedStart = performance.now();
391
+ const instrEmbeddings = await (0, embeddingService_1.getInstructionEmbeddings)(state.list, state.hash, cfg.embeddingPath, cfg.model, cfg.cacheDir, cfg.device, cfg.localOnly);
392
+ (0, logger_1.logDebug)(`[search] index embeddings ready in ${(performance.now() - indexEmbedStart).toFixed(1)}ms, entries=${Object.keys(instrEmbeddings).length}`);
393
+ // Score each instruction
394
+ const scored = [];
395
+ for (const instruction of state.list) {
396
+ if (contentType) {
397
+ const instrContentType = instruction.contentType || 'instruction';
398
+ if (instrContentType !== contentType)
399
+ continue;
400
+ }
401
+ const vec = instrEmbeddings[instruction.id];
402
+ if (!vec)
403
+ continue;
404
+ const similarity = (0, embeddingService_1.cosineSimilarity)(queryVec, vec);
405
+ if (similarity > 0) {
406
+ scored.push({
407
+ instructionId: instruction.id,
408
+ relevanceScore: Math.round(similarity * 100), // normalize to 0-100 scale
409
+ matchedFields: ['body'], // semantic matches are conceptual, mark as body
410
+ });
411
+ }
412
+ }
413
+ scored.sort((a, b) => b.relevanceScore - a.relevanceScore);
414
+ const limitedResults = scored.slice(0, limit);
415
+ const executionTime = performance.now() - startTime;
416
+ (0, logger_1.logInfo)(`[search] Semantic search completed: "${queryText}", ${limitedResults.length}/${scored.length} results, ${executionTime}ms`);
417
+ return {
418
+ results: limitedResults,
419
+ totalMatches: scored.length,
420
+ query: {
421
+ keywords: params.keywords,
422
+ mode: 'semantic',
423
+ limit,
424
+ includeCategories: params.includeCategories ?? false,
425
+ caseSensitive: params.caseSensitive ?? false,
426
+ contentType,
427
+ },
428
+ executionTimeMs: executionTime,
429
+ };
430
+ }
431
+ /**
432
+ * MCP Handler for index_search
433
+ */
434
+ async function handleInstructionsSearch(params) {
435
+ try {
436
+ // Input validation
437
+ if (!params || typeof params !== 'object') {
438
+ throw new Error('Invalid parameters: expected object');
439
+ }
440
+ if (!Array.isArray(params.keywords)) {
441
+ throw new Error('Invalid keywords: expected array');
442
+ }
443
+ if (params.keywords.length === 0) {
444
+ throw new Error('At least one keyword is required');
445
+ }
446
+ if (params.keywords.length > 10) {
447
+ throw new Error('Maximum 10 keywords allowed');
448
+ }
449
+ // Validate keyword strings
450
+ for (const keyword of params.keywords) {
451
+ if (typeof keyword !== 'string') {
452
+ throw new Error('All keywords must be strings');
453
+ }
454
+ if (keyword.trim().length === 0) {
455
+ throw new Error('Keywords cannot be empty');
456
+ }
457
+ if (keyword.length > 100 && params.mode !== 'regex') {
458
+ throw new Error('Keywords cannot exceed 100 characters');
459
+ }
460
+ }
461
+ // Validate optional parameters
462
+ if (params.limit !== undefined) {
463
+ if (typeof params.limit !== 'number' || params.limit < 1 || params.limit > 100) {
464
+ throw new Error('Limit must be a number between 1 and 100');
465
+ }
466
+ }
467
+ if (params.includeCategories !== undefined && typeof params.includeCategories !== 'boolean') {
468
+ throw new Error('includeCategories must be a boolean');
469
+ }
470
+ if (params.caseSensitive !== undefined && typeof params.caseSensitive !== 'boolean') {
471
+ throw new Error('caseSensitive must be a boolean');
472
+ }
473
+ if (params.contentType !== undefined) {
474
+ if (typeof params.contentType !== 'string') {
475
+ throw new Error('contentType must be a string');
476
+ }
477
+ const validContentTypes = ['instruction', 'template', 'chat-session', 'reference', 'example', 'agent'];
478
+ if (!validContentTypes.includes(params.contentType)) {
479
+ throw new Error(`contentType must be one of: ${validContentTypes.join(', ')}`);
480
+ }
481
+ }
482
+ // Validate mode parameter
483
+ const mode = params.mode ?? ((0, runtimeConfig_1.getRuntimeConfig)().semantic.enabled ? 'semantic' : 'keyword');
484
+ if (!VALID_MODES.includes(mode)) {
485
+ throw new Error(`Invalid mode: must be one of ${VALID_MODES.join(', ')}`);
486
+ }
487
+ (0, logger_1.logInfo)(`[search] Search request: mode=${mode}, keywords=[${params.keywords.join(', ')}], limit=${params.limit ?? 50}, contentType=${params.contentType ?? 'any'}`);
488
+ // Regex mode validation: pattern safety checks
489
+ if (mode === 'regex') {
490
+ for (const keyword of params.keywords) {
491
+ if (keyword.length > 200) {
492
+ throw new Error('Regex patterns must not exceed 200 characters to prevent ReDoS');
493
+ }
494
+ try {
495
+ new RegExp(keyword);
496
+ }
497
+ catch {
498
+ throw new Error(`Invalid regex pattern "${keyword}": check syntax and try again`);
499
+ }
500
+ }
501
+ }
502
+ // Semantic mode: check feature flag
503
+ if (mode === 'semantic') {
504
+ const cfg = (0, runtimeConfig_1.getRuntimeConfig)().semantic;
505
+ (0, logger_1.logDebug)(`[search] Semantic config: enabled=${cfg.enabled}, device=${cfg.device}, model=${cfg.model}, localOnly=${cfg.localOnly}`);
506
+ if (!cfg.enabled) {
507
+ (0, logger_1.logWarn)('[search] Semantic search requested but INDEX_SERVER_SEMANTIC_ENABLED is not set');
508
+ throw new Error('Semantic search mode is disabled. Set INDEX_SERVER_SEMANTIC_ENABLED=1 to enable.');
509
+ }
510
+ }
511
+ // Ensure case-insensitive search by default
512
+ const searchParams = {
513
+ keywords: params.keywords,
514
+ mode: mode,
515
+ limit: params.limit,
516
+ includeCategories: params.includeCategories,
517
+ caseSensitive: params.caseSensitive ?? false, // Explicit default to false for case-insensitive search
518
+ contentType: params.contentType
519
+ };
520
+ // Semantic mode: embedding-based similarity search
521
+ if (mode === 'semantic') {
522
+ try {
523
+ const result = await performSemanticSearch(searchParams);
524
+ if (result.totalMatches > 0) {
525
+ result._meta = buildAfterRetrievalMeta();
526
+ autoTrackSearchResults(result.results);
527
+ }
528
+ else {
529
+ result.hints = buildSearchHints(searchParams);
530
+ }
531
+ return result;
532
+ }
533
+ catch (err) {
534
+ // Graceful degradation: fall back to keyword mode
535
+ const errMsg = err instanceof Error ? err.message : String(err);
536
+ const errStack = err instanceof Error ? err.stack : undefined;
537
+ (0, logger_1.logError)(`[search] Semantic search failed, falling back to keyword mode: ${errMsg}`);
538
+ if (errStack) {
539
+ (0, logger_1.logDebug)(`[search] Semantic fallback stack trace: ${errStack}`);
540
+ }
541
+ searchParams.mode = 'keyword';
542
+ }
543
+ }
544
+ const result = performSearch(searchParams);
545
+ // Auto-tokenize fallback: if no results and any keyword contains whitespace,
546
+ // split multi-word keywords into individual tokens and retry.
547
+ // Skip auto-tokenize for regex mode — patterns should be used as-is.
548
+ if (result.totalMatches === 0 && mode !== 'regex') {
549
+ const hasMultiWord = searchParams.keywords.some(k => k.trim().includes(' '));
550
+ if (hasMultiWord) {
551
+ const tokenized = searchParams.keywords
552
+ .flatMap(k => k.split(/\s+/))
553
+ .map(t => t.trim())
554
+ .filter(t => t.length > 0);
555
+ // Deduplicate and enforce limits
556
+ const unique = [...new Set(tokenized)].slice(0, 10);
557
+ if (unique.length > 0 && unique.length !== searchParams.keywords.length || unique.some((t, i) => t !== searchParams.keywords[i])) {
558
+ (0, logger_1.logInfo)(`[search] Auto-tokenizing keywords: [${searchParams.keywords.join(', ')}] -> [${unique.join(', ')}]`);
559
+ const retryParams = { ...searchParams, keywords: unique };
560
+ const retryResult = performSearch(retryParams);
561
+ retryResult.autoTokenized = true;
562
+ if (retryResult.totalMatches === 0) {
563
+ retryResult.hints = buildSearchHints(retryParams);
564
+ }
565
+ else {
566
+ autoTrackSearchResults(retryResult.results);
567
+ }
568
+ return retryResult;
569
+ }
570
+ }
571
+ }
572
+ // Attach hints on zero-result responses
573
+ if (result.totalMatches === 0) {
574
+ result.hints = buildSearchHints(searchParams);
575
+ }
576
+ // Attach _meta hints on successful (non-empty) responses
577
+ if (result.totalMatches > 0) {
578
+ result._meta = buildAfterRetrievalMeta();
579
+ autoTrackSearchResults(result.results);
580
+ }
581
+ return result;
582
+ }
583
+ catch (error) {
584
+ const errorMessage = error instanceof Error ? error.message : 'Unknown search error';
585
+ (0, logger_1.logWarn)(`[search] Search error: ${errorMessage}`);
586
+ (0, errors_1.semanticError)(-32602, `Search failed: ${errorMessage}`, {
587
+ reason: 'invalid_params',
588
+ hint: 'Use q as a single phrase for index_dispatch search, or pass keywords as an array for index_search. Multi-word phrases are auto-tokenized only on retry.',
589
+ schema: SEARCH_SCHEMA,
590
+ example: { keywords: ['build', 'validate'], includeCategories: true }
591
+ });
592
+ }
593
+ }
594
+ /**
595
+ * Fire-and-forget usage tracking for search results.
596
+ * Tracks top results only to avoid excessive writes.
597
+ */
598
+ function autoTrackSearchResults(results) {
599
+ if (!(0, runtimeConfig_1.getRuntimeConfig)().index?.autoUsageTrack)
600
+ return;
601
+ const top = results.slice(0, 10);
602
+ for (const r of top) {
603
+ try {
604
+ (0, indexContext_1.incrementUsage)(r.instructionId, { action: 'search' });
605
+ }
606
+ catch { /* fire-and-forget */ }
607
+ }
608
+ }
609
+ /**
610
+ * Build _meta.afterRetrieval hints for MCP clients.
611
+ * Encourages callers to record usage and submit feedback.
612
+ */
613
+ function buildAfterRetrievalMeta() {
614
+ return {
615
+ afterRetrieval: [
616
+ 'Call usage_track with the instruction id and signal (helpful|not-relevant|outdated|applied) to record usage quality.',
617
+ 'Call feedback_submit to report issues, request features, or flag outdated content.',
618
+ ],
619
+ };
620
+ }
621
+ /**
622
+ * Build actionable hints for zero-result searches
623
+ */
624
+ function buildSearchHints(params) {
625
+ const hints = [];
626
+ if (!params.includeCategories) {
627
+ hints.push('Try setting includeCategories: true to also search category tags.');
628
+ }
629
+ if (params.keywords.length === 1) {
630
+ hints.push('Try using multiple shorter keywords instead of one long phrase, e.g. ["build", "validate"] instead of ["build validate"].');
631
+ }
632
+ if (params.keywords.some(k => k.length > 15)) {
633
+ hints.push('Try shorter or more general keywords — substring matching is used, not fuzzy/stemming.');
634
+ }
635
+ if (params.contentType) {
636
+ hints.push(`Remove the contentType filter ("${params.contentType}") to search across all content types.`);
637
+ }
638
+ if (params.mode !== 'regex') {
639
+ hints.push('Try mode: "regex" to use regex patterns (e.g. "deploy|release" for alternation).');
640
+ }
641
+ hints.push('Use index_dispatch with action="capabilities" to discover other retrieval methods.');
642
+ return hints;
643
+ }
644
+ // Register the handler
645
+ (0, registry_1.registerHandler)('index_search', handleInstructionsSearch);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const registry_1 = require("../server/registry");
4
+ // Test-only primitive returning handler used by feature flag tests to ensure envelope works with non-object values.
5
+ (0, registry_1.registerHandler)('test_primitive', () => 42);
@@ -0,0 +1 @@
1
+ export {};