@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
|
@@ -18,8 +18,10 @@ const runtimeConfig_1 = require("../../config/runtimeConfig");
|
|
|
18
18
|
const canonical_1 = require("../canonical");
|
|
19
19
|
const manifestManager_1 = require("../manifestManager");
|
|
20
20
|
const tracing_1 = require("../tracing");
|
|
21
|
+
const instructionRecordValidation_1 = require("../instructionRecordValidation");
|
|
22
|
+
const instructionRecordValidation_2 = require("../instructionRecordValidation");
|
|
21
23
|
const instructions_shared_1 = require("./instructions.shared");
|
|
22
|
-
(0, registry_1.registerHandler)('index_add', (0, instructions_shared_1.guard)('index_add', (p) => {
|
|
24
|
+
(0, registry_1.registerHandler)('index_add', (0, instructions_shared_1.guard)('index_add', async (p) => {
|
|
23
25
|
const e = p.entry;
|
|
24
26
|
const instructionsCfg = (0, runtimeConfig_1.getRuntimeConfig)().instructions;
|
|
25
27
|
const SEMVER_REGEX = /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:[-+].*)?$/;
|
|
@@ -38,24 +40,153 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
38
40
|
} : { id };
|
|
39
41
|
const base = {
|
|
40
42
|
id,
|
|
43
|
+
success: false,
|
|
41
44
|
created: false,
|
|
42
45
|
overwritten: false,
|
|
43
46
|
skipped: false,
|
|
44
47
|
error,
|
|
45
48
|
hash: opts?.hash,
|
|
46
|
-
|
|
49
|
+
message: opts?.message || 'Instruction not added.',
|
|
50
|
+
feedbackHint: 'Instruction not added. Fix validationErrors or call feedback_submit with reproEntry.',
|
|
47
51
|
reproEntry
|
|
48
52
|
};
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (opts?.validationErrors?.length)
|
|
54
|
+
base.validationErrors = opts.validationErrors;
|
|
55
|
+
if (opts?.hints?.length)
|
|
56
|
+
base.hints = opts.hints;
|
|
57
|
+
if (opts?.errorCode)
|
|
58
|
+
base.errorCode = opts.errorCode;
|
|
59
|
+
if (opts?.detail)
|
|
60
|
+
base.detail = opts.detail;
|
|
61
|
+
if (/^missing (entry|id|required fields)/.test(error) || error === 'missing required fields' || error === 'invalid_instruction') {
|
|
62
|
+
base.schemaRef = instructionRecordValidation_1.INSTRUCTION_INPUT_SCHEMA_REF;
|
|
63
|
+
if (ADD_INPUT_SCHEMA)
|
|
52
64
|
base.inputSchema = ADD_INPUT_SCHEMA;
|
|
65
|
+
}
|
|
66
|
+
return base;
|
|
67
|
+
};
|
|
68
|
+
const failValidation = (error, validationErrors, hints, opts) => fail(error, { ...opts, validationErrors, hints, message: 'Instruction not added.' });
|
|
69
|
+
const loadExistingEntry = async (id, filePath) => {
|
|
70
|
+
let priorLoad;
|
|
71
|
+
try {
|
|
72
|
+
const stExisting = await (0, indexContext_1.ensureLoadedAsync)();
|
|
73
|
+
const memEntry = stExisting.byId.get(id);
|
|
74
|
+
if (memEntry)
|
|
75
|
+
return { entry: { ...memEntry } };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
priorLoad = (0, instructionRecordValidation_1.sanitizeLoadError)(err, 'load_failed');
|
|
79
|
+
}
|
|
80
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
81
|
+
let diskRaw;
|
|
82
|
+
try {
|
|
83
|
+
diskRaw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
53
84
|
}
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
catch (err) {
|
|
86
|
+
return { error: (0, instructionRecordValidation_1.sanitizeLoadError)(err, 'load_failed') };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return { entry: JSON.parse(diskRaw) };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
return { error: (0, instructionRecordValidation_1.sanitizeLoadError)(err, 'parse_failed') };
|
|
56
93
|
}
|
|
57
94
|
}
|
|
58
|
-
|
|
95
|
+
if (priorLoad)
|
|
96
|
+
return { error: priorLoad };
|
|
97
|
+
return { error: { code: 'unknown', detail: 'missing-existing-entry', raw: 'missing-existing-entry' } };
|
|
98
|
+
};
|
|
99
|
+
const verifyReadBack = async (id, filePath, requestedCategories) => {
|
|
100
|
+
try {
|
|
101
|
+
(0, indexContext_1.invalidate)();
|
|
102
|
+
}
|
|
103
|
+
catch { /* ignore */ }
|
|
104
|
+
const stReloaded = await (0, indexContext_1.ensureLoadedAsync)();
|
|
105
|
+
let strictVerified = false;
|
|
106
|
+
const verifyIssues = [];
|
|
107
|
+
try {
|
|
108
|
+
let parsed;
|
|
109
|
+
parsed = stReloaded.byId.get(id) ?? undefined;
|
|
110
|
+
if (!parsed && fs_1.default.existsSync(filePath)) {
|
|
111
|
+
let diskRaw;
|
|
112
|
+
try {
|
|
113
|
+
diskRaw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
catch (ex) {
|
|
116
|
+
verifyIssues.push('read-failed:' + ex.message);
|
|
117
|
+
}
|
|
118
|
+
if (diskRaw) {
|
|
119
|
+
try {
|
|
120
|
+
parsed = JSON.parse(diskRaw);
|
|
121
|
+
}
|
|
122
|
+
catch (ex) {
|
|
123
|
+
verifyIssues.push('parse-failed:' + ex.message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (parsed) {
|
|
128
|
+
if (parsed.id !== id)
|
|
129
|
+
verifyIssues.push('id-mismatch');
|
|
130
|
+
if (!parsed.title)
|
|
131
|
+
verifyIssues.push('missing-title');
|
|
132
|
+
if (!parsed.body)
|
|
133
|
+
verifyIssues.push('missing-body');
|
|
134
|
+
const wantCats = Array.isArray(requestedCategories)
|
|
135
|
+
? requestedCategories.filter((c) => typeof c === 'string').map(c => c.toLowerCase())
|
|
136
|
+
: [];
|
|
137
|
+
if (wantCats.length) {
|
|
138
|
+
for (const c of wantCats) {
|
|
139
|
+
if (!parsed.categories?.includes(c))
|
|
140
|
+
verifyIssues.push('missing-category:' + c);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const mem = stReloaded.byId.get(id);
|
|
145
|
+
if (!mem)
|
|
146
|
+
verifyIssues.push('not-in-index');
|
|
147
|
+
const wantCats2 = Array.isArray(requestedCategories)
|
|
148
|
+
? requestedCategories.filter((c) => typeof c === 'string').map(c => c.toLowerCase())
|
|
149
|
+
: [];
|
|
150
|
+
if (wantCats2.length) {
|
|
151
|
+
const catHit = stReloaded.list.some(rec => rec.id === id && wantCats2.every(c => rec.categories.includes(c)));
|
|
152
|
+
if (!catHit)
|
|
153
|
+
verifyIssues.push('category-query-miss');
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
if (parsed) {
|
|
157
|
+
const classifier2 = new classificationService_1.ClassificationService();
|
|
158
|
+
const issues = classifier2.validate(parsed);
|
|
159
|
+
if (issues.length)
|
|
160
|
+
verifyIssues.push('classification-issues:' + issues.join(','));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
verifyIssues.push('classification-exception:' + err.message);
|
|
165
|
+
}
|
|
166
|
+
if (verifyIssues.includes('not-in-index')) {
|
|
167
|
+
try {
|
|
168
|
+
(0, indexContext_1.invalidate)();
|
|
169
|
+
const st2 = await (0, indexContext_1.ensureLoadedAsync)();
|
|
170
|
+
if (st2.byId.has(id)) {
|
|
171
|
+
const idx = verifyIssues.indexOf('not-in-index');
|
|
172
|
+
if (idx >= 0)
|
|
173
|
+
verifyIssues.splice(idx, 1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
if (!verifyIssues.length)
|
|
179
|
+
strictVerified = true;
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
verifyIssues.push('verify-exception:' + err.message);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
stReloaded,
|
|
186
|
+
strictVerified,
|
|
187
|
+
verifyIssues,
|
|
188
|
+
verified: strictVerified && verifyIssues.length === 0,
|
|
189
|
+
};
|
|
59
190
|
};
|
|
60
191
|
const metadataEquals = (left, right) => {
|
|
61
192
|
if (left === right)
|
|
@@ -79,87 +210,60 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
79
210
|
if (!e.id)
|
|
80
211
|
return fail('missing id');
|
|
81
212
|
const mutable = e;
|
|
82
|
-
|
|
213
|
+
// Only fill defaults for *missing* fields. Never silently coerce a wrong-typed value:
|
|
214
|
+
// shape violations are surfaced as invalid_instruction by validateInstructionInputSurface.
|
|
215
|
+
if (mutable.title === undefined)
|
|
83
216
|
mutable.title = mutable.id;
|
|
84
|
-
if (
|
|
217
|
+
if (mutable.priority === undefined)
|
|
85
218
|
mutable.priority = 50;
|
|
86
|
-
if (
|
|
219
|
+
if (mutable.audience === undefined)
|
|
87
220
|
mutable.audience = 'all';
|
|
88
|
-
if (
|
|
221
|
+
if (mutable.requirement === undefined)
|
|
89
222
|
mutable.requirement = 'optional';
|
|
90
|
-
if (
|
|
223
|
+
if (mutable.categories === undefined)
|
|
91
224
|
mutable.categories = [];
|
|
92
225
|
}
|
|
93
|
-
if (p.overwrite && (!e.body || !e.title)) {
|
|
94
|
-
try {
|
|
95
|
-
// Try in-memory state first (covers SQLite and JSON backends), fall back to disk
|
|
96
|
-
let raw;
|
|
97
|
-
const stHydrate = (0, indexContext_1.ensureLoaded)();
|
|
98
|
-
const memEntry = stHydrate.byId.get(e.id);
|
|
99
|
-
if (memEntry) {
|
|
100
|
-
raw = { ...memEntry };
|
|
101
|
-
}
|
|
102
|
-
if (!raw) {
|
|
103
|
-
const dirCandidate = (0, indexContext_1.getInstructionsDir)();
|
|
104
|
-
const fileCandidate = path_1.default.join(dirCandidate, `${e.id}.json`);
|
|
105
|
-
if (fs_1.default.existsSync(fileCandidate)) {
|
|
106
|
-
try {
|
|
107
|
-
raw = JSON.parse(fs_1.default.readFileSync(fileCandidate, 'utf8'));
|
|
108
|
-
}
|
|
109
|
-
catch { /* ignore parse */ }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (raw) {
|
|
113
|
-
const mutableExisting = e;
|
|
114
|
-
if (!mutableExisting.body && typeof raw.body === 'string' && raw.body.trim()) {
|
|
115
|
-
mutableExisting.body = raw.body;
|
|
116
|
-
}
|
|
117
|
-
if (!mutableExisting.title && typeof raw.title === 'string' && raw.title.trim()) {
|
|
118
|
-
mutableExisting.title = raw.title;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch { /* ignore hydration errors */ }
|
|
123
|
-
}
|
|
124
|
-
if (!e.id || !e.title || !e.body)
|
|
125
|
-
return fail('missing required fields');
|
|
126
226
|
const dir = (0, indexContext_1.getInstructionsDir)();
|
|
127
227
|
if (!fs_1.default.existsSync(dir))
|
|
128
228
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
129
229
|
const file = path_1.default.join(dir, `${e.id}.json`);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!visible) {
|
|
140
|
-
try {
|
|
141
|
-
(0, indexContext_1.invalidate)();
|
|
142
|
-
st0 = (0, indexContext_1.ensureLoaded)();
|
|
143
|
-
visible = st0.byId.has(e.id);
|
|
144
|
-
if (visible)
|
|
145
|
-
repaired = true;
|
|
230
|
+
if (p.overwrite && (!e.body || !e.title)) {
|
|
231
|
+
const hydrated = await loadExistingEntry(e.id, file);
|
|
232
|
+
if (hydrated.entry) {
|
|
233
|
+
const mutableExisting = e;
|
|
234
|
+
if (!mutableExisting.body && typeof hydrated.entry.body === 'string' && hydrated.entry.body.trim()) {
|
|
235
|
+
mutableExisting.body = hydrated.entry.body;
|
|
236
|
+
}
|
|
237
|
+
if (!mutableExisting.title && typeof hydrated.entry.title === 'string' && hydrated.entry.title.trim()) {
|
|
238
|
+
mutableExisting.title = hydrated.entry.title;
|
|
146
239
|
}
|
|
147
|
-
catch { /* ignore reload */ }
|
|
148
|
-
}
|
|
149
|
-
(0, auditLog_1.logAudit)('add', e.id, { skipped: true, late_visible: visible, repaired });
|
|
150
|
-
if ((0, instructions_shared_1.traceVisibility)()) {
|
|
151
|
-
(0, tracing_1.emitTrace)('[trace:add:skip]', { id: e.id, visible, repaired });
|
|
152
|
-
}
|
|
153
|
-
if ((0, instructions_shared_1.traceVisibility)()) {
|
|
154
|
-
(0, instructions_shared_1.traceInstructionVisibility)(e.id, 'add-skip-pre-return', { visible, repaired });
|
|
155
|
-
if (!visible)
|
|
156
|
-
(0, instructions_shared_1.traceEnvSnapshot)('add-skip-anomalous', { repaired });
|
|
157
240
|
}
|
|
158
|
-
if (
|
|
159
|
-
|
|
241
|
+
else if (hydrated.error) {
|
|
242
|
+
// Surface all read failures, including those combined with 'missing-existing-entry'
|
|
243
|
+
const hasRealError = hydrated.error.detail !== 'missing-existing-entry';
|
|
244
|
+
if (hasRealError) {
|
|
245
|
+
(0, auditLog_1.logAudit)('add_hydration_error', e.id, { error: hydrated.error.raw, errorCode: hydrated.error.code, overwrite: true });
|
|
246
|
+
return fail('existing_instruction_unreadable', {
|
|
247
|
+
id: e.id,
|
|
248
|
+
message: `Existing instruction could not be hydrated for overwrite (${hydrated.error.code}): ${hydrated.error.detail}`,
|
|
249
|
+
errorCode: hydrated.error.code,
|
|
250
|
+
detail: hydrated.error.detail,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
160
253
|
}
|
|
161
|
-
return { id: e.id, skipped: true, created: false, overwritten: false, hash: st0.hash, repaired: repaired ? true : undefined };
|
|
162
254
|
}
|
|
255
|
+
const requiredFieldErrors = [
|
|
256
|
+
!e.id ? 'id: missing required field' : undefined,
|
|
257
|
+
e.title === undefined ? 'title: missing required field' : undefined,
|
|
258
|
+
e.body === undefined ? 'body: missing required field' : undefined,
|
|
259
|
+
].filter((issue) => !!issue);
|
|
260
|
+
const surfaceValidation = (0, instructionRecordValidation_1.validateInstructionInputSurface)(e);
|
|
261
|
+
if (requiredFieldErrors.length || surfaceValidation.validationErrors.length) {
|
|
262
|
+
return failValidation(requiredFieldErrors.length ? 'missing required fields' : 'invalid_instruction', [...requiredFieldErrors, ...surfaceValidation.validationErrors], surfaceValidation.hints, { id: e.id });
|
|
263
|
+
}
|
|
264
|
+
const overwrite = !!p.overwrite;
|
|
265
|
+
const exists = overwrite ? ((await (0, indexContext_1.ensureLoadedAsync)()).byId.has(e.id) || fs_1.default.existsSync(file)) : false;
|
|
266
|
+
const existedBeforeOriginal = exists;
|
|
163
267
|
const now = new Date().toISOString();
|
|
164
268
|
const rawBody = typeof e.body === 'string' ? e.body : String(e.body || '');
|
|
165
269
|
const bodyTrimmed = rawBody.trim();
|
|
@@ -172,7 +276,7 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
172
276
|
guidance: `Body exceeds the ${bodyWarnLength}-character limit (${bodyTrimmed.length} chars). Please split into multiple cross-linked instructions, refine/compress content, or categorize sections as separate entries. Use categories and cross-references (e.g., "See also: <sibling-id>") to maintain discoverability.`
|
|
173
277
|
};
|
|
174
278
|
}
|
|
175
|
-
let categories =
|
|
279
|
+
let categories = (0, instructions_shared_1.normalizeInputCategories)(e.categories);
|
|
176
280
|
if (!categories.length) {
|
|
177
281
|
const allowAuto = lax || !instructionsCfg.requireCategory;
|
|
178
282
|
if (allowAuto) {
|
|
@@ -197,130 +301,154 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
197
301
|
const classifier = new classificationService_1.ClassificationService();
|
|
198
302
|
let base;
|
|
199
303
|
if (exists) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
base.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
304
|
+
const existingLoad = await loadExistingEntry(e.id, file);
|
|
305
|
+
if (!existingLoad.entry) {
|
|
306
|
+
const errCode = existingLoad.error?.code ?? 'unknown';
|
|
307
|
+
const errDetail = existingLoad.error?.detail ?? 'missing-existing-entry';
|
|
308
|
+
return fail('existing_instruction_unreadable', {
|
|
309
|
+
id: e.id,
|
|
310
|
+
message: `Existing instruction could not be read for overwrite (${errCode}): ${errDetail}`,
|
|
311
|
+
errorCode: errCode,
|
|
312
|
+
detail: errDetail,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const existing = existingLoad.entry;
|
|
316
|
+
base = { ...existing };
|
|
317
|
+
const prevBody = existing.body;
|
|
318
|
+
const prevVersion = existing.version || '1.0.0';
|
|
319
|
+
if (e.title)
|
|
320
|
+
base.title = e.title;
|
|
321
|
+
if (e.body)
|
|
322
|
+
base.body = bodyTrimmed;
|
|
323
|
+
if (e.rationale !== undefined)
|
|
324
|
+
base.rationale = e.rationale;
|
|
325
|
+
if (typeof e.priority === 'number')
|
|
326
|
+
base.priority = e.priority;
|
|
327
|
+
if (e.audience)
|
|
328
|
+
base.audience = e.audience;
|
|
329
|
+
if (e.requirement)
|
|
330
|
+
base.requirement = e.requirement;
|
|
331
|
+
if (categories.length) {
|
|
332
|
+
base.categories = categories;
|
|
333
|
+
base.primaryCategory = primaryCategory;
|
|
334
|
+
}
|
|
335
|
+
base.updatedAt = now;
|
|
336
|
+
if (e.version !== undefined)
|
|
337
|
+
base.version = e.version;
|
|
338
|
+
if (e.changeLog !== undefined)
|
|
339
|
+
base.changeLog = e.changeLog;
|
|
340
|
+
const semverRegex = SEMVER_REGEX;
|
|
341
|
+
const parse = (v) => { const m = semverRegex.exec(v); if (!m)
|
|
342
|
+
return null; return { major: +m[1], minor: +m[2], patch: +m[3] }; };
|
|
343
|
+
const gt = (a, b) => { const pa = parse(a), pb = parse(b); if (!pa || !pb)
|
|
344
|
+
return false; if (pa.major !== pb.major)
|
|
345
|
+
return pa.major > pb.major; if (pa.minor !== pb.minor)
|
|
346
|
+
return pa.minor > pb.minor; return pa.patch > pb.patch; };
|
|
347
|
+
const bodyChanged = e.body ? (bodyTrimmed !== prevBody) : false;
|
|
348
|
+
const titleChanged = e.title !== undefined && e.title !== existing.title;
|
|
349
|
+
const eRec = e;
|
|
350
|
+
const mutableMetadataKeys = ['owner', 'status', 'priorityTier', 'classification', 'lastReviewedAt', 'nextReviewDue', 'semanticSummary', 'contentType', 'extensions'];
|
|
351
|
+
const metadataChanged = mutableMetadataKeys.some((key) => eRec[key] !== undefined && !metadataEquals(eRec[key], existing[key]));
|
|
352
|
+
const versionChanged = e.version !== undefined && e.version !== existing.version;
|
|
353
|
+
const categoriesChanged = categories.length > 0 && JSON.stringify(categories.sort()) !== JSON.stringify((existing.categories || []).sort());
|
|
354
|
+
const governanceMetaChanged = titleChanged || metadataChanged || versionChanged || categoriesChanged;
|
|
355
|
+
if (overwrite && !bodyChanged && !governanceMetaChanged) {
|
|
356
|
+
// Noop overwrite: no mutation needed because the incoming entry matches
|
|
357
|
+
// the existing record. Even so, verify the persisted state still matches
|
|
358
|
+
// the in-memory index — the file (or store row) may have disappeared
|
|
359
|
+
// since the index was last loaded. Skipping verification here would mean
|
|
360
|
+
// silently reporting success when the entry is no longer durable.
|
|
361
|
+
const verification = await verifyReadBack(e.id, file, e.categories);
|
|
362
|
+
const noopVerified = verification.verified;
|
|
363
|
+
(0, auditLog_1.logAudit)('add', e.id, {
|
|
364
|
+
created: false,
|
|
365
|
+
overwritten: false,
|
|
366
|
+
skipped: true,
|
|
367
|
+
verified: noopVerified,
|
|
368
|
+
strictVerified: verification.strictVerified,
|
|
369
|
+
verifyIssues: verification.verifyIssues.length ? verification.verifyIssues : undefined,
|
|
370
|
+
noop: true,
|
|
371
|
+
note: noopVerified ? 'noop_verified' : 'noop_read_back_failed',
|
|
372
|
+
});
|
|
373
|
+
if ((0, instructions_shared_1.traceVisibility)())
|
|
374
|
+
(0, tracing_1.emitTrace)('[trace:add:noop-overwrite]', {
|
|
375
|
+
id: e.id,
|
|
376
|
+
hash: verification.stReloaded.hash,
|
|
377
|
+
verified: noopVerified,
|
|
378
|
+
strictVerified: verification.strictVerified,
|
|
379
|
+
issues: verification.verifyIssues.slice(0, 5),
|
|
380
|
+
reason: noopVerified
|
|
381
|
+
? 'no body/governance delta — persisted state verified'
|
|
382
|
+
: 'no body/governance delta — persisted state verification failed',
|
|
383
|
+
});
|
|
384
|
+
if (!noopVerified) {
|
|
385
|
+
return {
|
|
386
|
+
...fail('read-back verification failed', {
|
|
387
|
+
id: e.id,
|
|
388
|
+
hash: verification.stReloaded.hash,
|
|
389
|
+
message: 'Noop overwrite rejected: persisted instruction state does not match the in-memory index.',
|
|
390
|
+
validationErrors: verification.verifyIssues,
|
|
391
|
+
}),
|
|
392
|
+
created: false,
|
|
393
|
+
overwritten: false,
|
|
394
|
+
verified: false,
|
|
395
|
+
strictVerified: verification.strictVerified,
|
|
396
|
+
verifyIssues: verification.verifyIssues,
|
|
397
|
+
};
|
|
262
398
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
incomingVersion = autoVersion;
|
|
276
|
-
}
|
|
399
|
+
const respNoop = {
|
|
400
|
+
id: e.id,
|
|
401
|
+
success: true,
|
|
402
|
+
created: false,
|
|
403
|
+
overwritten: false,
|
|
404
|
+
skipped: true,
|
|
405
|
+
hash: verification.stReloaded.hash,
|
|
406
|
+
verified: true,
|
|
407
|
+
note: 'noop_verified',
|
|
408
|
+
};
|
|
409
|
+
if (instructionsCfg.strictCreate) {
|
|
410
|
+
respNoop.strictVerified = verification.strictVerified;
|
|
277
411
|
}
|
|
278
|
-
|
|
412
|
+
return respNoop;
|
|
413
|
+
}
|
|
414
|
+
let incomingVersion = e.version;
|
|
415
|
+
if (incomingVersion && !semverRegex.test(incomingVersion))
|
|
416
|
+
return fail('invalid_semver', { id: e.id });
|
|
417
|
+
if (bodyChanged) {
|
|
418
|
+
if (incomingVersion) {
|
|
279
419
|
if (!gt(incomingVersion, prevVersion))
|
|
280
420
|
return fail('version_not_bumped', { id: e.id });
|
|
281
421
|
}
|
|
282
422
|
else {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
base.changeLog = [{ version: prevVersion, changedAt: existing.createdAt || now, summary: 'initial import' }];
|
|
288
|
-
}
|
|
289
|
-
const finalVersion = base.version || incomingVersion || prevVersion;
|
|
290
|
-
const last = base.changeLog[base.changeLog.length - 1];
|
|
291
|
-
if (last.version !== finalVersion) {
|
|
292
|
-
const summary = bodyChanged ? (e.version ? 'body update' : 'auto bump (body change)') : 'metadata update';
|
|
293
|
-
base.changeLog.push({ version: finalVersion, changedAt: now, summary });
|
|
423
|
+
const pv = parse(prevVersion) || { major: 1, minor: 0, patch: 0 };
|
|
424
|
+
const autoVersion = `${pv.major}.${pv.minor}.${pv.patch + 1}`;
|
|
425
|
+
base.version = autoVersion;
|
|
426
|
+
incomingVersion = autoVersion;
|
|
294
427
|
}
|
|
295
|
-
const repairChangeLog = (cl) => {
|
|
296
|
-
const out = [];
|
|
297
|
-
if (Array.isArray(cl)) {
|
|
298
|
-
for (const entry of cl) {
|
|
299
|
-
if (!entry || typeof entry !== 'object')
|
|
300
|
-
continue;
|
|
301
|
-
const { version: v, changedAt: ca, summary: sum } = entry;
|
|
302
|
-
if (typeof v === 'string' && v.trim() && typeof sum === 'string' && sum.trim()) {
|
|
303
|
-
const caIso = typeof ca === 'string' && /T/.test(ca) ? ca : now;
|
|
304
|
-
out.push({ version: v.trim(), changedAt: caIso, summary: sum.trim() });
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (!out.length) {
|
|
309
|
-
out.push({ version: prevVersion, changedAt: existing.createdAt || now, summary: 'initial import (repaired)' });
|
|
310
|
-
}
|
|
311
|
-
const lastVer = out[out.length - 1].version;
|
|
312
|
-
if (lastVer !== finalVersion) {
|
|
313
|
-
out.push({ version: finalVersion, changedAt: now, summary: bodyChanged ? 'body update (repaired)' : 'metadata update (repaired)' });
|
|
314
|
-
}
|
|
315
|
-
return out;
|
|
316
|
-
};
|
|
317
|
-
base.changeLog = repairChangeLog(base.changeLog);
|
|
318
428
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
429
|
+
else if (incomingVersion) {
|
|
430
|
+
if (!gt(incomingVersion, prevVersion))
|
|
431
|
+
return fail('version_not_bumped', { id: e.id });
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
base.version = prevVersion;
|
|
435
|
+
incomingVersion = prevVersion;
|
|
436
|
+
}
|
|
437
|
+
if (!Array.isArray(base.changeLog) || !base.changeLog.length) {
|
|
438
|
+
base.changeLog = [{ version: prevVersion, changedAt: existing.createdAt || now, summary: 'initial import' }];
|
|
323
439
|
}
|
|
440
|
+
const finalVersion = base.version || incomingVersion || prevVersion;
|
|
441
|
+
const last = base.changeLog[base.changeLog.length - 1];
|
|
442
|
+
if (last.version !== finalVersion) {
|
|
443
|
+
const summary = bodyChanged ? (e.version ? 'body update' : 'auto bump (body change)') : 'metadata update';
|
|
444
|
+
base.changeLog.push({ version: finalVersion, changedAt: now, summary });
|
|
445
|
+
}
|
|
446
|
+
base.changeLog = (0, instructions_shared_1.repairChangeLog)(base.changeLog, {
|
|
447
|
+
finalVersion,
|
|
448
|
+
now,
|
|
449
|
+
fallback: { version: prevVersion, changedAt: existing.createdAt || now, summary: 'initial import (repaired)' },
|
|
450
|
+
trailingSummary: bodyChanged ? 'body update (repaired)' : 'metadata update (repaired)'
|
|
451
|
+
});
|
|
324
452
|
}
|
|
325
453
|
else {
|
|
326
454
|
base = { id: e.id, title: e.title, body: bodyTrimmed, rationale: e.rationale, priority: e.priority, audience: e.audience, requirement: e.requirement, categories, primaryCategory, sourceHash, schemaVersion: schemaVersion_1.SCHEMA_VERSION, deprecatedBy: e.deprecatedBy, createdAt: now, updatedAt: now, riskScore: e.riskScore, createdByAgent: instructionsCfg.agentId, sourceWorkspace: instructionsCfg.workspaceId, extensions: e.extensions };
|
|
@@ -336,32 +464,15 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
336
464
|
base.changeLog = [{ version: base.version, changedAt: now, summary: 'initial import' }];
|
|
337
465
|
}
|
|
338
466
|
if (Array.isArray(base.changeLog)) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const caIso = typeof ca === 'string' && /T/.test(ca) ? ca : now;
|
|
346
|
-
repaired.push({ version: v.trim(), changedAt: caIso, summary: sum.trim() });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
if (!repaired.length) {
|
|
350
|
-
repaired.push({ version: base.version, changedAt: now, summary: 'initial import (repaired)' });
|
|
351
|
-
}
|
|
352
|
-
if (repaired[repaired.length - 1].version !== base.version) {
|
|
353
|
-
repaired.push({ version: base.version, changedAt: now, summary: 'initial import (normalized)' });
|
|
354
|
-
}
|
|
355
|
-
base.changeLog = repaired;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
const govKeys = ['version', 'owner', 'status', 'priorityTier', 'classification', 'lastReviewedAt', 'nextReviewDue', 'semanticSummary', 'contentType', 'extensions'];
|
|
359
|
-
for (const k of govKeys) {
|
|
360
|
-
const v = e[k];
|
|
361
|
-
if (v !== undefined) {
|
|
362
|
-
base[k] = v;
|
|
467
|
+
base.changeLog = (0, instructions_shared_1.repairChangeLog)(base.changeLog, {
|
|
468
|
+
finalVersion: base.version,
|
|
469
|
+
now,
|
|
470
|
+
fallback: { version: base.version, changedAt: now, summary: 'initial import (repaired)' },
|
|
471
|
+
trailingSummary: 'initial import (normalized)'
|
|
472
|
+
});
|
|
363
473
|
}
|
|
364
474
|
}
|
|
475
|
+
(0, instructions_shared_1.applyGovernanceKeys)(base, e, instructions_shared_1.ADD_GOVERNANCE_KEYS);
|
|
365
476
|
if (!base.sourceWorkspace)
|
|
366
477
|
base.sourceWorkspace = instructionsCfg.workspaceId;
|
|
367
478
|
if (!exists || base.body === bodyTrimmed) {
|
|
@@ -380,119 +491,75 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
380
491
|
record.updatedAt = new Date().toISOString();
|
|
381
492
|
}
|
|
382
493
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
catch (err) {
|
|
387
|
-
return fail(err.message || 'write-failed', { id: e.id });
|
|
494
|
+
const recordValidation = (0, instructionRecordValidation_1.validateInstructionRecord)(record);
|
|
495
|
+
if (recordValidation.validationErrors.length) {
|
|
496
|
+
return failValidation('invalid_instruction', recordValidation.validationErrors, recordValidation.hints, { id: e.id });
|
|
388
497
|
}
|
|
389
498
|
try {
|
|
390
|
-
(0, indexContext_1.
|
|
391
|
-
}
|
|
392
|
-
catch { /* ignore */ }
|
|
393
|
-
let stReloaded;
|
|
394
|
-
const strictMode = instructionsCfg.strictVisibility;
|
|
395
|
-
if (strictMode) {
|
|
396
|
-
try {
|
|
397
|
-
const current = (0, indexContext_1.ensureLoaded)();
|
|
398
|
-
stReloaded = current;
|
|
399
|
-
if (!current.byId.has(record.id)) {
|
|
400
|
-
current.byId.set(record.id, record);
|
|
401
|
-
current.list.push(record);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
catch { /* fallback to reload path below if anything fails */ }
|
|
499
|
+
await (0, indexContext_1.writeEntryAsync)(record, overwrite ? undefined : { createOnly: true });
|
|
405
500
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
(
|
|
501
|
+
catch (err) {
|
|
502
|
+
if ((0, instructionRecordValidation_2.isInstructionValidationError)(err)) {
|
|
503
|
+
return failValidation('invalid_instruction', err.validationErrors, err.hints, { id: e.id });
|
|
409
504
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
let strictVerified = false;
|
|
416
|
-
const verifyIssues = [];
|
|
417
|
-
try {
|
|
418
|
-
let parsed;
|
|
419
|
-
// Verify from in-memory store first (covers SQLite), fall back to disk (JSON backend)
|
|
420
|
-
parsed = stReloaded.byId.get(e.id) ?? undefined;
|
|
421
|
-
if (!parsed) {
|
|
422
|
-
if (fs_1.default.existsSync(file)) {
|
|
423
|
-
let diskRaw;
|
|
505
|
+
if (!overwrite && (0, indexContext_1.isDuplicateInstructionWriteError)(err)) {
|
|
506
|
+
let st0 = await (0, indexContext_1.ensureLoadedAsync)();
|
|
507
|
+
let visible = st0.byId.has(e.id);
|
|
508
|
+
let repaired = false;
|
|
509
|
+
if (!visible) {
|
|
424
510
|
try {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (diskRaw) {
|
|
431
|
-
try {
|
|
432
|
-
parsed = JSON.parse(diskRaw);
|
|
433
|
-
}
|
|
434
|
-
catch (ex) {
|
|
435
|
-
verifyIssues.push('parse-failed:' + ex.message);
|
|
436
|
-
}
|
|
511
|
+
(0, indexContext_1.invalidate)();
|
|
512
|
+
st0 = await (0, indexContext_1.ensureLoadedAsync)();
|
|
513
|
+
visible = st0.byId.has(e.id);
|
|
514
|
+
if (visible)
|
|
515
|
+
repaired = true;
|
|
437
516
|
}
|
|
517
|
+
catch { /* ignore reload */ }
|
|
438
518
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
verifyIssues.push('id-mismatch');
|
|
443
|
-
if (!parsed.title)
|
|
444
|
-
verifyIssues.push('missing-title');
|
|
445
|
-
if (!parsed.body)
|
|
446
|
-
verifyIssues.push('missing-body');
|
|
447
|
-
const wantCats = Array.isArray(e.categories) ? e.categories.filter((c) => typeof c === 'string').map(c => c.toLowerCase()) : [];
|
|
448
|
-
if (wantCats.length) {
|
|
449
|
-
for (const c of wantCats) {
|
|
450
|
-
if (!parsed.categories?.includes(c)) {
|
|
451
|
-
verifyIssues.push('missing-category:' + c);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
519
|
+
(0, auditLog_1.logAudit)('add', e.id, { skipped: true, late_visible: visible, repaired, duplicateAtWrite: true });
|
|
520
|
+
if ((0, instructions_shared_1.traceVisibility)()) {
|
|
521
|
+
(0, tracing_1.emitTrace)('[trace:add:skip]', { id: e.id, visible, repaired, duplicateAtWrite: true });
|
|
454
522
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
const wantCats2 = Array.isArray(e.categories) ? e.categories.filter((c) => typeof c === 'string').map(c => c.toLowerCase()) : [];
|
|
461
|
-
if (wantCats2.length) {
|
|
462
|
-
const catHit = stReloaded.list.some(rec => rec.id === e.id && wantCats2.every(c => rec.categories.includes(c)));
|
|
463
|
-
if (!catHit)
|
|
464
|
-
verifyIssues.push('category-query-miss');
|
|
465
|
-
}
|
|
466
|
-
try {
|
|
467
|
-
if (parsed) {
|
|
468
|
-
const classifier2 = new classificationService_1.ClassificationService();
|
|
469
|
-
const issues = classifier2.validate(parsed);
|
|
470
|
-
if (issues.length) {
|
|
471
|
-
verifyIssues.push('classification-issues:' + issues.join(','));
|
|
472
|
-
}
|
|
523
|
+
if ((0, instructions_shared_1.traceVisibility)()) {
|
|
524
|
+
(0, instructions_shared_1.traceInstructionVisibility)(e.id, 'add-skip-post-write-conflict', { visible, repaired });
|
|
525
|
+
if (!visible)
|
|
526
|
+
(0, instructions_shared_1.traceEnvSnapshot)('add-skip-anomalous', { repaired });
|
|
473
527
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
528
|
+
if (!visible) {
|
|
529
|
+
const existingLoadError = st0.loadErrors?.find((issue) => {
|
|
530
|
+
const fileName = path_1.default.basename(issue.file);
|
|
531
|
+
return issue.file === `${e.id}.json` || fileName === `${e.id}.json` || issue.file.endsWith(`\\${e.id}.json`);
|
|
532
|
+
});
|
|
533
|
+
if (existingLoadError) {
|
|
534
|
+
return {
|
|
535
|
+
id: e.id,
|
|
536
|
+
success: false,
|
|
537
|
+
skipped: false,
|
|
538
|
+
created: false,
|
|
539
|
+
overwritten: false,
|
|
540
|
+
hash: st0.hash,
|
|
541
|
+
error: 'existing_instruction_invalid',
|
|
542
|
+
validationErrors: [(0, instructionRecordValidation_1.sanitizeErrorDetail)(existingLoadError.error) || 'existing entry could not be parsed'],
|
|
543
|
+
};
|
|
486
544
|
}
|
|
545
|
+
return { id: e.id, success: true, skipped: true, created: false, overwritten: false, hash: st0.hash, visibilityWarning: 'skipped_file_not_in_index' };
|
|
487
546
|
}
|
|
488
|
-
|
|
547
|
+
return { id: e.id, success: true, skipped: true, created: false, overwritten: false, hash: st0.hash, repaired: repaired ? true : undefined };
|
|
489
548
|
}
|
|
490
|
-
|
|
491
|
-
|
|
549
|
+
// Catch-all: never expose raw Node error text (ENOENT, null-byte path errors, stack frames) to MCP clients.
|
|
550
|
+
return fail('write_failed', {
|
|
551
|
+
id: e.id,
|
|
552
|
+
message: 'Instruction write failed due to an internal error. The error details are not exposed to clients.',
|
|
553
|
+
});
|
|
492
554
|
}
|
|
493
|
-
|
|
494
|
-
|
|
555
|
+
try {
|
|
556
|
+
(0, indexContext_1.touchIndexVersion)();
|
|
495
557
|
}
|
|
558
|
+
catch { /* ignore */ }
|
|
559
|
+
const strictMode = instructionsCfg.strictVisibility || instructionsCfg.strictCreate;
|
|
560
|
+
const createdNow = !existedBeforeOriginal;
|
|
561
|
+
const overwrittenNow = overwrite && existedBeforeOriginal;
|
|
562
|
+
const verification = await verifyReadBack(e.id, file, e.categories);
|
|
496
563
|
try {
|
|
497
564
|
if (instructionsCfg.manifest.writeEnabled)
|
|
498
565
|
(0, manifestManager_1.writeManifestFromIndex)();
|
|
@@ -503,8 +570,53 @@ const instructions_shared_1 = require("./instructions.shared");
|
|
|
503
570
|
catch { /* ignore */ } });
|
|
504
571
|
}
|
|
505
572
|
catch { /* ignore manifest */ }
|
|
506
|
-
(0, auditLog_1.logAudit)('add', e.id, {
|
|
573
|
+
(0, auditLog_1.logAudit)('add', e.id, {
|
|
574
|
+
created: createdNow,
|
|
575
|
+
overwritten: overwrittenNow,
|
|
576
|
+
verified: verification.verified,
|
|
577
|
+
strictVerified: verification.strictVerified,
|
|
578
|
+
verifyIssues: verification.verifyIssues.length ? verification.verifyIssues : undefined,
|
|
579
|
+
forcedReload: true,
|
|
580
|
+
});
|
|
507
581
|
if ((0, instructions_shared_1.traceVisibility)())
|
|
508
|
-
(0, tracing_1.emitTrace)('[trace:add:forced-reload]', {
|
|
509
|
-
|
|
582
|
+
(0, tracing_1.emitTrace)('[trace:add:forced-reload]', {
|
|
583
|
+
id: e.id,
|
|
584
|
+
created: createdNow,
|
|
585
|
+
overwritten: overwrittenNow,
|
|
586
|
+
hash: verification.stReloaded.hash,
|
|
587
|
+
verified: verification.verified,
|
|
588
|
+
strictVerified: verification.strictVerified,
|
|
589
|
+
issues: verification.verifyIssues.slice(0, 5),
|
|
590
|
+
strictMode,
|
|
591
|
+
});
|
|
592
|
+
if (!verification.verified) {
|
|
593
|
+
return {
|
|
594
|
+
...fail('read-back verification failed', {
|
|
595
|
+
id: e.id,
|
|
596
|
+
hash: verification.stReloaded.hash,
|
|
597
|
+
message: 'Instruction write completed but read-back verification failed.',
|
|
598
|
+
validationErrors: verification.verifyIssues,
|
|
599
|
+
}),
|
|
600
|
+
created: createdNow,
|
|
601
|
+
overwritten: overwrittenNow,
|
|
602
|
+
verified: false,
|
|
603
|
+
strictVerified: verification.strictVerified,
|
|
604
|
+
verifyIssues: verification.verifyIssues,
|
|
605
|
+
strictMode,
|
|
606
|
+
bodyLength: bodyTrimmed.length,
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
id: e.id,
|
|
611
|
+
success: true,
|
|
612
|
+
created: createdNow,
|
|
613
|
+
overwritten: overwrittenNow,
|
|
614
|
+
skipped: false,
|
|
615
|
+
hash: verification.stReloaded.hash,
|
|
616
|
+
verified: verification.verified,
|
|
617
|
+
strictVerified: verification.strictVerified,
|
|
618
|
+
verifyIssues: undefined,
|
|
619
|
+
strictMode,
|
|
620
|
+
bodyLength: bodyTrimmed.length,
|
|
621
|
+
};
|
|
510
622
|
}));
|