@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.
- package/CHANGELOG.md +87 -2
- package/CODE_OF_CONDUCT.md +2 -0
- package/CONTRIBUTING.md +32 -2
- package/README.md +82 -19
- 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 +170 -53
- package/dist/dashboard/client/css/admin.css +132 -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 +5 -4
- 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 +263 -78
- 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 +1 -1
- 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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { InstructionEntry } from '../models/instruction';
|
|
2
|
+
declare const INPUT_SCHEMA_REF = "index_add#input";
|
|
3
|
+
export interface InstructionValidationResult {
|
|
4
|
+
record: InstructionEntry;
|
|
5
|
+
validationErrors: string[];
|
|
6
|
+
hints: string[];
|
|
7
|
+
schemaRef: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class InstructionValidationError extends Error {
|
|
10
|
+
readonly validationErrors: string[];
|
|
11
|
+
readonly hints: string[];
|
|
12
|
+
readonly schemaRef: string;
|
|
13
|
+
readonly code = "invalid_instruction";
|
|
14
|
+
constructor(validationErrors: string[], hints?: string[], schemaRef?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare const INSTRUCTION_ID_MAX_LENGTH = 120;
|
|
17
|
+
export declare function validateInstructionInputSurface(entry: Record<string, unknown>): InstructionValidationResult;
|
|
18
|
+
export declare function validateInstructionRecord(entry: InstructionEntry): InstructionValidationResult;
|
|
19
|
+
export declare function assertValidInstructionRecord(entry: InstructionEntry): InstructionEntry;
|
|
20
|
+
export declare function isInstructionValidationError(error: unknown): error is InstructionValidationError;
|
|
21
|
+
export type LoadErrorCode = 'load_failed' | 'parse_failed' | 'unknown';
|
|
22
|
+
export interface SanitizedLoadError {
|
|
23
|
+
code: LoadErrorCode;
|
|
24
|
+
detail: string;
|
|
25
|
+
raw: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Strip absolute paths, Node fs error codes, quoted path arguments, and
|
|
29
|
+
* stack traces from a free-form error message. Used to keep client-facing
|
|
30
|
+
* error responses free of filesystem layout or internal details.
|
|
31
|
+
*/
|
|
32
|
+
export declare function sanitizeErrorDetail(message: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Convert an arbitrary error from the existing-entry load path into a
|
|
35
|
+
* client-safe shape. The `raw` field is preserved for internal audit logging
|
|
36
|
+
* only; never echo it directly to clients.
|
|
37
|
+
*/
|
|
38
|
+
export declare function sanitizeLoadError(err: unknown, kind?: LoadErrorCode): SanitizedLoadError;
|
|
39
|
+
export { INPUT_SCHEMA_REF as INSTRUCTION_INPUT_SCHEMA_REF };
|
|
@@ -0,0 +1,388 @@
|
|
|
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.INSTRUCTION_INPUT_SCHEMA_REF = exports.INSTRUCTION_ID_MAX_LENGTH = exports.InstructionValidationError = void 0;
|
|
7
|
+
exports.validateInstructionInputSurface = validateInstructionInputSurface;
|
|
8
|
+
exports.validateInstructionRecord = validateInstructionRecord;
|
|
9
|
+
exports.assertValidInstructionRecord = assertValidInstructionRecord;
|
|
10
|
+
exports.isInstructionValidationError = isInstructionValidationError;
|
|
11
|
+
exports.sanitizeErrorDetail = sanitizeErrorDetail;
|
|
12
|
+
exports.sanitizeLoadError = sanitizeLoadError;
|
|
13
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
14
|
+
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
15
|
+
const json_schema_draft_07_json_1 = __importDefault(require("ajv/dist/refs/json-schema-draft-07.json"));
|
|
16
|
+
const schemas_1 = require("../schemas");
|
|
17
|
+
const classificationService_1 = require("./classificationService");
|
|
18
|
+
const INPUT_SCHEMA_REF = 'index_add#input';
|
|
19
|
+
exports.INSTRUCTION_INPUT_SCHEMA_REF = INPUT_SCHEMA_REF;
|
|
20
|
+
const REQUIRED_RECORD_KEYS = new Set([
|
|
21
|
+
'id',
|
|
22
|
+
'title',
|
|
23
|
+
'body',
|
|
24
|
+
'priority',
|
|
25
|
+
'audience',
|
|
26
|
+
'requirement',
|
|
27
|
+
'categories',
|
|
28
|
+
'sourceHash',
|
|
29
|
+
'schemaVersion',
|
|
30
|
+
'createdAt',
|
|
31
|
+
'updatedAt',
|
|
32
|
+
'version',
|
|
33
|
+
'status',
|
|
34
|
+
'owner',
|
|
35
|
+
'priorityTier',
|
|
36
|
+
'classification',
|
|
37
|
+
'lastReviewedAt',
|
|
38
|
+
'nextReviewDue',
|
|
39
|
+
'changeLog',
|
|
40
|
+
'semanticSummary',
|
|
41
|
+
]);
|
|
42
|
+
const ALLOWED_INPUT_KEYS = new Set([
|
|
43
|
+
'id',
|
|
44
|
+
'title',
|
|
45
|
+
'body',
|
|
46
|
+
'rationale',
|
|
47
|
+
'priority',
|
|
48
|
+
'audience',
|
|
49
|
+
'requirement',
|
|
50
|
+
'categories',
|
|
51
|
+
'primaryCategory',
|
|
52
|
+
'deprecatedBy',
|
|
53
|
+
'riskScore',
|
|
54
|
+
'reviewIntervalDays',
|
|
55
|
+
'version',
|
|
56
|
+
'owner',
|
|
57
|
+
'status',
|
|
58
|
+
'priorityTier',
|
|
59
|
+
'classification',
|
|
60
|
+
'lastReviewedAt',
|
|
61
|
+
'nextReviewDue',
|
|
62
|
+
'changeLog',
|
|
63
|
+
'semanticSummary',
|
|
64
|
+
'contentType',
|
|
65
|
+
'extensions',
|
|
66
|
+
'supersedes',
|
|
67
|
+
'createdByAgent',
|
|
68
|
+
'sourceWorkspace',
|
|
69
|
+
'mode',
|
|
70
|
+
'lax',
|
|
71
|
+
]);
|
|
72
|
+
const ajv = new ajv_1.default({ allErrors: true, strict: false });
|
|
73
|
+
(0, ajv_formats_1.default)(ajv);
|
|
74
|
+
try {
|
|
75
|
+
if (!ajv.getSchema('https://json-schema.org/draft-07/schema')) {
|
|
76
|
+
ajv.addMetaSchema(json_schema_draft_07_json_1.default, 'https://json-schema.org/draft-07/schema');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Non-fatal; loader uses the same best-effort registration pattern.
|
|
81
|
+
}
|
|
82
|
+
const validateInstructionSchema = ajv.compile(JSON.parse(JSON.stringify(schemas_1.instructionEntry)));
|
|
83
|
+
class InstructionValidationError extends Error {
|
|
84
|
+
validationErrors;
|
|
85
|
+
hints;
|
|
86
|
+
schemaRef;
|
|
87
|
+
code = 'invalid_instruction';
|
|
88
|
+
constructor(validationErrors, hints = [], schemaRef = INPUT_SCHEMA_REF) {
|
|
89
|
+
super(`invalid_instruction: ${validationErrors.join('; ')}`);
|
|
90
|
+
this.validationErrors = validationErrors;
|
|
91
|
+
this.hints = hints;
|
|
92
|
+
this.schemaRef = schemaRef;
|
|
93
|
+
this.name = 'InstructionValidationError';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.InstructionValidationError = InstructionValidationError;
|
|
97
|
+
function normalizePath(path) {
|
|
98
|
+
return path || '/';
|
|
99
|
+
}
|
|
100
|
+
function formatAjvError(error) {
|
|
101
|
+
const instancePath = normalizePath(error.instancePath);
|
|
102
|
+
if (error.keyword === 'additionalProperties') {
|
|
103
|
+
const prop = error.params.additionalProperty;
|
|
104
|
+
return `${instancePath}: unexpected property "${prop}"`;
|
|
105
|
+
}
|
|
106
|
+
if (error.keyword === 'required') {
|
|
107
|
+
const prop = error.params.missingProperty;
|
|
108
|
+
return `${instancePath}: missing required property "${prop}"`;
|
|
109
|
+
}
|
|
110
|
+
if (error.keyword === 'enum') {
|
|
111
|
+
const allowed = Array.isArray(error.params.allowedValues)
|
|
112
|
+
? (error.params.allowedValues ?? []).join(', ')
|
|
113
|
+
: 'allowed enum values';
|
|
114
|
+
return `${instancePath}: must be one of ${allowed}`;
|
|
115
|
+
}
|
|
116
|
+
if (error.keyword === 'type') {
|
|
117
|
+
const expected = error.params.type ?? 'the expected type';
|
|
118
|
+
return `${instancePath}: must be ${expected}`;
|
|
119
|
+
}
|
|
120
|
+
if (error.keyword === 'minLength')
|
|
121
|
+
return `${instancePath}: must not be empty`;
|
|
122
|
+
if (error.keyword === 'maxLength')
|
|
123
|
+
return `${instancePath}: exceeds the allowed maximum length`;
|
|
124
|
+
if (error.keyword === 'minimum')
|
|
125
|
+
return `${instancePath}: must be greater than or equal to ${error.params.comparison ?? 'the minimum'}`;
|
|
126
|
+
if (error.keyword === 'maximum')
|
|
127
|
+
return `${instancePath}: must be less than or equal to ${error.params.comparison ?? 'the maximum'}`;
|
|
128
|
+
return `${instancePath}: ${error.message ?? 'failed validation'}`;
|
|
129
|
+
}
|
|
130
|
+
function dedupe(items) {
|
|
131
|
+
return Array.from(new Set(items));
|
|
132
|
+
}
|
|
133
|
+
function buildHints(validationErrors) {
|
|
134
|
+
const hints = [
|
|
135
|
+
'Instruction not added. Fix the listed validation errors and retry.',
|
|
136
|
+
'Use the returned inputSchema as the authoritative contract for index_add.',
|
|
137
|
+
];
|
|
138
|
+
if (validationErrors.some((msg) => msg.includes('missing required property') || msg.includes('missing required field'))) {
|
|
139
|
+
hints.push('Provide all required fields for strict add/import calls, especially id, title, and body.');
|
|
140
|
+
}
|
|
141
|
+
if (validationErrors.some((msg) => msg.includes('must be one of') || msg.includes('invalid value'))) {
|
|
142
|
+
hints.push('Use documented enum values for fields like audience, requirement, status, priorityTier, classification, and contentType.');
|
|
143
|
+
}
|
|
144
|
+
if (validationErrors.some((msg) => msg.includes('unexpected property'))) {
|
|
145
|
+
hints.push('Remove unsupported properties instead of sending fields that are not part of the instruction schema.');
|
|
146
|
+
}
|
|
147
|
+
if (validationErrors.some((msg) => msg.includes('/extensions') || msg.includes('extensions'))) {
|
|
148
|
+
hints.push('extensions must be a JSON object whose values are strings, numbers, booleans, arrays, or nested objects; null is not allowed.');
|
|
149
|
+
}
|
|
150
|
+
if (validationErrors.some((msg) => msg.includes('null is not allowed'))) {
|
|
151
|
+
hints.push('Replace null values with a valid value or omit the optional field entirely.');
|
|
152
|
+
}
|
|
153
|
+
return dedupe(hints);
|
|
154
|
+
}
|
|
155
|
+
function stripUndefinedAndOptionalNulls(value, key, depth = 0) {
|
|
156
|
+
if (Array.isArray(value)) {
|
|
157
|
+
return value.map((item) => stripUndefinedAndOptionalNulls(item, undefined, depth + 1)).filter((item) => item !== undefined);
|
|
158
|
+
}
|
|
159
|
+
if (!value || typeof value !== 'object')
|
|
160
|
+
return value;
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
163
|
+
if (childValue === undefined)
|
|
164
|
+
continue;
|
|
165
|
+
if (childValue === null && depth === 0 && !REQUIRED_RECORD_KEYS.has(childKey))
|
|
166
|
+
continue;
|
|
167
|
+
out[childKey] = stripUndefinedAndOptionalNulls(childValue, childKey, depth + 1); // lgtm[js/remote-property-injection] — childKey is own-property of caller-controlled entry; schema rejects unknown top-level keys downstream
|
|
168
|
+
}
|
|
169
|
+
if (key && Object.keys(out).length === 0 && !REQUIRED_RECORD_KEYS.has(key))
|
|
170
|
+
return undefined;
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
function applyWriteCompatibility(entry) {
|
|
174
|
+
const next = stripUndefinedAndOptionalNulls(entry);
|
|
175
|
+
if (next.status === 'active')
|
|
176
|
+
next.status = 'approved';
|
|
177
|
+
if (next.audience === undefined)
|
|
178
|
+
next.audience = 'all';
|
|
179
|
+
if (next.requirement === undefined)
|
|
180
|
+
next.requirement = 'optional';
|
|
181
|
+
if (typeof next.audience === 'string') {
|
|
182
|
+
const legacyAudienceMap = {
|
|
183
|
+
system: 'all',
|
|
184
|
+
developers: 'group',
|
|
185
|
+
developer: 'individual',
|
|
186
|
+
team: 'group',
|
|
187
|
+
teams: 'group',
|
|
188
|
+
users: 'group',
|
|
189
|
+
dev: 'individual',
|
|
190
|
+
devs: 'group',
|
|
191
|
+
testers: 'group',
|
|
192
|
+
administrators: 'group',
|
|
193
|
+
admins: 'group',
|
|
194
|
+
agents: 'group',
|
|
195
|
+
'powershell script authors': 'group',
|
|
196
|
+
};
|
|
197
|
+
const lower = next.audience.toLowerCase();
|
|
198
|
+
if (legacyAudienceMap[next.audience])
|
|
199
|
+
next.audience = legacyAudienceMap[next.audience];
|
|
200
|
+
else if (legacyAudienceMap[lower])
|
|
201
|
+
next.audience = legacyAudienceMap[lower];
|
|
202
|
+
else if (/author|script\s+author/i.test(lower))
|
|
203
|
+
next.audience = 'individual';
|
|
204
|
+
}
|
|
205
|
+
if (typeof next.requirement === 'string') {
|
|
206
|
+
const legacyRequirementMap = {
|
|
207
|
+
MUST: 'mandatory',
|
|
208
|
+
SHOULD: 'recommended',
|
|
209
|
+
MAY: 'optional',
|
|
210
|
+
CRITICAL: 'critical',
|
|
211
|
+
OPTIONAL: 'optional',
|
|
212
|
+
MANDATORY: 'mandatory',
|
|
213
|
+
DEPRECATED: 'deprecated',
|
|
214
|
+
};
|
|
215
|
+
const upper = next.requirement.toUpperCase();
|
|
216
|
+
if (legacyRequirementMap[next.requirement])
|
|
217
|
+
next.requirement = legacyRequirementMap[next.requirement];
|
|
218
|
+
else if (legacyRequirementMap[upper])
|
|
219
|
+
next.requirement = legacyRequirementMap[upper];
|
|
220
|
+
}
|
|
221
|
+
if (typeof next.priority !== 'number' || next.priority < 1 || next.priority > 100)
|
|
222
|
+
next.priority = 50;
|
|
223
|
+
return next;
|
|
224
|
+
}
|
|
225
|
+
// Matches the upper bound declared in schemas/instruction.schema.json (id maxLength).
|
|
226
|
+
exports.INSTRUCTION_ID_MAX_LENGTH = 120;
|
|
227
|
+
function findIllegalControlChar(id) {
|
|
228
|
+
for (let i = 0; i < id.length; i++) {
|
|
229
|
+
const code = id.charCodeAt(i);
|
|
230
|
+
// Reject ASCII control characters (incl. NUL 0x00) and DEL (0x7F).
|
|
231
|
+
if (code < 0x20 || code === 0x7f) {
|
|
232
|
+
const display = `\\x${code.toString(16).padStart(2, '0')}`;
|
|
233
|
+
return { display, code };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
function validateIdSurface(id) {
|
|
239
|
+
if (typeof id !== 'string' || !id.trim())
|
|
240
|
+
return ['id: missing required field'];
|
|
241
|
+
const illegal = findIllegalControlChar(id);
|
|
242
|
+
if (illegal) {
|
|
243
|
+
return [`id: contains illegal control character (${illegal.display}) — id-illegal-character`];
|
|
244
|
+
}
|
|
245
|
+
if (id.length > exports.INSTRUCTION_ID_MAX_LENGTH) {
|
|
246
|
+
return [`id: exceeds maximum length of ${exports.INSTRUCTION_ID_MAX_LENGTH} characters (id-too-long)`];
|
|
247
|
+
}
|
|
248
|
+
if (id.includes('..') || id.includes('/') || id.includes('\\') || /[:*?"<>|]/.test(id)) {
|
|
249
|
+
return ['id: must be a safe instruction id without path traversal or path separators'];
|
|
250
|
+
}
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
// Typed-field shape checks. These run for both strict and lax callers so that lax mode
|
|
254
|
+
// fills defaults for *missing* fields but never silently coerces wrong-typed inputs.
|
|
255
|
+
function validateTypedInputShape(entry) {
|
|
256
|
+
const errs = [];
|
|
257
|
+
if (entry.priority !== undefined && typeof entry.priority !== 'number') {
|
|
258
|
+
errs.push(`/priority: must be a number, received ${typeof entry.priority}`);
|
|
259
|
+
}
|
|
260
|
+
if (entry.categories !== undefined && !Array.isArray(entry.categories)) {
|
|
261
|
+
errs.push(`/categories: must be an array of strings, received ${typeof entry.categories}`);
|
|
262
|
+
}
|
|
263
|
+
if (entry.audience !== undefined && typeof entry.audience !== 'string') {
|
|
264
|
+
errs.push(`/audience: must be a string, received ${typeof entry.audience}`);
|
|
265
|
+
}
|
|
266
|
+
if (entry.requirement !== undefined && typeof entry.requirement !== 'string') {
|
|
267
|
+
errs.push(`/requirement: must be a string, received ${typeof entry.requirement}`);
|
|
268
|
+
}
|
|
269
|
+
if (entry.title !== undefined && typeof entry.title !== 'string') {
|
|
270
|
+
errs.push(`/title: must be a string, received ${typeof entry.title}`);
|
|
271
|
+
}
|
|
272
|
+
if (entry.body !== undefined && typeof entry.body !== 'string') {
|
|
273
|
+
errs.push(`/body: must be a string, received ${typeof entry.body}`);
|
|
274
|
+
}
|
|
275
|
+
if (entry.changeLog !== undefined && !Array.isArray(entry.changeLog)) {
|
|
276
|
+
errs.push(`/changeLog: must be an array, received ${typeof entry.changeLog}`);
|
|
277
|
+
}
|
|
278
|
+
if (entry.extensions !== undefined && (typeof entry.extensions !== 'object' || Array.isArray(entry.extensions))) {
|
|
279
|
+
errs.push(`/extensions: must be an object, received ${Array.isArray(entry.extensions) ? 'array' : typeof entry.extensions}`);
|
|
280
|
+
}
|
|
281
|
+
return errs;
|
|
282
|
+
}
|
|
283
|
+
function validateInstructionInputSurface(entry) {
|
|
284
|
+
const validationErrors = [];
|
|
285
|
+
validationErrors.push(...validateIdSurface(entry.id));
|
|
286
|
+
for (const key of Object.keys(entry)) {
|
|
287
|
+
if (!ALLOWED_INPUT_KEYS.has(key)) {
|
|
288
|
+
validationErrors.push(`/: unexpected property "${key}"`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (entry[key] === null)
|
|
292
|
+
validationErrors.push(`/${key}: null is not allowed`);
|
|
293
|
+
}
|
|
294
|
+
validationErrors.push(...validateTypedInputShape(entry));
|
|
295
|
+
return {
|
|
296
|
+
record: entry,
|
|
297
|
+
validationErrors: dedupe(validationErrors),
|
|
298
|
+
hints: buildHints(validationErrors),
|
|
299
|
+
schemaRef: INPUT_SCHEMA_REF,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function validateInstructionRecord(entry) {
|
|
303
|
+
const record = applyWriteCompatibility(entry);
|
|
304
|
+
const validationErrors = [];
|
|
305
|
+
if (record.status !== undefined && !['draft', 'review', 'approved', 'deprecated'].includes(record.status)) {
|
|
306
|
+
validationErrors.push(`/status: invalid value "${String(record.status)}"`);
|
|
307
|
+
}
|
|
308
|
+
if (record.priorityTier !== undefined && !['P1', 'P2', 'P3', 'P4'].includes(record.priorityTier)) {
|
|
309
|
+
validationErrors.push(`/priorityTier: invalid value "${String(record.priorityTier)}"`);
|
|
310
|
+
}
|
|
311
|
+
if (record.classification !== undefined && !['public', 'internal', 'restricted'].includes(record.classification)) {
|
|
312
|
+
validationErrors.push(`/classification: invalid value "${String(record.classification)}"`);
|
|
313
|
+
}
|
|
314
|
+
if (record.contentType !== undefined && !['instruction', 'template', 'chat-session', 'reference', 'example', 'agent'].includes(record.contentType)) {
|
|
315
|
+
validationErrors.push(`/contentType: invalid value "${String(record.contentType)}"`);
|
|
316
|
+
}
|
|
317
|
+
if (!validateInstructionSchema(record)) {
|
|
318
|
+
validationErrors.push(...(validateInstructionSchema.errors ?? []).map(formatAjvError));
|
|
319
|
+
}
|
|
320
|
+
const classifierIssues = new classificationService_1.ClassificationService().validate(record);
|
|
321
|
+
validationErrors.push(...classifierIssues.map((issue) => `/: ${issue}`));
|
|
322
|
+
if (typeof record.title === 'string' && !record.title.trim())
|
|
323
|
+
validationErrors.push('/title: must not be empty');
|
|
324
|
+
if (typeof record.body === 'string' && !record.body.trim())
|
|
325
|
+
validationErrors.push('/body: must not be empty');
|
|
326
|
+
return {
|
|
327
|
+
record,
|
|
328
|
+
validationErrors: dedupe(validationErrors),
|
|
329
|
+
hints: buildHints(validationErrors),
|
|
330
|
+
schemaRef: INPUT_SCHEMA_REF,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function assertValidInstructionRecord(entry) {
|
|
334
|
+
const validation = validateInstructionRecord(entry);
|
|
335
|
+
if (validation.validationErrors.length) {
|
|
336
|
+
throw new InstructionValidationError(validation.validationErrors, validation.hints, validation.schemaRef);
|
|
337
|
+
}
|
|
338
|
+
return validation.record;
|
|
339
|
+
}
|
|
340
|
+
function isInstructionValidationError(error) {
|
|
341
|
+
return error instanceof InstructionValidationError
|
|
342
|
+
|| (typeof error === 'object'
|
|
343
|
+
&& error !== null
|
|
344
|
+
&& 'code' in error
|
|
345
|
+
&& error.code === 'invalid_instruction'
|
|
346
|
+
&& Array.isArray(error.validationErrors));
|
|
347
|
+
}
|
|
348
|
+
const NODE_FS_ERROR_CODES = /\b(ENOENT|EACCES|EEXIST|EISDIR|ENOTDIR|EPERM|EBUSY|EMFILE|ENFILE|EROFS|ENOSPC|EAGAIN|EFAULT|EINVAL|EIO|ELOOP)\b[:,]?\s*/g;
|
|
349
|
+
/**
|
|
350
|
+
* Strip absolute paths, Node fs error codes, quoted path arguments, and
|
|
351
|
+
* stack traces from a free-form error message. Used to keep client-facing
|
|
352
|
+
* error responses free of filesystem layout or internal details.
|
|
353
|
+
*/
|
|
354
|
+
function sanitizeErrorDetail(message) {
|
|
355
|
+
if (!message)
|
|
356
|
+
return '';
|
|
357
|
+
// Truncate to first line — drops stack traces.
|
|
358
|
+
let s = String(message).split('\n')[0];
|
|
359
|
+
// Quoted path arguments (single and double quotes), e.g. open 'C:\\x.json'.
|
|
360
|
+
s = s.replace(/'[^']*[\\/][^']*'/g, "'<redacted-path>'");
|
|
361
|
+
s = s.replace(/"[^"]*[\\/][^"]*"/g, '"<redacted-path>"');
|
|
362
|
+
// Windows absolute paths.
|
|
363
|
+
s = s.replace(/[A-Za-z]:\\[^\s'"`]+/g, '<redacted-path>');
|
|
364
|
+
// Unix absolute paths with at least two segments.
|
|
365
|
+
s = s.replace(/\/(?:[^\s/'"`]+\/)+[^\s/'"`]+/g, '<redacted-path>');
|
|
366
|
+
// Node fs error codes (after path stripping so we don't break boundaries).
|
|
367
|
+
s = s.replace(NODE_FS_ERROR_CODES, '');
|
|
368
|
+
// Collapse whitespace and stray punctuation.
|
|
369
|
+
s = s.replace(/\s+/g, ' ').replace(/^[\s:,;-]+/, '').replace(/[\s:,;-]+$/, '').trim();
|
|
370
|
+
return s;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Convert an arbitrary error from the existing-entry load path into a
|
|
374
|
+
* client-safe shape. The `raw` field is preserved for internal audit logging
|
|
375
|
+
* only; never echo it directly to clients.
|
|
376
|
+
*/
|
|
377
|
+
function sanitizeLoadError(err, kind = 'load_failed') {
|
|
378
|
+
const raw = err instanceof Error ? (err.message ?? '') : (typeof err === 'string' ? err : '');
|
|
379
|
+
let detail = sanitizeErrorDetail(raw);
|
|
380
|
+
if (!detail) {
|
|
381
|
+
detail = kind === 'parse_failed'
|
|
382
|
+
? 'invalid JSON in existing entry'
|
|
383
|
+
: kind === 'load_failed'
|
|
384
|
+
? 'unable to read existing entry'
|
|
385
|
+
: 'unknown load error';
|
|
386
|
+
}
|
|
387
|
+
return { code: kind, detail, raw };
|
|
388
|
+
}
|
|
@@ -164,11 +164,11 @@ function isMutationEnabled() {
|
|
|
164
164
|
if (mutationMethods.has(target) && !isMutationEnabled()) {
|
|
165
165
|
// Dispatcher design intent: allow mutation-style actions even when direct mutation tools
|
|
166
166
|
// are disabled. The previous logic incorrectly blocked these calls, causing silent timeouts
|
|
167
|
-
// in tests expecting dispatcher add to succeed
|
|
167
|
+
// in tests expecting dispatcher add to succeed even when direct mutation calls were forced off.
|
|
168
168
|
// We now log (if verbose) and proceed instead of throwing a semantic error.
|
|
169
169
|
try {
|
|
170
170
|
if ((0, runtimeConfig_1.getRuntimeConfig)().logging.verbose)
|
|
171
|
-
process.stderr.write(`[dispatcher] mutation_allowed_via_dispatcher action=${action} target=${target} (
|
|
171
|
+
process.stderr.write(`[dispatcher] mutation_allowed_via_dispatcher action=${action} target=${target} (direct mutation override disabled)\n`);
|
|
172
172
|
}
|
|
173
173
|
catch { /* ignore */ }
|
|
174
174
|
}
|
|
@@ -192,7 +192,7 @@ function isMutationEnabled() {
|
|
|
192
192
|
// because the dispatch schema cannot express nested 'entry' wrappers.
|
|
193
193
|
// When 'entry' is absent but 'id' is present, assemble the entry from flat params.
|
|
194
194
|
if (action === 'add' && !rest.entry && typeof rest.id === 'string') {
|
|
195
|
-
const entryFields = ['id', 'body', 'title', 'rationale', 'priority', 'audience', 'requirement', 'categories', 'deprecatedBy', 'riskScore', 'version', 'owner', 'status', 'priorityTier', 'classification', 'lastReviewedAt', 'nextReviewDue', 'semanticSummary', 'changeLog', 'extensions'];
|
|
195
|
+
const entryFields = ['id', 'body', 'title', 'rationale', 'priority', 'audience', 'requirement', 'categories', 'deprecatedBy', 'riskScore', 'version', 'owner', 'status', 'priorityTier', 'classification', 'lastReviewedAt', 'nextReviewDue', 'semanticSummary', 'changeLog', 'contentType', 'extensions'];
|
|
196
196
|
const entry = {};
|
|
197
197
|
for (const k of entryFields) {
|
|
198
198
|
if (rest[k] !== undefined) {
|
|
@@ -204,7 +204,7 @@ function isMutationEnabled() {
|
|
|
204
204
|
}
|
|
205
205
|
void _ignoredAction; // explicitly ignore for lint
|
|
206
206
|
// Mark invocation origin so guard() can allow dispatcher-mediated mutations even if
|
|
207
|
-
//
|
|
207
|
+
// direct mutation tools were explicitly disabled via runtime override.
|
|
208
208
|
rest._viaDispatcher = true;
|
|
209
209
|
const hStart = timing ? Date.now() : 0;
|
|
210
210
|
// Gating: block mutation targets if bootstrap confirmation required or reference mode active.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface DiskValidationResult {
|
|
2
|
+
valid: boolean;
|
|
3
|
+
errors?: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate an instruction record against the loader JSON schema — the same
|
|
7
|
+
* schema used by IndexLoader when loading entries from disk.
|
|
8
|
+
*
|
|
9
|
+
* Call this on the exact object about to be serialized with JSON.stringify()
|
|
10
|
+
* and written to disk. If it fails, the entry WILL be silently skipped on
|
|
11
|
+
* the next reload.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateForDisk(record: unknown): DiskValidationResult;
|
|
14
|
+
/** Returns the set of property names allowed by the loader JSON schema. */
|
|
15
|
+
export declare function getSchemaPropertyNames(): Set<string>;
|
|
@@ -0,0 +1,69 @@
|
|
|
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.validateForDisk = validateForDisk;
|
|
7
|
+
exports.getSchemaPropertyNames = getSchemaPropertyNames;
|
|
8
|
+
/**
|
|
9
|
+
* Shared loader-schema validator — uses the SAME JSON schema the IndexLoader
|
|
10
|
+
* uses at reload time, compiled once and cached at module scope.
|
|
11
|
+
*
|
|
12
|
+
* Every instruction write path MUST validate through this before persisting
|
|
13
|
+
* to disk. This is the single source of truth that prevents schema drift
|
|
14
|
+
* between write-time and load-time validation from silently dropping entries.
|
|
15
|
+
*/
|
|
16
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
17
|
+
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
18
|
+
const json_schema_draft_07_json_1 = __importDefault(require("ajv/dist/refs/json-schema-draft-07.json"));
|
|
19
|
+
const instruction_schema_json_1 = __importDefault(require("../../schemas/instruction.schema.json"));
|
|
20
|
+
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
21
|
+
const ajv = new ajv_1.default({ strict: false, allErrors: true });
|
|
22
|
+
(0, ajv_formats_1.default)(ajv);
|
|
23
|
+
// Register draft-07 meta schema under https id (mirrors IndexLoader behavior)
|
|
24
|
+
try {
|
|
25
|
+
const httpsIdNoHash = 'https://json-schema.org/draft-07/schema';
|
|
26
|
+
const httpsIdHash = 'https://json-schema.org/draft-07/schema#';
|
|
27
|
+
if (!ajv.getSchema(httpsIdNoHash))
|
|
28
|
+
ajv.addMetaSchema({ ...json_schema_draft_07_json_1.default, $id: httpsIdNoHash });
|
|
29
|
+
if (!ajv.getSchema(httpsIdHash))
|
|
30
|
+
ajv.addMetaSchema({ ...json_schema_draft_07_json_1.default, $id: httpsIdHash });
|
|
31
|
+
}
|
|
32
|
+
catch { /* ignore meta-schema registration issues */ }
|
|
33
|
+
// Patch body maxLength from config (mirrors IndexLoader behavior)
|
|
34
|
+
const schemaCopy = JSON.parse(JSON.stringify(instruction_schema_json_1.default));
|
|
35
|
+
try {
|
|
36
|
+
const bodyMaxLen = (0, runtimeConfig_1.getRuntimeConfig)().index?.bodyWarnLength || 50000;
|
|
37
|
+
const props = schemaCopy.properties;
|
|
38
|
+
if (props?.body)
|
|
39
|
+
props.body.maxLength = bodyMaxLen;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
// If the bodyMaxLength patch fails the validator falls back to whatever
|
|
43
|
+
// limit ships in the on-disk schema, which can mismatch the configured
|
|
44
|
+
// INDEX_SERVER_BODY_WARN_LENGTH and silently accept/reject entries.
|
|
45
|
+
// Surface the warning so operators can see the divergence.
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.warn('[loaderSchemaValidator] failed to patch schema bodyMaxLength from runtime config:', err?.message || err);
|
|
48
|
+
}
|
|
49
|
+
const validate = ajv.compile(schemaCopy);
|
|
50
|
+
/**
|
|
51
|
+
* Validate an instruction record against the loader JSON schema — the same
|
|
52
|
+
* schema used by IndexLoader when loading entries from disk.
|
|
53
|
+
*
|
|
54
|
+
* Call this on the exact object about to be serialized with JSON.stringify()
|
|
55
|
+
* and written to disk. If it fails, the entry WILL be silently skipped on
|
|
56
|
+
* the next reload.
|
|
57
|
+
*/
|
|
58
|
+
function validateForDisk(record) {
|
|
59
|
+
const valid = validate(record);
|
|
60
|
+
if (valid)
|
|
61
|
+
return { valid: true };
|
|
62
|
+
const errors = validate.errors?.map(e => `${e.instancePath || '(root)'} ${e.message}${e.params ? ' ' + JSON.stringify(e.params) : ''}`) ?? ['unknown validation error'];
|
|
63
|
+
return { valid: false, errors };
|
|
64
|
+
}
|
|
65
|
+
/** Returns the set of property names allowed by the loader JSON schema. */
|
|
66
|
+
function getSchemaPropertyNames() {
|
|
67
|
+
const props = instruction_schema_json_1.default.properties;
|
|
68
|
+
return props ? new Set(Object.keys(props)) : new Set();
|
|
69
|
+
}
|
package/dist/services/logger.js
CHANGED
|
@@ -10,6 +10,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
12
|
const runtimeConfig_1 = require("../config/runtimeConfig");
|
|
13
|
+
const mcpLogBridge_1 = require("./mcpLogBridge");
|
|
13
14
|
/** Numeric priority for log level filtering (lower = more verbose). */
|
|
14
15
|
const LEVEL_PRIORITY = {
|
|
15
16
|
TRACE: 0,
|
|
@@ -177,8 +178,16 @@ function emit(rec) {
|
|
|
177
178
|
if (rec.correlationId)
|
|
178
179
|
out.correlationId = rec.correlationId;
|
|
179
180
|
const logLine = JSON.stringify(out);
|
|
180
|
-
//
|
|
181
|
-
|
|
181
|
+
// Route through MCP protocol notifications/message when available.
|
|
182
|
+
// This gives VS Code correct severity (info/debug/warning/error) instead
|
|
183
|
+
// of tagging every line as [warning] [server stderr].
|
|
184
|
+
if ((0, mcpLogBridge_1.isMcpLogBridgeActive)()) {
|
|
185
|
+
(0, mcpLogBridge_1.sendMcpLog)(rec.level, logLine);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Pre-handshake: fall back to stderr (intercepted and buffered by McpStdioLogger)
|
|
189
|
+
console.error(logLine);
|
|
190
|
+
}
|
|
182
191
|
// Also log to file if configured and available
|
|
183
192
|
if (logFileHandle && !logFileHandle.destroyed) {
|
|
184
193
|
try {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Log Bridge — thin adapter over the generalized McpStdioLogger.
|
|
3
|
+
*
|
|
4
|
+
* Preserves the existing module-level API (`registerMcpServer`, `activateMcpLogBridge`,
|
|
5
|
+
* `isMcpLogBridgeActive`, `sendMcpLog`, `_restoreStderr`) so that all existing
|
|
6
|
+
* call sites in index-server.ts, sdkServer.ts, handshakeManager.ts, and logger.ts
|
|
7
|
+
* continue to work without changes.
|
|
8
|
+
*
|
|
9
|
+
* The actual stderr interception, buffering, replay, and MCP protocol routing is
|
|
10
|
+
* delegated to `McpStdioLogger` from `../lib/mcpStdioLogging`, which is a
|
|
11
|
+
* self-contained, reusable module for any MCP stdio server.
|
|
12
|
+
*
|
|
13
|
+
* See `src/lib/mcpStdioLogging.ts` for the generalized implementation and
|
|
14
|
+
* `docs/mcp_stdio_logging.md` for integration guidance.
|
|
15
|
+
*/
|
|
16
|
+
import type { LogLevel } from './logger';
|
|
17
|
+
/**
|
|
18
|
+
* Register the SDK server instance. Called once after server creation.
|
|
19
|
+
* Does NOT activate the bridge — call `activateMcpLogBridge()` after ready.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerMcpServer(server: any): void;
|
|
22
|
+
/**
|
|
23
|
+
* Activate the bridge so subsequent log calls are routed via MCP protocol.
|
|
24
|
+
* Replays any buffered pre-handshake stderr lines through the protocol.
|
|
25
|
+
* Called from `emitReadyGlobal()` after the handshake completes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function activateMcpLogBridge(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if the bridge is active and logs will be sent via MCP protocol.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isMcpLogBridgeActive(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Send a log message through the MCP `notifications/message` protocol.
|
|
34
|
+
* No-op if the bridge is not yet active.
|
|
35
|
+
*
|
|
36
|
+
* @param level - The index-server log level (TRACE, DEBUG, INFO, WARN, ERROR)
|
|
37
|
+
* @param data - The log payload (typically the NDJSON string)
|
|
38
|
+
*/
|
|
39
|
+
export declare function sendMcpLog(level: LogLevel, data: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Write directly to the original process.stderr, bypassing the interceptor.
|
|
42
|
+
* Use from logger.ts to ensure VS Code Output panel always has content.
|
|
43
|
+
*/
|
|
44
|
+
export declare function writeRealStderr(data: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Restore original stderr and deactivate the bridge.
|
|
47
|
+
* Intended for testing cleanup only.
|
|
48
|
+
*/
|
|
49
|
+
export declare function _restoreStderr(): void;
|