@jagilber-org/index-server 1.22.1 → 1.26.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/CHANGELOG.md +91 -2
  2. package/CODE_OF_CONDUCT.md +2 -0
  3. package/CONTRIBUTING.md +32 -2
  4. package/README.md +82 -19
  5. package/SECURITY.md +17 -5
  6. package/dist/config/dashboardConfig.d.ts +3 -0
  7. package/dist/config/dashboardConfig.js +3 -0
  8. package/dist/config/defaultValues.d.ts +1 -1
  9. package/dist/config/defaultValues.js +1 -1
  10. package/dist/config/featureConfig.d.ts +2 -0
  11. package/dist/config/featureConfig.js +6 -1
  12. package/dist/config/runtimeConfig.d.ts +1 -1
  13. package/dist/config/runtimeConfig.js +8 -9
  14. package/dist/dashboard/client/admin.html +170 -53
  15. package/dist/dashboard/client/css/admin.css +132 -0
  16. package/dist/dashboard/client/js/admin.auth.js +25 -11
  17. package/dist/dashboard/client/js/admin.config.js +1 -1
  18. package/dist/dashboard/client/js/admin.feedback.js +328 -0
  19. package/dist/dashboard/client/js/admin.graph.js +120 -18
  20. package/dist/dashboard/client/js/admin.instructions.js +27 -13
  21. package/dist/dashboard/client/js/admin.logs.js +1 -5
  22. package/dist/dashboard/client/js/admin.maintenance.js +53 -8
  23. package/dist/dashboard/client/js/admin.messaging.js +1 -4
  24. package/dist/dashboard/client/js/admin.overview.js +5 -1
  25. package/dist/dashboard/client/js/admin.sessions.js +1 -1
  26. package/dist/dashboard/client/js/admin.utils.js +43 -1
  27. package/dist/dashboard/client/js/mermaid.min.js +813 -537
  28. package/dist/dashboard/export/DataExporter.js +2 -1
  29. package/dist/dashboard/server/AdminPanel.d.ts +3 -0
  30. package/dist/dashboard/server/AdminPanel.js +132 -35
  31. package/dist/dashboard/server/ApiRoutes.js +40 -9
  32. package/dist/dashboard/server/DashboardServer.js +1 -1
  33. package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
  34. package/dist/dashboard/server/FileMetricsStorage.js +52 -5
  35. package/dist/dashboard/server/HttpTransport.js +6 -0
  36. package/dist/dashboard/server/InstanceManager.js +7 -2
  37. package/dist/dashboard/server/KnowledgeStore.js +7 -2
  38. package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
  39. package/dist/dashboard/server/MetricsCollector.js +113 -17
  40. package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
  41. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
  42. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
  43. package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
  44. package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
  45. package/dist/dashboard/server/routes/admin.routes.js +35 -27
  46. package/dist/dashboard/server/routes/alerts.routes.js +4 -3
  47. package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
  48. package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
  49. package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
  50. package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
  51. package/dist/dashboard/server/routes/graph.routes.js +10 -13
  52. package/dist/dashboard/server/routes/index.d.ts +1 -0
  53. package/dist/dashboard/server/routes/index.js +74 -39
  54. package/dist/dashboard/server/routes/instances.routes.js +2 -1
  55. package/dist/dashboard/server/routes/instructions.routes.js +46 -27
  56. package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
  57. package/dist/dashboard/server/routes/logs.routes.js +5 -4
  58. package/dist/dashboard/server/routes/messaging.routes.js +15 -14
  59. package/dist/dashboard/server/routes/metrics.routes.js +14 -13
  60. package/dist/dashboard/server/routes/scripts.routes.js +6 -3
  61. package/dist/dashboard/server/routes/status.routes.js +5 -4
  62. package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
  63. package/dist/dashboard/server/routes/usage.routes.js +2 -1
  64. package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
  65. package/dist/dashboard/server/utils/escapeHtml.js +11 -0
  66. package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
  67. package/dist/dashboard/server/utils/pathContainment.js +15 -0
  68. package/dist/dashboard/server/wsInit.js +2 -2
  69. package/dist/lib/mcpStdioLogging.d.ts +165 -0
  70. package/dist/lib/mcpStdioLogging.js +287 -0
  71. package/dist/schemas/index.d.ts +37 -2
  72. package/dist/schemas/index.js +27 -3
  73. package/dist/server/backgroundServicesStartup.d.ts +7 -1
  74. package/dist/server/backgroundServicesStartup.js +25 -8
  75. package/dist/server/certInit.d.ts +97 -0
  76. package/dist/server/certInit.js +359 -0
  77. package/dist/server/certInit.types.d.ts +92 -0
  78. package/dist/server/certInit.types.js +34 -0
  79. package/dist/server/handshake/fallbackFrames.d.ts +31 -0
  80. package/dist/server/handshake/fallbackFrames.js +38 -0
  81. package/dist/server/handshake/initializeDetector.d.ts +31 -0
  82. package/dist/server/handshake/initializeDetector.js +88 -0
  83. package/dist/server/handshake/protocol.d.ts +15 -0
  84. package/dist/server/handshake/protocol.js +37 -0
  85. package/dist/server/handshake/readyEmitter.d.ts +6 -0
  86. package/dist/server/handshake/readyEmitter.js +88 -0
  87. package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
  88. package/dist/server/handshake/safetyFallbacks.js +134 -0
  89. package/dist/server/handshake/stdinSniffer.d.ts +1 -0
  90. package/dist/server/handshake/stdinSniffer.js +260 -0
  91. package/dist/server/handshake/tracing.d.ts +16 -0
  92. package/dist/server/handshake/tracing.js +95 -0
  93. package/dist/server/handshakeManager.d.ts +23 -23
  94. package/dist/server/handshakeManager.js +36 -466
  95. package/dist/server/index-server.d.ts +23 -0
  96. package/dist/server/index-server.js +194 -9
  97. package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
  98. package/dist/server/mcpReadOnlySurfaces.js +297 -0
  99. package/dist/server/sdkServer.js +69 -7
  100. package/dist/server/transport.d.ts +5 -6
  101. package/dist/server/transport.js +46 -64
  102. package/dist/server/transportFactory.d.ts +3 -9
  103. package/dist/server/transportFactory.js +18 -380
  104. package/dist/services/atomicFs.d.ts +3 -0
  105. package/dist/services/atomicFs.js +171 -13
  106. package/dist/services/auditLog.d.ts +17 -2
  107. package/dist/services/auditLog.js +75 -14
  108. package/dist/services/bootstrapGating.js +1 -1
  109. package/dist/services/categoryRules.d.ts +10 -0
  110. package/dist/services/categoryRules.js +17 -0
  111. package/dist/services/classificationService.js +7 -5
  112. package/dist/services/embeddingService.d.ts +27 -11
  113. package/dist/services/embeddingService.js +51 -14
  114. package/dist/services/feedbackStorage.d.ts +39 -0
  115. package/dist/services/feedbackStorage.js +88 -0
  116. package/dist/services/handlers/instructions.add.js +429 -317
  117. package/dist/services/handlers/instructions.groom.js +128 -31
  118. package/dist/services/handlers/instructions.import.js +56 -23
  119. package/dist/services/handlers/instructions.patch.js +43 -32
  120. package/dist/services/handlers/instructions.query.js +20 -29
  121. package/dist/services/handlers/instructions.shared.d.ts +54 -0
  122. package/dist/services/handlers/instructions.shared.js +126 -1
  123. package/dist/services/handlers.activation.js +83 -81
  124. package/dist/services/handlers.dashboardConfig.d.ts +2 -2
  125. package/dist/services/handlers.dashboardConfig.js +1 -2
  126. package/dist/services/handlers.diagnostics.js +75 -54
  127. package/dist/services/handlers.feedback.d.ts +4 -11
  128. package/dist/services/handlers.feedback.js +11 -333
  129. package/dist/services/handlers.gates.js +69 -37
  130. package/dist/services/handlers.graph.js +2 -2
  131. package/dist/services/handlers.help.js +2 -2
  132. package/dist/services/handlers.instructionSchema.js +4 -2
  133. package/dist/services/handlers.integrity.js +42 -22
  134. package/dist/services/handlers.messaging.js +1 -1
  135. package/dist/services/handlers.metrics.js +51 -6
  136. package/dist/services/handlers.prompt.js +10 -2
  137. package/dist/services/handlers.search.js +94 -44
  138. package/dist/services/handlers.trace.js +1 -1
  139. package/dist/services/handlers.usage.js +38 -7
  140. package/dist/services/indexContext.d.ts +21 -1
  141. package/dist/services/indexContext.js +263 -78
  142. package/dist/services/indexLoader.d.ts +1 -0
  143. package/dist/services/indexLoader.js +28 -8
  144. package/dist/services/instructionRecordValidation.d.ts +39 -0
  145. package/dist/services/instructionRecordValidation.js +388 -0
  146. package/dist/services/instructions.dispatcher.js +4 -4
  147. package/dist/services/loaderSchemaValidator.d.ts +15 -0
  148. package/dist/services/loaderSchemaValidator.js +69 -0
  149. package/dist/services/logger.js +11 -2
  150. package/dist/services/mcpLogBridge.d.ts +49 -0
  151. package/dist/services/mcpLogBridge.js +83 -0
  152. package/dist/services/ownershipService.js +18 -8
  153. package/dist/services/performanceBaseline.js +23 -22
  154. package/dist/services/promptReviewService.d.ts +3 -1
  155. package/dist/services/promptReviewService.js +41 -13
  156. package/dist/services/regexSafety.d.ts +6 -0
  157. package/dist/services/regexSafety.js +46 -0
  158. package/dist/services/seedBootstrap.js +1 -1
  159. package/dist/services/storage/factory.d.ts +14 -1
  160. package/dist/services/storage/factory.js +61 -1
  161. package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
  162. package/dist/services/storage/jsonEmbeddingStore.js +83 -0
  163. package/dist/services/storage/jsonFileStore.d.ts +3 -1
  164. package/dist/services/storage/jsonFileStore.js +8 -6
  165. package/dist/services/storage/migrationEngine.d.ts +13 -0
  166. package/dist/services/storage/migrationEngine.js +31 -0
  167. package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
  168. package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
  169. package/dist/services/storage/sqliteStore.d.ts +3 -1
  170. package/dist/services/storage/sqliteStore.js +2 -2
  171. package/dist/services/storage/types.d.ts +48 -1
  172. package/dist/services/toolRegistry.js +77 -67
  173. package/dist/services/toolRegistry.zod.js +89 -86
  174. package/dist/services/tracing.js +5 -4
  175. package/dist/utils/envUtils.d.ts +4 -0
  176. package/dist/utils/envUtils.js +7 -0
  177. package/dist/utils/memoryMonitor.js +11 -10
  178. package/package.json +12 -4
  179. package/schemas/instruction.schema.json +38 -1
  180. package/scripts/copy-dashboard-assets.mjs +1 -1
  181. package/scripts/dist/README.md +1 -1
  182. package/scripts/generate-certs.mjs +201 -0
  183. package/scripts/setup-wizard.mjs +781 -0
  184. package/server.json +20 -0
  185. package/dist/externalClientLib.d.ts +0 -1
  186. package/dist/externalClientLib.js +0 -2
  187. package/dist/portableClientWrapper.d.ts +0 -1
  188. package/dist/portableClientWrapper.js +0 -2
  189. package/dist/services/indexingService.d.ts +0 -1
  190. package/dist/services/indexingService.js +0 -2
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Log Bridge — thin adapter over the generalized McpStdioLogger.
4
+ *
5
+ * Preserves the existing module-level API (`registerMcpServer`, `activateMcpLogBridge`,
6
+ * `isMcpLogBridgeActive`, `sendMcpLog`, `_restoreStderr`) so that all existing
7
+ * call sites in index-server.ts, sdkServer.ts, handshakeManager.ts, and logger.ts
8
+ * continue to work without changes.
9
+ *
10
+ * The actual stderr interception, buffering, replay, and MCP protocol routing is
11
+ * delegated to `McpStdioLogger` from `../lib/mcpStdioLogging`, which is a
12
+ * self-contained, reusable module for any MCP stdio server.
13
+ *
14
+ * See `src/lib/mcpStdioLogging.ts` for the generalized implementation and
15
+ * `docs/mcp_stdio_logging.md` for integration guidance.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.registerMcpServer = registerMcpServer;
19
+ exports.activateMcpLogBridge = activateMcpLogBridge;
20
+ exports.isMcpLogBridgeActive = isMcpLogBridgeActive;
21
+ exports.sendMcpLog = sendMcpLog;
22
+ exports.writeRealStderr = writeRealStderr;
23
+ exports._restoreStderr = _restoreStderr;
24
+ const mcpStdioLogging_1 = require("../lib/mcpStdioLogging");
25
+ const LEVEL_MAP = {
26
+ TRACE: 'debug',
27
+ DEBUG: 'debug',
28
+ INFO: 'info',
29
+ WARN: 'warning',
30
+ ERROR: 'error',
31
+ };
32
+ // Singleton instance — intercepts stderr immediately on module load.
33
+ const _logger = new mcpStdioLogging_1.McpStdioLogger({
34
+ serverName: 'index-server',
35
+ interceptImmediately: process.env.INDEX_SERVER_DISABLE_STDERR_BRIDGE !== '1',
36
+ maxBufferSize: 500,
37
+ });
38
+ /**
39
+ * Register the SDK server instance. Called once after server creation.
40
+ * Does NOT activate the bridge — call `activateMcpLogBridge()` after ready.
41
+ */
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ function registerMcpServer(server) {
44
+ _logger.registerServer(server);
45
+ }
46
+ /**
47
+ * Activate the bridge so subsequent log calls are routed via MCP protocol.
48
+ * Replays any buffered pre-handshake stderr lines through the protocol.
49
+ * Called from `emitReadyGlobal()` after the handshake completes.
50
+ */
51
+ function activateMcpLogBridge() {
52
+ _logger.activate();
53
+ }
54
+ /**
55
+ * Returns true if the bridge is active and logs will be sent via MCP protocol.
56
+ */
57
+ function isMcpLogBridgeActive() {
58
+ return _logger.isActive;
59
+ }
60
+ /**
61
+ * Send a log message through the MCP `notifications/message` protocol.
62
+ * No-op if the bridge is not yet active.
63
+ *
64
+ * @param level - The index-server log level (TRACE, DEBUG, INFO, WARN, ERROR)
65
+ * @param data - The log payload (typically the NDJSON string)
66
+ */
67
+ function sendMcpLog(level, data) {
68
+ _logger.log(LEVEL_MAP[level] ?? 'info', data);
69
+ }
70
+ /**
71
+ * Write directly to the original process.stderr, bypassing the interceptor.
72
+ * Use from logger.ts to ensure VS Code Output panel always has content.
73
+ */
74
+ function writeRealStderr(data) {
75
+ _logger.writeOriginalStderr(data);
76
+ }
77
+ /**
78
+ * Restore original stderr and deactivate the bridge.
79
+ * Intended for testing cleanup only.
80
+ */
81
+ function _restoreStderr() {
82
+ _logger.restore();
83
+ }
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveOwner = resolveOwner;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
+ const logger_js_1 = require("./logger.js");
10
+ const regexSafety_js_1 = require("./regexSafety.js");
9
11
  let cached = null;
