@jagilber-org/index-server 1.22.1 → 1.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +87 -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 +11 -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/setup-wizard.mjs +781 -0
  183. package/server.json +1 -0
  184. package/dist/externalClientLib.d.ts +0 -1
  185. package/dist/externalClientLib.js +0 -2
  186. package/dist/portableClientWrapper.d.ts +0 -1
  187. package/dist/portableClientWrapper.js +0 -2
  188. package/dist/services/indexingService.d.ts +0 -1
  189. package/dist/services/indexingService.js +0 -2
@@ -1,60 +1,81 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const registry_1 = require("../server/registry");
4
- /**
5
- * diagnostics_block: Intentionally CPU blocks the event loop for a specified number of milliseconds.
6
- * Purpose: Reproduce / probe health_check hang or starvation behavior under synchronous handler saturation.
7
- * NOTE: This is test/instrumentation oriented and not part of stable tool surface.
8
- */
9
- (0, registry_1.registerHandler)('diagnostics_block', (p) => {
10
- const ms = typeof p.ms === 'number' ? Math.min(Math.max(p.ms, 0), 10_000) : 250; // cap at 10s
11
- const start = Date.now();
12
- // Busy-loop (intentional) to simulate CPU starvation in a single-threaded event loop
13
- // eslint-disable-next-line no-empty
14
- while (Date.now() - start < ms) { /* block */ }
15
- return { blockedMs: ms, startedAt: new Date(start).toISOString(), endedAt: new Date().toISOString() };
16
- });
17
- /**
18
- * diagnostics_microtaskFlood: Schedules a large number of microtasks (Promise.resolve chains)
19
- * to create event loop turn pressure without pure synchronous blocking.
20
- * Useful to probe starvation scenarios distinct from a tight busy loop.
21
- */
22
- (0, registry_1.registerHandler)('diagnostics_microtaskFlood', async (p) => {
23
- const count = typeof p.count === 'number' ? Math.min(Math.max(p.count, 0), 200_000) : 25_000;
24
- let ops = 0;
25
- // Chain microtasks in batches to avoid blowing the call stack while still flooding.
26
- function batch(n) {
27
- if (n <= 0)
28
- return Promise.resolve();
29
- return Promise.resolve().then(() => { ops++; }).then(() => batch(n - 1));
30
- }
31
- const start = Date.now();
32
- await batch(count);
33
- return { scheduled: count, executed: ops, ms: Date.now() - start };
34
- });
35
- /**
36
- * diagnostics_memoryPressure: Allocates transient buffers to induce GC / memory pressure.
37
- * Allocation is bounded & immediately released (locally scoped) before returning.
38
- */
39
- (0, registry_1.registerHandler)('diagnostics_memoryPressure', (p) => {
40
- const mb = typeof p.mb === 'number' ? Math.min(Math.max(p.mb, 1), 512) : 64; // cap to 512MB
41
- const start = Date.now();
42
- const blocks = [];
43
- const PER = 4 * 1024 * 1024; // 4MB per block
44
- const needed = Math.ceil((mb * 1024 * 1024) / PER);
45
- for (let i = 0; i < needed; i++) {
46
- const b = Buffer.allocUnsafe(PER);
47
- // touch a few bytes to ensure physical commit
48
- b[0] = 1;
49
- b[PER - 1] = 1;
50
- blocks.push(b);
51
- }
52
- const allocMs = Date.now() - start;
53
- // Release references so GC can reclaim
54
- return { requestedMB: mb, blocks: blocks.length, perBlockBytes: PER, allocMs };
55
- });
56
- // Augment global type locally (non-invasive)
57
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
4
+ const auditLog_1 = require("./auditLog");
5
+ const envUtils_1 = require("../utils/envUtils");
6
+ const MAX_BLOCK_MS = 1_000;
7
+ const DEFAULT_BLOCK_MS = 100;
8
+ const MAX_MICROTASK_COUNT = 25_000;
9
+ const DEFAULT_MICROTASK_COUNT = 5_000;
10
+ const MAX_MEMORY_MB = 64;
11
+ const DEFAULT_MEMORY_MB = 16;
12
+ function diagnosticsDisabled(tool) {
13
+ return {
14
+ error: 'diagnostics_disabled',
15
+ tool,
16
+ message: 'Dangerous diagnostics tools require INDEX_SERVER_DEBUG=1 or INDEX_SERVER_STRESS_DIAG=1.',
17
+ };
18
+ }
19
+ if ((0, envUtils_1.dangerousDiagnosticsEnabled)()) {
20
+ /**
21
+ * diagnostics_block: Intentionally CPU blocks the event loop for a specified number of milliseconds.
22
+ * Purpose: Reproduce / probe health_check hang or starvation behavior under synchronous handler saturation.
23
+ * NOTE: This is test/instrumentation oriented and not part of stable tool surface.
24
+ */
25
+ (0, registry_1.registerHandler)('diagnostics_block', (p) => {
26
+ if (!(0, envUtils_1.dangerousDiagnosticsEnabled)())
27
+ return diagnosticsDisabled('diagnostics_block');
28
+ const ms = typeof p.ms === 'number' ? Math.min(Math.max(p.ms, 0), MAX_BLOCK_MS) : DEFAULT_BLOCK_MS;
29
+ const start = Date.now();
30
+ (0, auditLog_1.logAudit)('diagnostics_block', undefined, { phase: 'start', requestedMs: p.ms, effectiveMs: ms }, 'mutation');
31
+ while (Date.now() - start < ms) { /* intentionally blocking */ }
32
+ return { blockedMs: ms, startedAt: new Date(start).toISOString(), endedAt: new Date().toISOString() };
33
+ });
34
+ /**
35
+ * diagnostics_microtaskFlood: Schedules a large number of microtasks (Promise.resolve chains)
36
+ * to create event loop turn pressure without pure synchronous blocking.
37
+ * Useful to probe starvation scenarios distinct from a tight busy loop.
38
+ */
39
+ (0, registry_1.registerHandler)('diagnostics_microtaskFlood', async (p) => {
40
+ if (!(0, envUtils_1.dangerousDiagnosticsEnabled)())
41
+ return diagnosticsDisabled('diagnostics_microtaskFlood');
42
+ const count = typeof p.count === 'number' ? Math.min(Math.max(p.count, 0), MAX_MICROTASK_COUNT) : DEFAULT_MICROTASK_COUNT;
43
+ let ops = 0;
44
+ (0, auditLog_1.logAudit)('diagnostics_microtaskFlood', undefined, { phase: 'start', requestedCount: p.count, effectiveCount: count }, 'mutation');
45
+ function batch(n) {
46
+ if (n <= 0)
47
+ return Promise.resolve();
48
+ return Promise.resolve().then(() => { ops++; }).then(() => batch(n - 1));
49
+ }
50
+ const start = Date.now();
51
+ await batch(count);
52
+ return { scheduled: count, executed: ops, ms: Date.now() - start };
53
+ });
54
+ /**
55
+ * diagnostics_memoryPressure: Allocates transient buffers to induce GC / memory pressure.
56
+ * Allocation is bounded & immediately released (locally scoped) before returning.
57
+ */
58
+ (0, registry_1.registerHandler)('diagnostics_memoryPressure', (p) => {
59
+ if (!(0, envUtils_1.dangerousDiagnosticsEnabled)())
60
+ return diagnosticsDisabled('diagnostics_memoryPressure');
61
+ const mb = typeof p.mb === 'number' ? Math.min(Math.max(p.mb, 1), MAX_MEMORY_MB) : DEFAULT_MEMORY_MB;
62
+ const start = Date.now();
63
+ const blocks = [];
64
+ (0, auditLog_1.logAudit)('diagnostics_memoryPressure', undefined, { phase: 'start', requestedMB: p.mb, effectiveMB: mb }, 'mutation');
65
+ const PER = 4 * 1024 * 1024; // 4MB per block
66
+ const needed = Math.ceil((mb * 1024 * 1024) / PER);
67
+ for (let i = 0; i < needed; i++) {
68
+ const b = Buffer.allocUnsafe(PER);
69
+ // touch a few bytes to ensure physical commit
70
+ b[0] = 1;
71
+ b[PER - 1] = 1;
72
+ blocks.push(b);
73
+ }
74
+ const allocMs = Date.now() - start;
75
+ // Release references so GC can reclaim
76
+ return { requestedMB: mb, blocks: blocks.length, perBlockBytes: PER, allocMs };
77
+ });
78
+ }
58
79
  const gRef = global;
