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