10
12
  function loadRules() {
11
13
  const file = path_1.default.join(process.cwd(), 'owners.json');
@@ -13,8 +15,20 @@ function loadRules() {
13
15
  const stat = fs_1.default.statSync(file);
14
16
  if (cached && cached.mtimeMs === stat.mtimeMs)
15
17
  return cached.rules;
16
- const raw = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
17
- const rules = Array.isArray(raw.ownership) ? raw.ownership.filter(r => r && typeof r.pattern === 'string' && typeof r.owner === 'string') : [];
18
+ const raw = JSON.parse(fs_1.default.readFileSync(file, 'utf8')); // lgtm[js/file-system-race] — file path is cwd-resolved owners.json; mtime-checked above for cache invalidation
19
+ const rules = Array.isArray(raw.ownership)
20
+ ? raw.ownership.flatMap((rule) => {
21
+ if (!rule || typeof rule.pattern !== 'string' || typeof rule.owner !== 'string') {
22
+ return [];
23
+ }
24
+ const { regex, error } = (0, regexSafety_js_1.compileSafeRegex)(rule.pattern);
25
+ if (!regex) {
26
+ (0, logger_js_1.logWarn)(`[ownershipService] Skipping ownership rule for "${rule.owner}": ${error}`);
27
+ return [];
28
+ }
29
+ return [{ owner: rule.owner, regex }];
30
+ })
31
+ : [];
18
32
  cached = { mtimeMs: stat.mtimeMs, rules };
19
33
  return rules;
20
34
  }
@@ -25,12 +39,8 @@ function loadRules() {
25
39
  function resolveOwner(id) {
26
40
  const rules = loadRules();
27
41
  for (const r of rules) {
28
- try {
29
- const re = new RegExp(r.pattern);
30
- if (re.test(id))
31
- return r.owner;
32
- }
33
- catch { /* ignore */ }
42
+ if (r.regex.test(id))
43
+ return r.owner;
34
44
  }
35
45
  return undefined;
36
46
  }
@@ -10,6 +10,7 @@ const child_process_1 = require("child_process");
10
10
  const perf_hooks_1 = require("perf_hooks");
11
11
  const fs_1 = __importDefault(require("fs"));
12
12
  const path_1 = __importDefault(require("path"));
13
+ const logger_js_1 = require("./logger.js");
13
14
  function startServer(enableUsage) {
14
15
  return new Promise((resolve) => {
15
16
  const env = {
@@ -143,37 +144,37 @@ function calculateOverhead(withoutUsage, withUsage) {
143
144
  return ((usageP95 - baselineP95) / baselineP95) * 100;
144
145
  }
145
146
  async function runPerformanceBaseline() {
146
- console.log('🚀 Starting Phase 1 Performance Baseline Measurement...');
147
- console.log('Target: <5% overhead for usage tracking');
148
- console.log();
147
+ (0, logger_js_1.logInfo)('🚀 Starting Phase 1 Performance Baseline Measurement...');
148
+ (0, logger_js_1.logInfo)('Target: <5% overhead for usage tracking');
149
+ (0, logger_js_1.logInfo)('---');
149
150
  // List operations
150
- console.log('📊 Measuring list operations...');
151
+ (0, logger_js_1.logInfo)('📊 Measuring list operations...');
151
152
  const listWithoutUsage = await measureListOperations(false);
152
153
  const listWithUsage = await measureListOperations(true);
153
154
  const listOverhead = calculateOverhead(listWithoutUsage, listWithUsage);
154
- console.log('✅ List operations measured');
155
- console.log(` Without usage: ${calculateStats(listWithoutUsage).p95.toFixed(2)}ms P95`);
156
- console.log(` With usage: ${calculateStats(listWithUsage).p95.toFixed(2)}ms P95`);
157
- console.log(` Overhead: ${listOverhead.toFixed(2)}%`);
158
- console.log();
155
+ (0, logger_js_1.logInfo)('✅ List operations measured');
156
+ (0, logger_js_1.logInfo)(` Without usage: ${calculateStats(listWithoutUsage).p95.toFixed(2)}ms P95`);
157
+ (0, logger_js_1.logInfo)(` With usage: ${calculateStats(listWithUsage).p95.toFixed(2)}ms P95`);
158
+ (0, logger_js_1.logInfo)(` Overhead: ${listOverhead.toFixed(2)}%`);
159
+ (0, logger_js_1.logInfo)('---');
159
160
  // Mutation operations
160
- console.log('📊 Measuring mutation operations...');
161
+ (0, logger_js_1.logInfo)('📊 Measuring mutation operations...');
161
162
  const mutationWithoutUsage = await measureMutationOperations(false);
162
163
  const mutationWithUsage = await measureMutationOperations(true);
163
164
  const mutationOverhead = calculateOverhead(mutationWithoutUsage, mutationWithUsage);
164
- console.log('✅ Mutation operations measured');
165
- console.log(` Without usage: ${calculateStats(mutationWithoutUsage).p95.toFixed(2)}ms P95`);
166
- console.log(` With usage: ${calculateStats(mutationWithUsage).p95.toFixed(2)}ms P95`);
167
- console.log(` Overhead: ${mutationOverhead.toFixed(2)}%`);
168
- console.log();
165
+ (0, logger_js_1.logInfo)('✅ Mutation operations measured');
166
+ (0, logger_js_1.logInfo)(` Without usage: ${calculateStats(mutationWithoutUsage).p95.toFixed(2)}ms P95`);
167
+ (0, logger_js_1.logInfo)(` With usage: ${calculateStats(mutationWithUsage).p95.toFixed(2)}ms P95`);
168
+ (0, logger_js_1.logInfo)(` Overhead: ${mutationOverhead.toFixed(2)}%`);
169
+ (0, logger_js_1.logInfo)('---');
169
170
  // Summary
170
171
  const maxOverhead = Math.max(listOverhead, mutationOverhead);
171
172
  const meetsTarget = maxOverhead <= 5.0;
172
- console.log('📈 Performance Baseline Summary:');
173
- console.log(` List operations overhead: ${listOverhead.toFixed(2)}%`);
174
- console.log(` Mutation operations overhead: ${mutationOverhead.toFixed(2)}%`);
175
- console.log(` Maximum overhead: ${maxOverhead.toFixed(2)}%`);
176
- console.log(` Target met (<5%): ${meetsTarget ? '✅ YES' : '❌ NO'}`);
173
+ (0, logger_js_1.logInfo)('📈 Performance Baseline Summary:');
174
+ (0, logger_js_1.logInfo)(` List operations overhead: ${listOverhead.toFixed(2)}%`);
175
+ (0, logger_js_1.logInfo)(` Mutation operations overhead: ${mutationOverhead.toFixed(2)}%`);
176
+ (0, logger_js_1.logInfo)(` Maximum overhead: ${maxOverhead.toFixed(2)}%`);
177
+ (0, logger_js_1.logInfo)(` Target met (<5%): ${meetsTarget ? '✅ YES' : '❌ NO'}`);
177
178
  const results = {
178
179
  listOperations: {
179
180
  withoutUsage: listWithoutUsage,
@@ -204,7 +205,7 @@ async function runPerformanceBaseline() {
204
205
  target: '<5% overhead',
205
206
  results
206
207
  }, null, 2));
207
- console.log();
208
- console.log(`💾 Detailed results saved to: ${baselineFile}`);
208
+ (0, logger_js_1.logInfo)('---');
209
+ (0, logger_js_1.logInfo)(`💾 Detailed results saved to: ${baselineFile}`);
209
210
  return results;
210
211
  }
@@ -20,7 +20,7 @@ export interface PromptIssue {
20
20
  match?: string;
21
21
  }
22
22
  export declare class PromptReviewService {
23
- private criteria;
23
+ private compiledCategories;
24
24
  /**
25
25
  * @param criteriaPath - Optional explicit path to a `PROMPT-CRITERIA.json` file.
26
26
  * When omitted the service searches a set of standard candidate locations.
@@ -32,6 +32,8 @@ export declare class PromptReviewService {
32
32
  * @returns Array of {@link PromptIssue} objects describing detected rule violations; empty when clean
33
33
  */
34
34
  review(prompt: string): PromptIssue[];
35
+ private compileCategories;
36
+ private compileRule;
35
37
  }
36
38
  /**
37
39
  * Aggregate a list of prompt issues into per-severity counts and identify the highest severity present.
@@ -7,8 +7,10 @@ exports.PromptReviewService = void 0;
7
7
  exports.summarizeIssues = summarizeIssues;
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const logger_js_1 = require("./logger.js");
11
+ const regexSafety_js_1 = require("./regexSafety.js");
10
12
  class PromptReviewService {
11
- criteria;
13
+ compiledCategories;
12
14
  /**
13
15
  * @param criteriaPath - Optional explicit path to a `PROMPT-CRITERIA.json` file.
14
16
  * When omitted the service searches a set of standard candidate locations.
@@ -41,14 +43,14 @@ class PromptReviewService {
41
43
  if (!loaded) {
42
44
  // Graceful fallback: empty criteria so server can still start.
43
45
  const msg = `[promptReviewService] WARN: Could not locate PROMPT-CRITERIA.json in any candidate paths. Using empty criteria.`;
44
- // Write to stderr explicitly (console.error already does)
45
- console.error(msg);
46
+ // Write to stderr explicitly (logWarn writes to stderr)
47
+ (0, logger_js_1.logWarn)(msg);
46
48
  loaded = { version: '0.0.0', categories: [] };
47
49
  }
48
50
  else {
49
- console.error(`[promptReviewService] Loaded criteria from ${usedPath}`); // stderr so it won't pollute stdout
51
+ (0, logger_js_1.logInfo)(`[promptReviewService] Loaded criteria from ${usedPath}`); // stderr so it won't pollute stdout
50
52
  }
51
- this.criteria = loaded;
53
+ this.compiledCategories = this.compileCategories(loaded);
52
54
  }
53
55
  /**
54
56
  * Run all loaded criteria rules against a prompt string and return any detected issues.
@@ -57,19 +59,17 @@ class PromptReviewService {
57
59
  */
58
60
  review(prompt) {
59
61
  const issues = [];
60
- for (const cat of this.criteria.categories) {
62
+ for (const cat of this.compiledCategories) {
61
63
  for (const rule of cat.rules) {
62
- if (rule.pattern) {
63
- // Apply global + case-insensitive flags for broader detection
64
- const regex = new RegExp(rule.pattern, 'gi');
65
- const m = prompt.match(regex);
64
+ if (rule.patternRegex) {
65
+ rule.patternRegex.lastIndex = 0;
66
+ const m = prompt.match(rule.patternRegex);
66
67
  if (m) {
67
68
  issues.push({ ruleId: rule.id, severity: rule.severity, description: rule.description, match: m[0] });
68
69
  }
69
70
  }
70
- if (rule.mustContain) {
71
- const mc = new RegExp(rule.mustContain, 'i');
72
- if (!mc.test(prompt)) {
71
+ if (rule.mustContainRegex) {
72
+ if (!rule.mustContainRegex.test(prompt)) {
73
73
  issues.push({ ruleId: rule.id, severity: rule.severity, description: 'Missing required token(s): ' + rule.description });
74
74
  }
75
75
  }
@@ -77,6 +77,34 @@ class PromptReviewService {
77
77
  }
78
78
  return issues;
79
79
  }
80
+ compileCategories(criteria) {
81
+ return criteria.categories.map((category) => ({
82
+ id: category.id,
83
+ rules: category.rules.map((rule) => this.compileRule(category.id, rule)),
84
+ }));
85
+ }
86
+ compileRule(categoryId, rule) {
87
+ const compiled = { ...rule };
88
+ if (rule.pattern) {
89
+ const { regex, error } = (0, regexSafety_js_1.compileSafeRegex)(rule.pattern, 'gi');
90
+ if (regex) {
91
+ compiled.patternRegex = regex;
92
+ }
93
+ else {
94
+ (0, logger_js_1.logWarn)(`[promptReviewService] Skipping unsafe pattern regex for rule "${rule.id}" in category "${categoryId}": ${error}`);
95
+ }
96
+ }
97
+ if (rule.mustContain) {
98
+ const { regex, error } = (0, regexSafety_js_1.compileSafeRegex)(rule.mustContain, 'i');
99
+ if (regex) {
100
+ compiled.mustContainRegex = regex;
101
+ }
102
+ else {
103
+ (0, logger_js_1.logWarn)(`[promptReviewService] Skipping unsafe mustContain regex for rule "${rule.id}" in category "${categoryId}": ${error}`);
104
+ }
105
+ }
106
+ return compiled;
107
+ }
80
108
  }
81
109
  exports.PromptReviewService = PromptReviewService;
82
110
  /**
@@ -0,0 +1,6 @@
1
+ export declare const MAX_REGEX_PATTERN_LENGTH = 200;
2
+ export declare function getRegexSafetyError(pattern: string): string | undefined;
3
+ export declare function compileSafeRegex(pattern: string, flags?: string): {
4
+ regex?: RegExp;
5
+ error?: string;
6
+ };
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MAX_REGEX_PATTERN_LENGTH = void 0;
7
+ exports.getRegexSafetyError = getRegexSafetyError;
8
+ exports.compileSafeRegex = compileSafeRegex;
9
+ const safe_regex2_1 = __importDefault(require("safe-regex2"));
10
+ exports.MAX_REGEX_PATTERN_LENGTH = 200;
11
+ function getRegexSafetyError(pattern) {
12
+ if (pattern.length > exports.MAX_REGEX_PATTERN_LENGTH) {
13
+ return 'Regex patterns must not exceed 200 characters to prevent ReDoS';
14
+ }
15
+ if (/\([^)]*[+*}]\)[+*{]/.test(pattern)) {
16
+ return 'Regex pattern rejected: nested quantifiers can cause catastrophic backtracking';
17
+ }
18
+ if (/\)[+*}][^(]*\)[+*{]/.test(pattern)) {
19
+ return 'Regex pattern rejected: nested quantifiers can cause catastrophic backtracking';
20
+ }
21
+ if (/\([^)]*\|[^)]*\)[+*]{1,}/.test(pattern)) {
22
+ return 'Regex pattern rejected: alternation with quantifiers can cause catastrophic backtracking';
23
+ }
24
+ try {
25
+ new RegExp(pattern);
26
+ }
27
+ catch {
28
+ return `Invalid regex pattern "${pattern}": check syntax and try again`;
29
+ }
30
+ if (!(0, safe_regex2_1.default)(pattern)) {
31
+ return 'Regex pattern rejected: potentially catastrophic backtracking detected';
32
+ }
33
+ return undefined;
34
+ }
35
+ function compileSafeRegex(pattern, flags) {
36
+ const error = getRegexSafetyError(pattern);
37
+ if (error) {
38
+ return { error };
39
+ }
40
+ try {
41
+ return { regex: new RegExp(pattern, flags) };
42
+ }
43
+ catch {
44
+ return { error: `Invalid regex pattern "${pattern}": check syntax and try again` };
45
+ }
46
+ }
@@ -94,7 +94,7 @@ Or add directly:
94
94
 
95
95
  - \`index_groom\` — clean duplicates and stale entries
96
96
  - \`index_governanceUpdate\` — deprecate outdated content (don't silently delete)
97
- - \`feedback_dispatch\` with action="submit" — report issues or request features
97
+ - \`feedback_submit\` — report issues or request features
98
98
  - \`usage_track\` — signal when guidance was helpful or outdated
99
99
 
100
100
  ---
@@ -4,8 +4,13 @@
4
4
  * Creates the appropriate IInstructionStore implementation based on config.
5
5
  * Default: JsonFileStore (json). Experimental: SqliteStore (sqlite).
6
6
  */
7
- import type { IInstructionStore } from './types.js';
7
+ import type { IInstructionStore, IEmbeddingStore } from './types.js';
8
8
  export type StorageBackend = 'json' | 'sqlite';
9
+ /**
10
+ * Check that the current Node.js version meets the minimum requirement.
11
+ * Throws a clear error if the version is too old.
12
+ */
13
+ export declare function checkNodeVersion(minVersion: string, feature: string): void;
9
14
  /**
10
15
  * Create a storage backend instance.
11
16
  *
@@ -15,3 +20,11 @@ export type StorageBackend = 'json' | 'sqlite';
15
20
  * @returns An IInstructionStore implementation.
16
21
  */
17
22
  export declare function createStore(backend?: StorageBackend, dir?: string, sqlitePath?: string): IInstructionStore;
23
+ /**
24
+ * Create an embedding store instance.
25
+ *
26
+ * @param backend - Override backend type (default: from config).
27
+ * @param embeddingPath - Override embedding file/db path (default: from config).
28
+ * @returns An IEmbeddingStore implementation.
29
+ */
30
+ export declare function createEmbeddingStore(backend?: StorageBackend, embeddingPath?: string): IEmbeddingStore;
@@ -6,10 +6,31 @@
6
6
  * Default: JsonFileStore (json). Experimental: SqliteStore (sqlite).
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.checkNodeVersion = checkNodeVersion;
9
10
  exports.createStore = createStore;
11
+ exports.createEmbeddingStore = createEmbeddingStore;
10
12
  const runtimeConfig_js_1 = require("../../config/runtimeConfig.js");
11
13
  const jsonFileStore_js_1 = require("./jsonFileStore.js");
14
+ const jsonEmbeddingStore_js_1 = require("./jsonEmbeddingStore.js");
12
15
  const sqliteStore_js_1 = require("./sqliteStore.js");
16
+ const logger_js_1 = require("../logger.js");
17
+ /**
18
+ * Check that the current Node.js version meets the minimum requirement.
19
+ * Throws a clear error if the version is too old.
20
+ */
21
+ function checkNodeVersion(minVersion, feature) {
22
+ const current = process.versions.node;
23
+ const cur = current.split('.').map(Number);
24
+ const min = minVersion.split('.').map(Number);
25
+ for (let i = 0; i < 3; i++) {
26
+ if ((cur[i] ?? 0) > (min[i] ?? 0))
27
+ return;
28
+ if ((cur[i] ?? 0) < (min[i] ?? 0)) {
29
+ throw new Error(`Node.js ${minVersion}+ required for ${feature} (current: ${current}). ` +
30
+ `Please upgrade Node.js or switch to the JSON storage backend.`);
31
+ }
32
+ }
33
+ }
13
34
  /**
14
35
  * Create a storage backend instance.
15
36
  *
@@ -24,7 +45,8 @@ function createStore(backend, dir, sqlitePath) {
24
45
  const resolvedDir = dir ?? config.index?.baseDir;
25
46
  switch (resolvedBackend) {
26
47
  case 'sqlite': {
27
- console.warn('[storage] ⚠️ EXPERIMENTAL: SQLite backend is enabled. This feature has limited testing and may have data-loss or compatibility issues. Not recommended for production use.');
48
+ checkNodeVersion('22.5.0', 'SQLite storage backend (node:sqlite)');
49
+ (0, logger_js_1.logWarn)('[storage] ⚠️ EXPERIMENTAL: SQLite backend is enabled. This feature has limited testing and may have data-loss or compatibility issues. Not recommended for production use.');
28
50
  const dbPath = sqlitePath ?? config.storage?.sqlitePath ?? 'data/index.db';
29
51
  return new sqliteStore_js_1.SqliteStore(dbPath);
30
52
  }
@@ -33,3 +55,41 @@ function createStore(backend, dir, sqlitePath) {
33
55
  return new jsonFileStore_js_1.JsonFileStore(resolvedDir);
34
56
  }
35
57
  }
58
+ /**
59
+ * Create an embedding store instance.
60
+ *
61
+ * @param backend - Override backend type (default: from config).
62
+ * @param embeddingPath - Override embedding file/db path (default: from config).
63
+ * @returns An IEmbeddingStore implementation.
64
+ */
65
+ function createEmbeddingStore(backend, embeddingPath) {
66
+ const config = (0, runtimeConfig_js_1.getRuntimeConfig)();
67
+ const resolvedBackend = backend ?? config.storage?.backend ?? 'json';
68
+ switch (resolvedBackend) {
69
+ case 'sqlite': {
70
+ // Check if sqlite-vec is explicitly disabled via config
71
+ if (config.storage?.sqliteVecEnabled === false) {
72
+ const jsonPath = embeddingPath ?? config.semantic?.embeddingPath ?? 'data/embeddings.json';
73
+ return new jsonEmbeddingStore_js_1.JsonEmbeddingStore(jsonPath);
74
+ }
75
+ checkNodeVersion('22.13.0', 'sqlite-vec extension (DatabaseSync.loadExtension)');
76
+ // Lazy-load SqliteEmbeddingStore to avoid import errors when sqlite-vec is unavailable
77
+ try {
78
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
79
+ const { SqliteEmbeddingStore } = require('./sqliteEmbeddingStore.js');
80
+ const dbPath = embeddingPath ?? (config.storage?.sqliteVecPath || 'data/embeddings.db');
81
+ return new SqliteEmbeddingStore(dbPath);
82
+ }
83
+ catch (err) {
84
+ (0, logger_js_1.logWarn)(`[storage] sqlite-vec embedding store failed to initialize: ${err instanceof Error ? err.message : 'unknown'}. Falling back to JSON.`);
85
+ const jsonPath = embeddingPath ?? config.semantic?.embeddingPath ?? 'data/embeddings.json';
86
+ return new jsonEmbeddingStore_js_1.JsonEmbeddingStore(jsonPath);
87
+ }
88
+ }
89
+ case 'json':
90
+ default: {
91
+ const jsonPath = embeddingPath ?? config.semantic?.embeddingPath ?? 'data/embeddings.json';
92
+ return new jsonEmbeddingStore_js_1.JsonEmbeddingStore(jsonPath);
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * JsonEmbeddingStore — IEmbeddingStore backed by a flat JSON file.
3
+ *
4
+ * Wraps the existing embeddings.json disk cache format.
5
+ * Search is brute-force cosine similarity (no indexing).
6
+ */
7
+ import type { IEmbeddingStore, EmbeddingCacheData, EmbeddingSearchResult } from './types.js';
8
+ export declare class JsonEmbeddingStore implements IEmbeddingStore {
9
+ private filePath;
10
+ constructor(filePath: string);
11
+ load(): EmbeddingCacheData | null;
12
+ save(data: EmbeddingCacheData): void;
13
+ search(queryVector: Float32Array, limit: number): EmbeddingSearchResult[];
14
+ close(): void;
15
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * JsonEmbeddingStore — IEmbeddingStore backed by a flat JSON file.
4
+ *
5
+ * Wraps the existing embeddings.json disk cache format.
6
+ * Search is brute-force cosine similarity (no indexing).
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.JsonEmbeddingStore = void 0;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const path_1 = __importDefault(require("path"));
15
+ /**
16
+ * Cosine similarity between two numeric arrays.
17
+ */
18
+ function cosineSimilarity(a, b) {
19
+ let dot = 0;
20
+ let normA = 0;
21
+ let normB = 0;
22
+ for (let i = 0; i < a.length; i++) {
23
+ dot += a[i] * b[i];
24
+ normA += a[i] * a[i];
25
+ normB += b[i] * b[i];
26
+ }
27
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
28
+ if (denom === 0)
29
+ return 0;
30
+ return dot / denom;
31
+ }
32
+ class JsonEmbeddingStore {
33
+ filePath;
34
+ constructor(filePath) {
35
+ this.filePath = filePath;
36
+ }
37
+ load() {
38
+ try {
39
+ if (!fs_1.default.existsSync(this.filePath))
40
+ return null;
41
+ const raw = fs_1.default.readFileSync(this.filePath, 'utf-8');
42
+ const data = JSON.parse(raw);
43
+ // Backwards-compat: older caches used 'catalogHash'
44
+ if (data && typeof data.catalogHash === 'string' && typeof data.indexHash !== 'string') {
45
+ data.indexHash = data.catalogHash;
46
+ delete data.catalogHash;
47
+ }
48
+ if (!data || typeof data.indexHash !== 'string' || typeof data.embeddings !== 'object')
49
+ return null;
50
+ return data;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ save(data) {
57
+ const dir = path_1.default.dirname(this.filePath);
58
+ if (!fs_1.default.existsSync(dir)) {
59
+ fs_1.default.mkdirSync(dir, { recursive: true });
60
+ }
61
+ fs_1.default.writeFileSync(this.filePath, JSON.stringify(data), 'utf-8');
62
+ }
63
+ search(queryVector, limit) {
64
+ if (limit <= 0)
65
+ return [];
66
+ const cached = this.load();
67
+ if (!cached)
68
+ return [];
69
+ const query = Array.from(queryVector);
70
+ const results = [];
71
+ for (const [id, vec] of Object.entries(cached.embeddings)) {
72
+ const similarity = cosineSimilarity(query, vec);
73
+ // Distance = 1 - similarity (lower is closer)
74
+ results.push({ id, distance: 1 - similarity });
75
+ }
76
+ results.sort((a, b) => a.distance - b.distance);
77
+ return results.slice(0, limit);
78
+ }
79
+ close() {
80
+ // No resources to release for file-based store
81
+ }
82
+ }
83
+ exports.JsonEmbeddingStore = JsonEmbeddingStore;
@@ -19,7 +19,9 @@ export declare class JsonFileStore implements IInstructionStore {
19
19
  load(): LoadResult;
20
20
  close(): void;
21
21
  get(id: string): InstructionEntry | null;
22
- write(entry: InstructionEntry): void;
22
+ write(entry: InstructionEntry, opts?: {
23
+ createOnly?: boolean;
24
+ }): void;
23
25
  remove(id: string): void;
24
26
  list(opts?: ListOptions): InstructionEntry[];
25
27
  query(opts: QueryOptions): InstructionEntry[];
@@ -14,8 +14,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.JsonFileStore = void 0;
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const path_1 = __importDefault(require("path"));
17
- const crypto_1 = __importDefault(require("crypto"));
18
17
  const hashUtils_js_1 = require("./hashUtils.js");
18
+ const atomicFs_js_1 = require("../atomicFs.js");
19
19
  class JsonFileStore {
20
20
  dir;
21
21
  cache = new Map();
@@ -87,12 +87,14 @@ class JsonFileStore {
87
87
  this.ensureLoaded();
88
88
  return this.cache.get(id) ?? null;
89
89
  }
90
- write(entry) {
91
- // Write to disk
90
+ write(entry, opts) {
92
91
  const filePath = path_1.default.join(this.dir, `${entry.id}.json`);
93
- const tmpPath = `${filePath}.${crypto_1.default.randomBytes(4).toString('hex')}.tmp`;
94
- fs_1.default.writeFileSync(tmpPath, JSON.stringify(entry, null, 2), 'utf-8');
95
- fs_1.default.renameSync(tmpPath, filePath);
92
+ if (opts?.createOnly) {
93
+ (0, atomicFs_js_1.atomicCreateJson)(filePath, entry);
94
+ }
95
+ else {
96
+ (0, atomicFs_js_1.atomicWriteJson)(filePath, entry);
97
+ }
96
98
  // Update in-memory cache
97
99
  this.cache.set(entry.id, entry);
98
100
  this.loaded = true;
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Uses Node.js built-in node:sqlite. Zero third-party dependencies.
5
5
  */
6
+ import type { IEmbeddingStore } from './types.js';
6
7
  export interface MigrationOptions {
7
8
  onProgress?: (current: number, total: number) => void;
8
9
  }
@@ -33,3 +34,15 @@ export declare function migrateJsonToSqlite(jsonDir: string, dbPath: string, opt
33
34
  * Each entry becomes a separate .json file named by ID.
34
35
  */
35
36
  export declare function migrateSqliteToJson(dbPath: string, jsonDir: string, opts?: MigrationOptions): ExportResult;
37
+ export interface EmbeddingMigrationResult {
38
+ migrated: number;
39
+ skipped: number;
40
+ error?: string;
41
+ }
42
+ /**
43
+ * Migrate embeddings from a JSON file to an IEmbeddingStore (e.g. SqliteEmbeddingStore).
44
+ *
45
+ * Reads the JSON embedding cache and saves it into the target store.
46
+ * Idempotent: calling again with the same data overwrites safely.
47
+ */
48
+ export declare function migrateJsonEmbeddingsToStore(jsonEmbeddingPath: string, targetStore: IEmbeddingStore): EmbeddingMigrationResult;