59
80
  (0, registry_1.registerHandler)('diagnostics_handshake', () => {
60
81
  const buf = gRef.HANDSHAKE_EVENTS_REF;
@@ -1,15 +1,8 @@
1
1
  /**
2
- * Feedback/Emit System for Index Server
2
+ * MCP feedback handler submit-only surface.
3
3
  *
4
- * Provides MCP protocol-compliant tools for clients to submit structured feedback,
5
- * status reports, security issues, feature requests, and other communications
6
- * to server administrators or monitoring systems.
7
- *
8
- * This system follows MCP best practices:
9
- * - Tools are discoverable via tools/list
10
- * - Input validation via JSON schemas
11
- * - Structured responses for programmatic consumption
12
- * - Proper error handling and logging
13
- * - Audit trail for security and compliance
4
+ * Only feedback_submit is exposed via MCP.
5
+ * Storage I/O is delegated to the shared feedbackStorage module so the
6
+ * dashboard CRUD phase can use the same persisted file without duplicating logic.
14
7
  */
15
8
  export {};
@@ -1,119 +1,20 @@
1
1
  "use strict";
2
2
  /**
3
- * Feedback/Emit System for Index Server
3
+ * MCP feedback handler submit-only surface.
4
4
  *
5
- * Provides MCP protocol-compliant tools for clients to submit structured feedback,
6
- * status reports, security issues, feature requests, and other communications
7
- * to server administrators or monitoring systems.
8
- *
9
- * This system follows MCP best practices:
10
- * - Tools are discoverable via tools/list
11
- * - Input validation via JSON schemas
12
- * - Structured responses for programmatic consumption
13
- * - Proper error handling and logging
14
- * - Audit trail for security and compliance
5
+ * Only feedback_submit is exposed via MCP.
6
+ * Storage I/O is delegated to the shared feedbackStorage module so the
7
+ * dashboard CRUD phase can use the same persisted file without duplicating logic.
15
8
  */
16
- var __importDefault = (this && this.__importDefault) || function (mod) {
17
- return (mod && mod.__esModule) ? mod : { "default": mod };
18
- };
19
9
  Object.defineProperty(exports, "__esModule", { value: true });
20
10
  const registry_1 = require("../server/registry");
21
11
  const logger_1 = require("./logger");
22
12
  const auditLog_1 = require("./auditLog");
23
- const runtimeConfig_1 = require("../config/runtimeConfig");
24
- const fs_1 = __importDefault(require("fs"));
25
- const path_1 = __importDefault(require("path"));
26
- const crypto_1 = require("crypto");
27
- // Directory is resolved dynamically so tests can override via env per test case.
28
- // Fall back to config for max entries; tests may still override INDEX_SERVER_FEEDBACK_DIR.
29
- function getMaxEntries() {
30
- return (0, runtimeConfig_1.getRuntimeConfig)().feedback.maxEntries;
31
- }
32
- function getFeedbackDir() {
33
- return (0, runtimeConfig_1.getRuntimeConfig)().feedback.dir;
34
- }
35
- function getFeedbackFile() {
36
- return path_1.default.join(getFeedbackDir(), 'feedback-entries.json');
37
- }
38
- function ensureFeedbackDir() {
39
- const dir = getFeedbackDir();
40
- if (!fs_1.default.existsSync(dir)) {
41
- try {
42
- fs_1.default.mkdirSync(dir, { recursive: true });
43
- }
44
- catch (error) {
45
- (0, logger_1.logError)('[feedback] Failed to create feedback directory', { error: String(error), dir });
46
- }
47
- }
48
- return dir;
49
- }
50
- /**
51
- * Load feedback entries from storage
52
- */
53
- function loadFeedbackStorage() {
54
- const file = getFeedbackFile();
55
- ensureFeedbackDir();
56
- try {
57
- if (fs_1.default.existsSync(file)) {
58
- const content = fs_1.default.readFileSync(file, 'utf8');
59
- const parsed = JSON.parse(content);
60
- // Validate structure
61
- if (!parsed.entries || !Array.isArray(parsed.entries)) {
62
- throw new Error('Invalid feedback storage format');
63
- }
64
- return parsed;
65
- }
66
- }
67
- catch (error) {
68
- (0, logger_1.logWarn)('[feedback] Failed to load feedback storage, initializing empty', { error: String(error) });
69
- }
70
- return {
71
- entries: [],
72
- lastUpdated: new Date().toISOString(),
73
- version: '1.0.0'
74
- };
75
- }
76
- /**
77
- * Save feedback entries to storage
78
- */
79
- function saveFeedbackStorage(storage) {
80
- const file = getFeedbackFile();
81
- ensureFeedbackDir();
82
- try {
83
- // Limit storage size
84
- const maxEntries = getMaxEntries();
85
- if (storage.entries.length > maxEntries) {
86
- // Keep most recent entries
87
- storage.entries = storage.entries
88
- .sort((a, b) => b.timestamp.localeCompare(a.timestamp))
89
- .slice(0, maxEntries);
90
- }
91
- storage.lastUpdated = new Date().toISOString();
92
- const content = JSON.stringify(storage, null, 2);
93
- fs_1.default.writeFileSync(file, content, 'utf8');
94
- }
95
- catch (error) {
96
- (0, logger_1.logError)('[feedback] Failed to save feedback storage', error instanceof Error ? error : { error: String(error) });
97
- throw error;
98
- }
99
- }
100
- /**
101
- * Generate unique ID for feedback entry
102
- */
103
- function generateFeedbackId(type, timestamp) {
104
- const hash = (0, crypto_1.createHash)('sha256');
105
- hash.update(`${type}-${timestamp}-${Math.random()}`);
106
- return hash.digest('hex').substring(0, 16);
107
- }
108
- /**
109
- * feedback_submit - Submit new feedback entry
110
- */
13
+ const feedbackStorage_1 = require("./feedbackStorage");
111
14
  (0, registry_1.registerHandler)('feedback_submit', (params) => {
112
- // Validate required parameters
113
15
  if (!params.type || !params.severity || !params.title || !params.description) {
114
16
  throw new Error('Missing required parameters: type, severity, title, description');
115
17
  }
116
- // Validate enum values
117
18
  const validTypes = ['issue', 'status', 'security', 'feature-request', 'bug-report', 'performance', 'usability', 'other'];
118
19
  const validSeverities = ['low', 'medium', 'high', 'critical'];
119
20
  if (!validTypes.includes(params.type)) {
@@ -124,21 +25,20 @@ function generateFeedbackId(type, timestamp) {
124
25
  }
125
26
  const timestamp = new Date().toISOString();
126
27
  const entry = {
127
- id: generateFeedbackId(params.type, timestamp),
28
+ id: (0, feedbackStorage_1.generateFeedbackId)(params.type, timestamp),
128
29
  timestamp,
129
30
  type: params.type,
130
31
  severity: params.severity,
131
- title: params.title.substring(0, 200), // Limit title length
132
- description: params.description.substring(0, 10000), // Limit description length
32
+ title: params.title.substring(0, 200),
33
+ description: params.description.substring(0, 10000),
133
34
  context: params.context,
134
35
  metadata: params.metadata,
135
- tags: params.tags?.slice(0, 10), // Limit number of tags
36
+ tags: params.tags?.slice(0, 10),
136
37
  status: 'new'
137
38
  };
138
- const storage = loadFeedbackStorage();
39
+ const storage = (0, feedbackStorage_1.loadFeedbackStorage)();
139
40
  storage.entries.push(entry);
140
- saveFeedbackStorage(storage);
141
- // Log feedback submission for audit trail
41
+ (0, feedbackStorage_1.saveFeedbackStorage)(storage);
142
42
  (0, auditLog_1.logAudit)('feedback_submit', [entry.id], {
143
43
  type: entry.type,
144
44
  severity: entry.severity,
@@ -150,7 +50,6 @@ function generateFeedbackId(type, timestamp) {
150
50
  severity: entry.severity,
151
51
  title: entry.title
152
52
  });
153
- // For security issues, also log to stderr for immediate visibility
154
53
  if (entry.type === 'security' || entry.severity === 'critical') {
155
54
  try {
156
55
  process.stderr.write(`[SECURITY/CRITICAL] Feedback ID: ${entry.id}, Type: ${entry.type}, Title: ${entry.title}\n`);
@@ -166,224 +65,3 @@ function generateFeedbackId(type, timestamp) {
166
65
  message: 'Feedback submitted successfully'
167
66
  };
168
67
  });
169
- /**
170
- * feedback_list - List feedback entries with filtering
171
- */
172
- (0, registry_1.registerHandler)('feedback_list', (params) => {
173
- const storage = loadFeedbackStorage();
174
- let entries = [...storage.entries];
175
- // Apply filters
176
- if (params.type) {
177
- entries = entries.filter(e => e.type === params.type);
178
- }
179
- if (params.severity) {
180
- entries = entries.filter(e => e.severity === params.severity);
181
- }
182
- if (params.status) {
183
- entries = entries.filter(e => e.status === params.status);
184
- }
185
- if (typeof params.since === 'string' && params.since) {
186
- const sinceVal = params.since;
187
- entries = entries.filter(e => e.timestamp >= sinceVal);
188
- }
189
- if (params.tags && params.tags.length > 0) {
190
- entries = entries.filter(e => e.tags && params.tags.some(tag => e.tags.includes(tag)));
191
- }
192
- // Sort by timestamp (newest first)
193
- entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
194
- // Apply pagination
195
- const limit = Math.min(params.limit || 50, 200); // Max 200 entries per request
196
- const offset = params.offset || 0;
197
- const paginatedEntries = entries.slice(offset, offset + limit);
198
- return {
199
- entries: paginatedEntries,
200
- total: entries.length,
201
- limit,
202
- offset,
203
- hasMore: offset + limit < entries.length
204
- };
205
- });
206
- /**
207
- * feedback_get - Get specific feedback entry by ID
208
- */
209
- (0, registry_1.registerHandler)('feedback_get', (params) => {
210
- if (!params.id) {
211
- throw new Error('Missing required parameter: id');
212
- }
213
- const storage = loadFeedbackStorage();
214
- const entry = storage.entries.find(e => e.id === params.id);
215
- if (!entry) {
216
- return { notFound: true, id: params.id, hint: `No feedback entry found with id "${params.id}". Use feedback_list to see all entries.` };
217
- }
218
- return { entry };
219
- });
220
- /**
221
- * feedback_update - Update feedback entry status (admin function)
222
- */
223
- (0, registry_1.registerHandler)('feedback_update', (params) => {
224
- if (!params.id) {
225
- throw new Error('Missing required parameter: id');
226
- }
227
- const validStatuses = ['new', 'acknowledged', 'in-progress', 'resolved', 'closed'];
228
- if (params.status && !validStatuses.includes(params.status)) {
229
- throw new Error(`Invalid status. Must be one of: ${validStatuses.join(', ')}`);
230
- }
231
- const storage = loadFeedbackStorage();
232
- const entryIndex = storage.entries.findIndex(e => e.id === params.id);
233
- if (entryIndex === -1) {
234
- return { notFound: true, id: params.id, hint: `No feedback entry found with id "${params.id}". Use feedback_list to see all entries.` };
235
- }
236
- const entry = storage.entries[entryIndex];
237
- const oldStatus = entry.status;
238
- // Update fields
239
- if (params.status) {
240
- entry.status = params.status;
241
- }
242
- if (params.metadata) {
243
- entry.metadata = { ...entry.metadata, ...params.metadata };
244
- }
245
- // Add update timestamp to metadata
246
- entry.metadata = {
247
- ...entry.metadata,
248
- lastUpdated: new Date().toISOString(),
249
- updatedBy: 'system' // Could be enhanced to track admin user
250
- };
251
- storage.entries[entryIndex] = entry;
252
- saveFeedbackStorage(storage);
253
- (0, auditLog_1.logAudit)('feedback_update', [entry.id], {
254
- oldStatus,
255
- newStatus: entry.status,
256
- type: entry.type
257
- });
258
- (0, logger_1.logInfo)('[feedback] Feedback entry updated', {
259
- id: entry.id,
260
- oldStatus,
261
- newStatus: entry.status,
262
- type: entry.type
263
- });
264
- return {
265
- success: true,
266
- entry,
267
- message: 'Feedback entry updated successfully'
268
- };
269
- });
270
- /**
271
- * feedback_stats - Get feedback statistics and metrics
272
- */
273
- (0, registry_1.registerHandler)('feedback_stats', (params) => {
274
- const storage = loadFeedbackStorage();
275
- let entries = storage.entries;
276
- // Filter by date if specified
277
- if (params.since) {
278
- const sinceDate = params.since;
279
- entries = entries.filter(e => e.timestamp >= sinceDate);
280
- }
281
- // Calculate statistics
282
- const stats = {
283
- total: entries.length,
284
- byType: {},
285
- bySeverity: {},
286
- byStatus: {},
287
- recentActivity: {
288
- last24h: 0,
289
- last7d: 0,
290
- last30d: 0
291
- }
292
- };
293
- const now = Date.now();
294
- const day = 24 * 60 * 60 * 1000;
295
- entries.forEach(entry => {
296
- // Count by type
297
- stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
298
- // Count by severity
299
- stats.bySeverity[entry.severity] = (stats.bySeverity[entry.severity] || 0) + 1;
300
- // Count by status
301
- stats.byStatus[entry.status] = (stats.byStatus[entry.status] || 0) + 1;
302
- // Count recent activity
303
- const entryTime = new Date(entry.timestamp).getTime();
304
- const age = now - entryTime;
305
- if (age <= day)
306
- stats.recentActivity.last24h++;
307
- if (age <= 7 * day)
308
- stats.recentActivity.last7d++;
309
- if (age <= 30 * day)
310
- stats.recentActivity.last30d++;
311
- });
312
- return {
313
- stats,
314
- storageInfo: {
315
- lastUpdated: storage.lastUpdated,
316
- version: storage.version,
317
- maxEntries: getMaxEntries()
318
- }
319
- };
320
- });
321
- /**
322
- * feedback_health - Health check for feedback system
323
- */
324
- (0, registry_1.registerHandler)('feedback_health', () => {
325
- const dir = getFeedbackDir();
326
- const file = getFeedbackFile();
327
- const health = {
328
- status: 'ok',
329
- timestamp: new Date().toISOString(),
330
- storage: {
331
- accessible: false,
332
- writable: false,
333
- directory: dir,
334
- file
335
- },
336
- config: {
337
- maxEntries: getMaxEntries(),
338
- feedbackDir: dir
339
- }
340
- };
341
- try {
342
- // Check if storage is accessible
343
- if (fs_1.default.existsSync(file)) {
344
- fs_1.default.accessSync(file, fs_1.default.constants.R_OK);
345
- health.storage.accessible = true;
346
- }
347
- else {
348
- // File doesn't exist but directory should be writable
349
- health.storage.accessible = fs_1.default.existsSync(dir);
350
- }
351
- // Check if writable
352
- ensureFeedbackDir();
353
- fs_1.default.accessSync(dir, fs_1.default.constants.W_OK);
354
- health.storage.writable = true;
355
- }
356
- catch (error) {
357
- health.status = 'degraded';
358
- (0, logger_1.logWarn)('[feedback] Health check failed', error instanceof Error ? error : { error: String(error) });
359
- }
360
- return health;
361
- });
362
- // Unified feedback dispatch handler (002 Phase 2a)
363
- (0, registry_1.registerHandler)('feedback_dispatch', async (params) => {
364
- const { action, ...rest } = params;
365
- if (!action)
366
- throw new Error('Missing required parameter: action');
367
- const validActions = ['submit', 'list', 'get', 'update', 'stats', 'health'];
368
- if (!validActions.includes(action)) {
369
- throw new Error(`Unknown feedback action: ${action}. Valid: ${validActions.join(', ')}`);
370
- }
371
- // Flat-param alias: agents may send 'body' instead of 'description' (v1.8.2+)
372
- if (action === 'submit' && rest.body != null && rest.description == null) {
373
- rest.description = rest.body;
374
- delete rest.body;
375
- }
376
- const handler = (0, registry_1.getHandler)(`feedback_${action}`);
377
- if (!handler)
378
- throw new Error(`Handler not found for feedback_${action}`);
379
- const result = await Promise.resolve(handler(rest));
380
- // Reshape results for consistent dispatch API
381
- if (action === 'submit') {
382
- return { id: result.feedbackId, status: 'new', ...result };
383
- }
384
- if (action === 'stats') {
385
- const stats = result.stats;
386
- return { total: stats?.total, ...result };
387
- }
388
- return result;
389
- });
@@ -7,41 +7,73 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const registry_1 = require("../server/registry");
9
9
  const indexContext_1 = require("./indexContext");
10
- (0, registry_1.registerHandler)('gates_evaluate', () => { const st = (0, indexContext_1.ensureLoaded)(); const gatesPath = path_1.default.join((0, indexContext_1.getInstructionsDir)(), 'gates.json'); if (!fs_1.default.existsSync(gatesPath))
11
- return { notConfigured: true }; let data; try {
12
- data = JSON.parse(fs_1.default.readFileSync(gatesPath, 'utf8'));
13
- }
14
- catch {
15
- return { error: 'invalid gates file' };
16
- } const results = []; for (const g of data.gates || []) {
17
- const matches = st.list.filter(e => { const w = g.where || {}; let ok = true; if (w.requirement !== undefined)
18
- ok = ok && e.requirement === w.requirement; if (w.priorityGt !== undefined)
19
- ok = ok && e.priority > w.priorityGt; return ok; });
20
- const count = matches.length;
21
- const v = g.value;
22
- let passed = true;
23
- switch (g.op) {
24
- case '>=':
25
- passed = count >= v;
26
- break;
27
- case '>':
28
- passed = count > v;
29
- break;
30
- case '<=':
31
- passed = count <= v;
32
- break;
33
- case '<':
34
- passed = count < v;
35
- break;
36
- case '==':
37
- passed = count === v;
38
- break;
39
- case '!=':
40
- passed = count !== v;
41
- break;
42
- default:
43
- passed = true;
44
- break;
10
+ (0, registry_1.registerHandler)('gates_evaluate', () => {
11
+ const st = (0, indexContext_1.ensureLoaded)();
12
+ const gatesPath = path_1.default.join((0, indexContext_1.getInstructionsDir)(), 'gates.json');
13
+ if (!fs_1.default.existsSync(gatesPath))
14
+ return { notConfigured: true };
15
+ let data;
16
+ try {
17
+ data = JSON.parse(fs_1.default.readFileSync(gatesPath, 'utf8'));
45
18
  }
46
- results.push({ id: g.id, passed, count, op: g.op, value: v, severity: g.severity, description: g.description });
47
- } const summary = { errors: results.filter(r => !r.passed && r.severity === 'error').length, warnings: results.filter(r => !r.passed && r.severity === 'warn').length, total: results.length }; return { generatedAt: new Date().toISOString(), results, summary }; });
19
+ catch {
20
+ return { error: 'invalid gates file' };
21
+ }
22
+ const results = [];
23
+ for (const g of data.gates || []) {
24
+ const matches = st.list.filter(e => {
25
+ const w = g.where || {};
26
+ let ok = true;
27
+ if (w.requirement !== undefined)
28
+ ok = e.requirement === w.requirement;
29
+ if (w.priorityGt !== undefined)
30
+ ok = ok && e.priority > w.priorityGt;
31
+ return ok;
32
+ });
33
+ const count = matches.length;
34
+ const v = g.value;
35
+ let passed;
36
+ switch (g.op) {
37
+ case '>=':
38
+ passed = count >= v;
39
+ break;
40
+ case '>':
41
+ passed = count > v;
42
+ break;
43
+ case '<=':
44
+ passed = count <= v;
45
+ break;
46
+ case '<':
47
+ passed = count < v;
48
+ break;
49
+ case '==':
50
+ passed = count === v;
51
+ break;
52
+ case '!=':
53
+ passed = count !== v;
54
+ break;
55
+ // Unrecognized operator: gate evaluation is reporting-only (not access control),
56
+ // so we fail-open (passed=true) to preserve historical behavior. New operators
57
+ // must be added explicitly above; this default exists to bound `passed` to a
58
+ // boolean and make the choice auditable.
59
+ default:
60
+ passed = true;
61
+ break;
62
+ }
63
+ results.push({
64
+ id: g.id,
65
+ passed,
66
+ count,
67
+ op: g.op,
68
+ value: v,
69
+ severity: g.severity,
70
+ description: g.description,
71
+ });
72
+ }
73
+ const summary = {
74
+ errors: results.filter(r => !r.passed && r.severity === 'error').length,
75
+ warnings: results.filter(r => !r.passed && r.severity === 'warn').length,
76
+ total: results.length,
77
+ };
78
+ return { generatedAt: new Date().toISOString(), results, summary };
79
+ });