@jagilber-org/index-server 1.22.0 → 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.
- package/CHANGELOG.md +87 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +83 -20
- package/SECURITY.md +17 -5
- package/dist/config/dashboardConfig.d.ts +3 -0
- package/dist/config/dashboardConfig.js +3 -0
- package/dist/config/defaultValues.d.ts +1 -1
- package/dist/config/defaultValues.js +1 -1
- package/dist/config/featureConfig.d.ts +2 -0
- package/dist/config/featureConfig.js +6 -1
- package/dist/config/runtimeConfig.d.ts +1 -1
- package/dist/config/runtimeConfig.js +8 -9
- package/dist/dashboard/client/admin.html +173 -54
- package/dist/dashboard/client/css/admin.css +151 -0
- package/dist/dashboard/client/js/admin.auth.js +25 -11
- package/dist/dashboard/client/js/admin.config.js +1 -1
- package/dist/dashboard/client/js/admin.feedback.js +328 -0
- package/dist/dashboard/client/js/admin.graph.js +120 -18
- package/dist/dashboard/client/js/admin.instructions.js +27 -13
- package/dist/dashboard/client/js/admin.logs.js +1 -5
- package/dist/dashboard/client/js/admin.maintenance.js +53 -8
- package/dist/dashboard/client/js/admin.messaging.js +1 -4
- package/dist/dashboard/client/js/admin.overview.js +5 -1
- package/dist/dashboard/client/js/admin.sessions.js +1 -1
- package/dist/dashboard/client/js/admin.utils.js +43 -1
- package/dist/dashboard/client/js/mermaid.min.js +813 -537
- package/dist/dashboard/export/DataExporter.js +2 -1
- package/dist/dashboard/server/AdminPanel.d.ts +3 -0
- package/dist/dashboard/server/AdminPanel.js +132 -35
- package/dist/dashboard/server/ApiRoutes.js +40 -9
- package/dist/dashboard/server/DashboardServer.js +1 -1
- package/dist/dashboard/server/FileMetricsStorage.d.ts +19 -0
- package/dist/dashboard/server/FileMetricsStorage.js +52 -5
- package/dist/dashboard/server/HttpTransport.js +6 -0
- package/dist/dashboard/server/InstanceManager.js +7 -2
- package/dist/dashboard/server/KnowledgeStore.js +7 -2
- package/dist/dashboard/server/MetricsCollector.d.ts +16 -0
- package/dist/dashboard/server/MetricsCollector.js +113 -17
- package/dist/dashboard/server/legacyDashboardHtml.js +7 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +8 -3
- package/dist/dashboard/server/routes/admin.feedback.routes.d.ts +15 -0
- package/dist/dashboard/server/routes/admin.feedback.routes.js +188 -0
- package/dist/dashboard/server/routes/admin.routes.js +35 -27
- package/dist/dashboard/server/routes/alerts.routes.js +4 -3
- package/dist/dashboard/server/routes/api.feedback.routes.js +2 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +8 -7
- package/dist/dashboard/server/routes/embeddings.routes.d.ts +2 -1
- package/dist/dashboard/server/routes/embeddings.routes.js +18 -9
- package/dist/dashboard/server/routes/graph.routes.js +10 -13
- package/dist/dashboard/server/routes/index.d.ts +1 -0
- package/dist/dashboard/server/routes/index.js +74 -39
- package/dist/dashboard/server/routes/instances.routes.js +2 -1
- package/dist/dashboard/server/routes/instructions.routes.js +46 -27
- package/dist/dashboard/server/routes/knowledge.routes.js +4 -3
- package/dist/dashboard/server/routes/logs.routes.js +5 -4
- package/dist/dashboard/server/routes/messaging.routes.js +15 -14
- package/dist/dashboard/server/routes/metrics.routes.js +14 -13
- package/dist/dashboard/server/routes/scripts.routes.js +6 -3
- package/dist/dashboard/server/routes/status.routes.js +25 -6
- package/dist/dashboard/server/routes/synthetic.routes.js +3 -2
- package/dist/dashboard/server/routes/usage.routes.js +2 -1
- package/dist/dashboard/server/utils/escapeHtml.d.ts +1 -0
- package/dist/dashboard/server/utils/escapeHtml.js +11 -0
- package/dist/dashboard/server/utils/pathContainment.d.ts +1 -0
- package/dist/dashboard/server/utils/pathContainment.js +15 -0
- package/dist/dashboard/server/wsInit.js +2 -2
- package/dist/lib/mcpStdioLogging.d.ts +165 -0
- package/dist/lib/mcpStdioLogging.js +287 -0
- package/dist/schemas/index.d.ts +37 -2
- package/dist/schemas/index.js +27 -3
- package/dist/server/backgroundServicesStartup.d.ts +7 -1
- package/dist/server/backgroundServicesStartup.js +25 -8
- package/dist/server/certInit.d.ts +97 -0
- package/dist/server/certInit.js +359 -0
- package/dist/server/certInit.types.d.ts +92 -0
- package/dist/server/certInit.types.js +34 -0
- package/dist/server/handshake/fallbackFrames.d.ts +31 -0
- package/dist/server/handshake/fallbackFrames.js +38 -0
- package/dist/server/handshake/initializeDetector.d.ts +31 -0
- package/dist/server/handshake/initializeDetector.js +88 -0
- package/dist/server/handshake/protocol.d.ts +15 -0
- package/dist/server/handshake/protocol.js +37 -0
- package/dist/server/handshake/readyEmitter.d.ts +6 -0
- package/dist/server/handshake/readyEmitter.js +88 -0
- package/dist/server/handshake/safetyFallbacks.d.ts +1 -0
- package/dist/server/handshake/safetyFallbacks.js +134 -0
- package/dist/server/handshake/stdinSniffer.d.ts +1 -0
- package/dist/server/handshake/stdinSniffer.js +260 -0
- package/dist/server/handshake/tracing.d.ts +16 -0
- package/dist/server/handshake/tracing.js +95 -0
- package/dist/server/handshakeManager.d.ts +23 -23
- package/dist/server/handshakeManager.js +36 -466
- package/dist/server/index-server.d.ts +23 -0
- package/dist/server/index-server.js +194 -9
- package/dist/server/mcpReadOnlySurfaces.d.ts +44 -0
- package/dist/server/mcpReadOnlySurfaces.js +297 -0
- package/dist/server/sdkServer.js +69 -7
- package/dist/server/transport.d.ts +5 -6
- package/dist/server/transport.js +46 -64
- package/dist/server/transportFactory.d.ts +3 -9
- package/dist/server/transportFactory.js +18 -380
- package/dist/services/atomicFs.d.ts +3 -0
- package/dist/services/atomicFs.js +171 -13
- package/dist/services/auditLog.d.ts +17 -2
- package/dist/services/auditLog.js +75 -14
- package/dist/services/bootstrapGating.js +1 -1
- package/dist/services/categoryRules.d.ts +10 -0
- package/dist/services/categoryRules.js +17 -0
- package/dist/services/classificationService.js +7 -5
- package/dist/services/embeddingService.d.ts +27 -11
- package/dist/services/embeddingService.js +51 -14
- package/dist/services/feedbackStorage.d.ts +39 -0
- package/dist/services/feedbackStorage.js +88 -0
- package/dist/services/handlers/instructions.add.js +429 -317
- package/dist/services/handlers/instructions.groom.js +128 -31
- package/dist/services/handlers/instructions.import.js +56 -23
- package/dist/services/handlers/instructions.patch.js +43 -32
- package/dist/services/handlers/instructions.query.js +20 -29
- package/dist/services/handlers/instructions.shared.d.ts +54 -0
- package/dist/services/handlers/instructions.shared.js +126 -1
- package/dist/services/handlers.activation.js +83 -81
- package/dist/services/handlers.dashboardConfig.d.ts +2 -2
- package/dist/services/handlers.dashboardConfig.js +1 -2
- package/dist/services/handlers.diagnostics.js +75 -54
- package/dist/services/handlers.feedback.d.ts +4 -11
- package/dist/services/handlers.feedback.js +11 -333
- package/dist/services/handlers.gates.js +69 -37
- package/dist/services/handlers.graph.js +2 -2
- package/dist/services/handlers.help.js +2 -2
- package/dist/services/handlers.instructionSchema.js +4 -2
- package/dist/services/handlers.integrity.js +42 -22
- package/dist/services/handlers.messaging.js +1 -1
- package/dist/services/handlers.metrics.js +51 -6
- package/dist/services/handlers.prompt.js +10 -2
- package/dist/services/handlers.search.js +94 -44
- package/dist/services/handlers.trace.js +1 -1
- package/dist/services/handlers.usage.js +38 -7
- package/dist/services/indexContext.d.ts +21 -1
- package/dist/services/indexContext.js +267 -82
- package/dist/services/indexLoader.d.ts +1 -0
- package/dist/services/indexLoader.js +28 -8
- package/dist/services/instructionRecordValidation.d.ts +39 -0
- package/dist/services/instructionRecordValidation.js +388 -0
- package/dist/services/instructions.dispatcher.js +4 -4
- package/dist/services/loaderSchemaValidator.d.ts +15 -0
- package/dist/services/loaderSchemaValidator.js +69 -0
- package/dist/services/logger.js +11 -2
- package/dist/services/mcpLogBridge.d.ts +49 -0
- package/dist/services/mcpLogBridge.js +83 -0
- package/dist/services/ownershipService.js +18 -8
- package/dist/services/performanceBaseline.js +23 -22
- package/dist/services/promptReviewService.d.ts +3 -1
- package/dist/services/promptReviewService.js +41 -13
- package/dist/services/regexSafety.d.ts +6 -0
- package/dist/services/regexSafety.js +46 -0
- package/dist/services/seedBootstrap.js +4 -4
- package/dist/services/storage/factory.d.ts +14 -1
- package/dist/services/storage/factory.js +61 -1
- package/dist/services/storage/jsonEmbeddingStore.d.ts +15 -0
- package/dist/services/storage/jsonEmbeddingStore.js +83 -0
- package/dist/services/storage/jsonFileStore.d.ts +3 -1
- package/dist/services/storage/jsonFileStore.js +8 -6
- package/dist/services/storage/migrationEngine.d.ts +13 -0
- package/dist/services/storage/migrationEngine.js +31 -0
- package/dist/services/storage/sqliteEmbeddingStore.d.ts +30 -0
- package/dist/services/storage/sqliteEmbeddingStore.js +222 -0
- package/dist/services/storage/sqliteStore.d.ts +3 -1
- package/dist/services/storage/sqliteStore.js +2 -2
- package/dist/services/storage/types.d.ts +48 -1
- package/dist/services/toolRegistry.js +77 -67
- package/dist/services/toolRegistry.zod.js +89 -86
- package/dist/services/tracing.js +5 -4
- package/dist/utils/envUtils.d.ts +4 -0
- package/dist/utils/envUtils.js +7 -0
- package/dist/utils/memoryMonitor.js +11 -10
- package/package.json +11 -4
- package/schemas/instruction.schema.json +38 -1
- package/scripts/copy-dashboard-assets.mjs +1 -1
- package/scripts/dist/README.md +1 -1
- package/scripts/setup-wizard.mjs +781 -0
- package/server.json +1 -0
- package/dist/externalClientLib.d.ts +0 -1
- package/dist/externalClientLib.js +0 -2
- package/dist/portableClientWrapper.d.ts +0 -1
- package/dist/portableClientWrapper.js +0 -2
- package/dist/services/indexingService.d.ts +0 -1
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
(0, registry_1.registerHandler)('
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
*
|
|
2
|
+
* MCP feedback handler — submit-only surface.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
3
|
+
* MCP feedback handler — submit-only surface.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
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),
|
|
132
|
-
description: params.description.substring(0, 10000),
|
|
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),
|
|
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', () => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
+
});
|