@ruso-0/nreki 7.1.2 → 7.3.0
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 +805 -774
- package/README.md +308 -442
- package/dist/ast-sandbox.d.ts.map +1 -1
- package/dist/ast-sandbox.js +17 -1
- package/dist/ast-sandbox.js.map +1 -1
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +10 -4
- package/dist/audit.js.map +1 -1
- package/dist/chronos-memory.d.ts.map +1 -1
- package/dist/chronos-memory.js +10 -2
- package/dist/chronos-memory.js.map +1 -1
- package/dist/compressor.d.ts.map +1 -1
- package/dist/compressor.js +13 -1
- package/dist/compressor.js.map +1 -1
- package/dist/database.d.ts +12 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +81 -29
- package/dist/database.js.map +1 -1
- package/dist/embedder.d.ts.map +1 -1
- package/dist/embedder.js +7 -2
- package/dist/embedder.js.map +1 -1
- package/dist/handlers/code.d.ts.map +1 -1
- package/dist/handlers/code.js +198 -243
- package/dist/handlers/code.js.map +1 -1
- package/dist/handlers/guard.d.ts.map +1 -1
- package/dist/handlers/guard.js +10 -1
- package/dist/handlers/guard.js.map +1 -1
- package/dist/hologram/shadow-generator.d.ts.map +1 -1
- package/dist/hologram/shadow-generator.js +20 -1
- package/dist/hologram/shadow-generator.js.map +1 -1
- package/dist/kernel/backends/lsp-sidecar-base.d.ts +49 -5
- package/dist/kernel/backends/lsp-sidecar-base.d.ts.map +1 -1
- package/dist/kernel/backends/lsp-sidecar-base.js +209 -69
- package/dist/kernel/backends/lsp-sidecar-base.js.map +1 -1
- package/dist/kernel/backends/ts-compiler-wrapper.d.ts +1 -1
- package/dist/kernel/backends/ts-compiler-wrapper.d.ts.map +1 -1
- package/dist/kernel/backends/ts-compiler-wrapper.js +7 -4
- package/dist/kernel/backends/ts-compiler-wrapper.js.map +1 -1
- package/dist/kernel/backends/ts-corsa-sidecar.d.ts +26 -0
- package/dist/kernel/backends/ts-corsa-sidecar.d.ts.map +1 -0
- package/dist/kernel/backends/ts-corsa-sidecar.js +30 -0
- package/dist/kernel/backends/ts-corsa-sidecar.js.map +1 -0
- package/dist/kernel/nreki-kernel.d.ts +37 -0
- package/dist/kernel/nreki-kernel.d.ts.map +1 -1
- package/dist/kernel/nreki-kernel.js +669 -290
- package/dist/kernel/nreki-kernel.js.map +1 -1
- package/dist/kernel/spectral-topology.d.ts.map +1 -1
- package/dist/kernel/spectral-topology.js +32 -16
- package/dist/kernel/spectral-topology.js.map +1 -1
- package/dist/middleware/circuit-breaker.d.ts.map +1 -1
- package/dist/middleware/circuit-breaker.js +18 -2
- package/dist/middleware/circuit-breaker.js.map +1 -1
- package/dist/middleware/file-lock.d.ts.map +1 -1
- package/dist/middleware/file-lock.js +8 -3
- package/dist/middleware/file-lock.js.map +1 -1
- package/dist/monitor.d.ts.map +1 -1
- package/dist/monitor.js +1 -0
- package/dist/monitor.js.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +19 -2
- package/dist/parser.js.map +1 -1
- package/dist/pin-memory.d.ts +2 -2
- package/dist/pin-memory.d.ts.map +1 -1
- package/dist/pin-memory.js.map +1 -1
- package/dist/repo-map.d.ts.map +1 -1
- package/dist/repo-map.js +26 -0
- package/dist/repo-map.js.map +1 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +58 -18
- package/dist/router.js.map +1 -1
- package/dist/undo.js +1 -1
- package/dist/undo.js.map +1 -1
- package/dist/utils/imports.d.ts.map +1 -1
- package/dist/utils/imports.js +8 -4
- package/dist/utils/imports.js.map +1 -1
- package/dist/utils/latency-tracker.d.ts +22 -0
- package/dist/utils/latency-tracker.d.ts.map +1 -0
- package/dist/utils/latency-tracker.js +49 -0
- package/dist/utils/latency-tracker.js.map +1 -0
- package/dist/utils/logger.d.ts +5 -2
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +29 -6
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/path-jail.d.ts.map +1 -1
- package/dist/utils/path-jail.js +3 -0
- package/dist/utils/path-jail.js.map +1 -1
- package/package.json +96 -79
package/dist/handlers/code.js
CHANGED
|
@@ -43,6 +43,157 @@ async function ensureHologramReady(kernel, nrekiMode) {
|
|
|
43
43
|
kernel.setShadows(scanResult.prunable, scanResult.unprunable, scanResult.ambientFiles);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
+
/** Shared kernel boot logic for edit handlers. */
|
|
47
|
+
async function ensureKernelBooted(deps) {
|
|
48
|
+
if (!deps.kernel || deps.nrekiMode === "syntax")
|
|
49
|
+
return false;
|
|
50
|
+
if (!deps.kernel.isBooted()) {
|
|
51
|
+
logger.info(`Booting kernel (${deps.nrekiMode} mode). First edit will be slower.`);
|
|
52
|
+
try {
|
|
53
|
+
await ensureHologramReady(deps.kernel, deps.nrekiMode ?? "");
|
|
54
|
+
deps.kernel.boot(process.cwd(), deps.nrekiMode);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.error(`Kernel boot failed: ${err.message}. Falling back to Layer 1.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return deps.kernel.isBooted();
|
|
61
|
+
}
|
|
62
|
+
// ─── H-07: Shared kernel verification + TTRD + Chronos logic ────────
|
|
63
|
+
//
|
|
64
|
+
// Extracted from handleEdit and handleBatchEdit to eliminate ~150 lines
|
|
65
|
+
// of duplicated post-intercept logic.
|
|
66
|
+
/**
|
|
67
|
+
* Shared kernel verification pipeline (Layer 2).
|
|
68
|
+
*
|
|
69
|
+
* Given a kernel intercept result, performs:
|
|
70
|
+
* 1. node_modules error filtering
|
|
71
|
+
* 2. Chronos error recording
|
|
72
|
+
* 3. Backup + commit/rollback
|
|
73
|
+
* 4. TTRD regression tracking + debt payment
|
|
74
|
+
* 5. Chronos friction sync
|
|
75
|
+
*
|
|
76
|
+
* @param kernelResult - result from interceptAtomicBatch
|
|
77
|
+
* @param committedFiles - paths that were committed (for backup + Chronos)
|
|
78
|
+
* @param deps - router dependencies (kernel, chronos)
|
|
79
|
+
* @param rejectionHeader - header text for the rejection response
|
|
80
|
+
*/
|
|
81
|
+
async function processKernelResult(kernelResult, committedFiles, deps, rejectionHeader) {
|
|
82
|
+
const kernel = deps.kernel;
|
|
83
|
+
let ttrdFeedback = "";
|
|
84
|
+
if (!kernelResult.safe) {
|
|
85
|
+
const agentErrors = kernelResult.structured?.filter(e => !e.file.match(/[/\\]node_modules[/\\]/)) || [];
|
|
86
|
+
if (agentErrors.length === 0) {
|
|
87
|
+
// All errors are in node_modules — safe to commit
|
|
88
|
+
logger.warn(`${kernelResult.structured?.length} error(s) in node_modules ignored.`);
|
|
89
|
+
for (const fp of committedFiles) {
|
|
90
|
+
try {
|
|
91
|
+
saveBackup(process.cwd(), fp);
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
95
|
+
await kernel.commitToDisk();
|
|
96
|
+
return { committed: true, ttrdFeedback };
|
|
97
|
+
}
|
|
98
|
+
// Real errors in agent code — record + rollback
|
|
99
|
+
if (deps.chronos) {
|
|
100
|
+
const errorByFile = new Map();
|
|
101
|
+
for (const e of agentErrors) {
|
|
102
|
+
if (!errorByFile.has(e.file))
|
|
103
|
+
errorByFile.set(e.file, e.message);
|
|
104
|
+
}
|
|
105
|
+
for (const [fragileFile, firstMsg] of errorByFile.entries()) {
|
|
106
|
+
deps.chronos.recordSemanticError(fragileFile, firstMsg);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await kernel.rollbackAll();
|
|
110
|
+
const structuredInfo = "\n\nSemantic errors:\n" +
|
|
111
|
+
agentErrors.map(e => ` \u2192 ${path.relative(process.cwd(), e.file)} (${e.line},${e.column}): ${e.code} - ${e.message}`).join("\n");
|
|
112
|
+
return {
|
|
113
|
+
committed: false,
|
|
114
|
+
ttrdFeedback,
|
|
115
|
+
response: {
|
|
116
|
+
content: [{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: `${rejectionHeader}` +
|
|
119
|
+
`Layer 1 (syntax) passed, but Layer 2 (cross-file semantics) detected errors.\n` +
|
|
120
|
+
`\ud83d\udee1\ufe0f **DISK UNTOUCHED: Caught in RAM. No files modified.**${structuredInfo}\n\n` +
|
|
121
|
+
`Fix the type errors and retry. If you changed a function signature, ` +
|
|
122
|
+
`use \`nreki_code action:"batch_edit"\` to update all callers in one atomic transaction.\n\n` +
|
|
123
|
+
`[NREKI: validated in ${kernelResult.latencyMs}ms]`,
|
|
124
|
+
}],
|
|
125
|
+
isError: true,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// ─── Safe: backup → commit → TTRD ───
|
|
130
|
+
for (const fp of committedFiles) {
|
|
131
|
+
try {
|
|
132
|
+
saveBackup(process.cwd(), fp);
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
}
|
|
136
|
+
if (kernelResult.healedFiles) {
|
|
137
|
+
for (const hf of kernelResult.healedFiles) {
|
|
138
|
+
try {
|
|
139
|
+
saveBackup(process.cwd(), path.resolve(process.cwd(), hf));
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
if (deps.chronos)
|
|
143
|
+
deps.chronos.recordHeal(hf);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await kernel.commitToDisk();
|
|
147
|
+
if (deps.chronos && kernelResult.postContracts) {
|
|
148
|
+
// Regression tracking
|
|
149
|
+
if (kernelResult.regressions && kernelResult.regressions.length > 0) {
|
|
150
|
+
const byFile = new Map();
|
|
151
|
+
for (const r of kernelResult.regressions) {
|
|
152
|
+
const arr = byFile.get(r.filePath) || [];
|
|
153
|
+
arr.push(r);
|
|
154
|
+
byFile.set(r.filePath, arr);
|
|
155
|
+
}
|
|
156
|
+
const penaltyList = [];
|
|
157
|
+
for (const [fPath, regs] of byFile.entries()) {
|
|
158
|
+
deps.chronos.recordRegressions(path.resolve(process.cwd(), fPath), regs);
|
|
159
|
+
for (const r of regs) {
|
|
160
|
+
penaltyList.push(` - \`${r.symbol}\` in \`${path.basename(fPath)}\`: \`${r.oldType}\` -> \`${r.newType}\``);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
ttrdFeedback += `\n\n**TYPE REGRESSION DETECTED**\n` +
|
|
164
|
+
`The edit compiled successfully, but weakened type safety:\n` +
|
|
165
|
+
`${penaltyList.join("\n")}\n` +
|
|
166
|
+
`This technical debt has been logged. Restore strict typing instead of using any/unknown.`;
|
|
167
|
+
}
|
|
168
|
+
// Debt payment tracking
|
|
169
|
+
const allPaid = [];
|
|
170
|
+
for (const fp of committedFiles) {
|
|
171
|
+
const posixPath = kernel.resolvePosixPath(fp);
|
|
172
|
+
const fileContracts = kernelResult.postContracts.get(posixPath);
|
|
173
|
+
const paid = deps.chronos.assessDebtPayments(fp, fileContracts);
|
|
174
|
+
if (paid.length > 0) {
|
|
175
|
+
allPaid.push(`\`${path.basename(fp)}\`: ${paid.join(", ")}`);
|
|
176
|
+
}
|
|
177
|
+
const hasRegressionHere = kernelResult.regressions?.some(r => r.filePath === posixPath);
|
|
178
|
+
if (!hasRegressionHere) {
|
|
179
|
+
deps.chronos.recordSuccess(fp);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (allPaid.length > 0) {
|
|
183
|
+
ttrdFeedback += `\n\n**TYPE DEBT PAID**\n` +
|
|
184
|
+
`Strict typing restored for: ${allPaid.map(s => s).join(", ")}.\n` +
|
|
185
|
+
`Friction score reduced.`;
|
|
186
|
+
}
|
|
187
|
+
deps.chronos.syncTechDebt(kernel.getInitialErrorCount(), kernel.getCurrentErrorCount());
|
|
188
|
+
}
|
|
189
|
+
else if (deps.chronos) {
|
|
190
|
+
for (const fp of committedFiles) {
|
|
191
|
+
deps.chronos.recordSuccess(fp);
|
|
192
|
+
}
|
|
193
|
+
deps.chronos.syncTechDebt(kernel.getInitialErrorCount(), kernel.getCurrentErrorCount());
|
|
194
|
+
}
|
|
195
|
+
return { committed: true, ttrdFeedback };
|
|
196
|
+
}
|
|
46
197
|
// ─── Read ───────────────────────────────────────────────────────────
|
|
47
198
|
export async function handleRead(params, deps) {
|
|
48
199
|
const { engine } = deps;
|
|
@@ -307,20 +458,7 @@ export async function handleEdit(params, deps) {
|
|
|
307
458
|
const mode = (typeof params.mode === "string" && ["replace", "insert_before", "insert_after"].includes(params.mode))
|
|
308
459
|
? params.mode
|
|
309
460
|
: "replace";
|
|
310
|
-
|
|
311
|
-
if (deps.kernel && deps.nrekiMode !== "syntax") {
|
|
312
|
-
if (!deps.kernel.isBooted()) {
|
|
313
|
-
logger.info(`Booting kernel (${deps.nrekiMode} mode). First edit will be slower.`);
|
|
314
|
-
try {
|
|
315
|
-
await ensureHologramReady(deps.kernel, deps.nrekiMode ?? "");
|
|
316
|
-
deps.kernel.boot(process.cwd(), deps.nrekiMode);
|
|
317
|
-
}
|
|
318
|
-
catch (err) {
|
|
319
|
-
logger.error(`Kernel boot failed: ${err.message}. Falling back to Layer 1.`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
useKernel = deps.kernel.isBooted();
|
|
323
|
-
}
|
|
461
|
+
const useKernel = await ensureKernelBooted(deps);
|
|
324
462
|
const result = await semanticEdit(resolvedPath, symbol, new_code, parser, sandbox, mode, useKernel);
|
|
325
463
|
if (!result.success || (useKernel && !result.newContent)) {
|
|
326
464
|
return {
|
|
@@ -375,115 +513,34 @@ export async function handleEdit(params, deps) {
|
|
|
375
513
|
kernelResult = await deps.kernel.interceptAtomicBatch([
|
|
376
514
|
{ targetFile: resolvedPath, proposedContent: result.newContent },
|
|
377
515
|
], dependentsToInject);
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
catch { }
|
|
386
|
-
await deps.kernel.commitToDisk();
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
if (deps.chronos) {
|
|
390
|
-
const errorByFile = new Map();
|
|
391
|
-
for (const e of agentErrors) {
|
|
392
|
-
if (!errorByFile.has(e.file))
|
|
393
|
-
errorByFile.set(e.file, e.message);
|
|
394
|
-
}
|
|
395
|
-
for (const [fragileFile, firstMsg] of errorByFile.entries()) {
|
|
396
|
-
deps.chronos.recordSemanticError(fragileFile, firstMsg);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
await deps.kernel.rollbackAll();
|
|
400
|
-
const structuredInfo = "\n\nSemantic errors:\n" +
|
|
401
|
-
agentErrors.map(e => ` → ${path.relative(process.cwd(), e.file)} (${e.line},${e.column}): ${e.code} - ${e.message}`).join("\n");
|
|
402
|
-
return {
|
|
403
|
-
content: [{
|
|
404
|
-
type: "text",
|
|
405
|
-
text: `## Semantic Edit: BLOCKED BY NREKI (Layer 2)\n\n` +
|
|
406
|
-
`**Symbol:** ${symbol}\n` +
|
|
407
|
-
`**File:** ${file}\n\n` +
|
|
408
|
-
`Layer 1 (syntax) passed, but Layer 2 (cross-file semantics) detected errors.\n` +
|
|
409
|
-
`🛡️ **DISK UNTOUCHED: Caught in RAM. File not modified.**${structuredInfo}\n\n` +
|
|
410
|
-
`Fix the type errors and retry. If you changed a function signature, ` +
|
|
411
|
-
`use \`nreki_code action:"batch_edit"\` to update all callers in one atomic transaction.\n\n` +
|
|
412
|
-
`[NREKI: validated in ${kernelResult.latencyMs}ms]`,
|
|
413
|
-
}],
|
|
414
|
-
isError: true,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
try {
|
|
420
|
-
saveBackup(process.cwd(), resolvedPath);
|
|
421
|
-
}
|
|
422
|
-
catch { }
|
|
423
|
-
if (kernelResult.healedFiles) {
|
|
424
|
-
for (const hf of kernelResult.healedFiles) {
|
|
425
|
-
try {
|
|
426
|
-
saveBackup(process.cwd(), path.resolve(process.cwd(), hf));
|
|
427
|
-
}
|
|
428
|
-
catch { }
|
|
429
|
-
if (deps.chronos)
|
|
430
|
-
deps.chronos.recordHeal(hf);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
await deps.kernel.commitToDisk();
|
|
434
|
-
if (deps.chronos && kernelResult.postContracts) {
|
|
435
|
-
if (kernelResult.regressions && kernelResult.regressions.length > 0) {
|
|
436
|
-
const byFile = new Map();
|
|
437
|
-
for (const r of kernelResult.regressions) {
|
|
438
|
-
const arr = byFile.get(r.filePath) || [];
|
|
439
|
-
arr.push(r);
|
|
440
|
-
byFile.set(r.filePath, arr);
|
|
441
|
-
}
|
|
442
|
-
const penaltyList = [];
|
|
443
|
-
for (const [fPath, regs] of byFile.entries()) {
|
|
444
|
-
deps.chronos.recordRegressions(path.resolve(process.cwd(), fPath), regs);
|
|
445
|
-
for (const r of regs) {
|
|
446
|
-
penaltyList.push(` - \`${r.symbol}\` in \`${path.basename(fPath)}\`: \`${r.oldType}\` -> \`${r.newType}\``);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
ttrdFeedback += `\n\n**TYPE REGRESSION DETECTED**\n` +
|
|
450
|
-
`The edit compiled successfully, but weakened the type safety of the project:\n` +
|
|
451
|
-
`${penaltyList.join("\n")}\n` +
|
|
452
|
-
`This technical debt has been logged. Restore strict typing instead of using any/unknown.`;
|
|
453
|
-
}
|
|
454
|
-
const posixResolved = deps.kernel.resolvePosixPath(resolvedPath);
|
|
455
|
-
const fileContracts = kernelResult.postContracts.get(posixResolved);
|
|
456
|
-
const paidDebts = deps.chronos.assessDebtPayments(resolvedPath, fileContracts);
|
|
457
|
-
if (paidDebts.length > 0) {
|
|
458
|
-
ttrdFeedback += `\n\n**TYPE DEBT PAID**\n` +
|
|
459
|
-
`Strict typing restored for: ${paidDebts.map(s => `\`${s}\``).join(", ")}.\n` +
|
|
460
|
-
`Friction score reduced.`;
|
|
461
|
-
}
|
|
462
|
-
const hasRegressionHere = kernelResult.regressions?.some(r => r.filePath === posixResolved);
|
|
463
|
-
if (!hasRegressionHere) {
|
|
464
|
-
deps.chronos.recordSuccess(resolvedPath);
|
|
465
|
-
}
|
|
466
|
-
deps.chronos.syncTechDebt(deps.kernel.getInitialErrorCount(), deps.kernel.getCurrentErrorCount());
|
|
467
|
-
}
|
|
468
|
-
else if (deps.chronos) {
|
|
469
|
-
deps.chronos.recordSuccess(resolvedPath);
|
|
470
|
-
deps.chronos.syncTechDebt(deps.kernel.getInitialErrorCount(), deps.kernel.getCurrentErrorCount());
|
|
471
|
-
}
|
|
472
|
-
}
|
|
516
|
+
const verifyResult = await processKernelResult(kernelResult, [resolvedPath], deps, `## Semantic Edit: BLOCKED BY NREKI (Layer 2)\n\n` +
|
|
517
|
+
`**Symbol:** ${symbol}\n` +
|
|
518
|
+
`**File:** ${file}\n\n`);
|
|
519
|
+
if (verifyResult.response)
|
|
520
|
+
return verifyResult.response;
|
|
521
|
+
ttrdFeedback = verifyResult.ttrdFeedback;
|
|
473
522
|
}
|
|
474
523
|
catch (kernelError) {
|
|
475
524
|
logger.error(`Kernel error during edit verification: ${kernelError}`);
|
|
476
|
-
try {
|
|
477
|
-
saveBackup(process.cwd(), resolvedPath);
|
|
478
|
-
}
|
|
479
|
-
catch { }
|
|
480
|
-
fs.writeFileSync(resolvedPath, result.newContent, "utf-8");
|
|
481
525
|
try {
|
|
482
526
|
await deps.kernel.rollbackAll();
|
|
483
527
|
}
|
|
484
528
|
catch (e) {
|
|
485
529
|
logger.error(`Rollback after kernel crash also failed: ${e}`);
|
|
486
530
|
}
|
|
531
|
+
return {
|
|
532
|
+
content: [{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: `## Edit: KERNEL ERROR\n\n` +
|
|
535
|
+
`The edit passed AST validation (Layer 1) but the kernel crashed during ` +
|
|
536
|
+
`cross-file semantic verification (Layer 2).\n\n` +
|
|
537
|
+
`**Error:** ${kernelError}\n\n` +
|
|
538
|
+
`The file was NOT modified. The kernel has been reset.\n` +
|
|
539
|
+
`Retry the edit, or apply manually if you trust the change.\n\n` +
|
|
540
|
+
`[NREKI saved ~0 tokens]`,
|
|
541
|
+
}],
|
|
542
|
+
isError: true,
|
|
543
|
+
};
|
|
487
544
|
}
|
|
488
545
|
}
|
|
489
546
|
// ─── End NREKI Layer 2 ───────────────────────────────────────
|
|
@@ -553,12 +610,15 @@ export async function handleBatchEdit(params, deps) {
|
|
|
553
610
|
};
|
|
554
611
|
}
|
|
555
612
|
}
|
|
613
|
+
const uniquePathSet = new Set();
|
|
556
614
|
const uniquePaths = [];
|
|
557
615
|
for (const e of edits) {
|
|
558
616
|
try {
|
|
559
617
|
const resolved = safePath(process.cwd(), e.path);
|
|
560
|
-
if (!
|
|
618
|
+
if (!uniquePathSet.has(resolved)) {
|
|
619
|
+
uniquePathSet.add(resolved);
|
|
561
620
|
uniquePaths.push(resolved);
|
|
621
|
+
}
|
|
562
622
|
}
|
|
563
623
|
catch (err) {
|
|
564
624
|
return {
|
|
@@ -612,20 +672,7 @@ export async function handleBatchEdit(params, deps) {
|
|
|
612
672
|
? e.mode
|
|
613
673
|
: "replace",
|
|
614
674
|
}));
|
|
615
|
-
|
|
616
|
-
if (deps.kernel && deps.nrekiMode !== "syntax") {
|
|
617
|
-
if (!deps.kernel.isBooted()) {
|
|
618
|
-
logger.info(`Booting kernel (${deps.nrekiMode} mode). First edit will be slower.`);
|
|
619
|
-
try {
|
|
620
|
-
await ensureHologramReady(deps.kernel, deps.nrekiMode ?? "");
|
|
621
|
-
deps.kernel.boot(process.cwd(), deps.nrekiMode);
|
|
622
|
-
}
|
|
623
|
-
catch (err) {
|
|
624
|
-
logger.error(`Kernel boot failed: ${err.message}. Falling back to Layer 1.`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
useKernel = deps.kernel.isBooted();
|
|
628
|
-
}
|
|
675
|
+
const useKernel = await ensureKernelBooted(deps);
|
|
629
676
|
const result = await batchSemanticEdit(batchOps, parser, sandbox, process.cwd(), useKernel);
|
|
630
677
|
if (!result.success || (useKernel && !result.vfs)) {
|
|
631
678
|
return {
|
|
@@ -681,130 +728,38 @@ export async function handleBatchEdit(params, deps) {
|
|
|
681
728
|
}
|
|
682
729
|
if (kernelEdits.length > 0) {
|
|
683
730
|
const kernelResult = await deps.kernel.interceptAtomicBatch(kernelEdits, batchDependents);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
catch { }
|
|
693
|
-
}
|
|
694
|
-
await deps.kernel.commitToDisk();
|
|
695
|
-
}
|
|
696
|
-
else {
|
|
697
|
-
if (deps.chronos) {
|
|
698
|
-
const errorByFile = new Map();
|
|
699
|
-
for (const e of agentErrors) {
|
|
700
|
-
if (!errorByFile.has(e.file))
|
|
701
|
-
errorByFile.set(e.file, e.message);
|
|
702
|
-
}
|
|
703
|
-
for (const [fragileFile, firstMsg] of errorByFile.entries()) {
|
|
704
|
-
deps.chronos.recordSemanticError(fragileFile, firstMsg);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
await deps.kernel.rollbackAll();
|
|
708
|
-
const structuredInfo = "\n\nSemantic errors:\n" +
|
|
709
|
-
agentErrors.map(e => ` → ${path.relative(process.cwd(), e.file)} (${e.line},${e.column}): ${e.code} - ${e.message}`).join("\n");
|
|
710
|
-
return {
|
|
711
|
-
content: [{
|
|
712
|
-
type: "text",
|
|
713
|
-
text: `## Batch Edit: BLOCKED BY NREKI (Layer 2)\n\n` +
|
|
714
|
-
`**Edits attempted:** ${result.editCount}\n` +
|
|
715
|
-
`**Files involved:** ${result.fileCount}\n\n` +
|
|
716
|
-
`Layer 1 (syntax) passed for all files, but Layer 2 (cross-file semantics) detected errors.\n` +
|
|
717
|
-
`🛡️ **DISK UNTOUCHED: Transaction aborted in RAM. No files were modified.**${structuredInfo}\n\n` +
|
|
718
|
-
`If you changed a function signature, include ALL callers in the same batch.\n\n` +
|
|
719
|
-
`[NREKI: RAM validated in ${kernelResult.latencyMs}ms]`,
|
|
720
|
-
}],
|
|
721
|
-
isError: true,
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
else {
|
|
726
|
-
for (const filePath of result.vfs.keys()) {
|
|
727
|
-
try {
|
|
728
|
-
saveBackup(process.cwd(), filePath);
|
|
729
|
-
}
|
|
730
|
-
catch { }
|
|
731
|
-
}
|
|
732
|
-
if (kernelResult.healedFiles) {
|
|
733
|
-
for (const hf of kernelResult.healedFiles) {
|
|
734
|
-
try {
|
|
735
|
-
saveBackup(process.cwd(), path.resolve(process.cwd(), hf));
|
|
736
|
-
}
|
|
737
|
-
catch { }
|
|
738
|
-
if (deps.chronos)
|
|
739
|
-
deps.chronos.recordHeal(hf);
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
await deps.kernel.commitToDisk();
|
|
743
|
-
if (deps.chronos && kernelResult.postContracts) {
|
|
744
|
-
if (kernelResult.regressions && kernelResult.regressions.length > 0) {
|
|
745
|
-
const byFile = new Map();
|
|
746
|
-
for (const r of kernelResult.regressions) {
|
|
747
|
-
const arr = byFile.get(r.filePath) || [];
|
|
748
|
-
arr.push(r);
|
|
749
|
-
byFile.set(r.filePath, arr);
|
|
750
|
-
}
|
|
751
|
-
const penaltyList = [];
|
|
752
|
-
for (const [fPath, regs] of byFile.entries()) {
|
|
753
|
-
deps.chronos.recordRegressions(path.resolve(process.cwd(), fPath), regs);
|
|
754
|
-
for (const r of regs) {
|
|
755
|
-
penaltyList.push(` - \`${r.symbol}\` in \`${path.basename(fPath)}\`: \`${r.oldType}\` -> \`${r.newType}\``);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
batchTtrdFeedback += `\n\n**TYPE REGRESSION DETECTED**\n` +
|
|
759
|
-
`The batch compiled successfully, but weakened type safety:\n` +
|
|
760
|
-
`${penaltyList.join("\n")}\n` +
|
|
761
|
-
`This technical debt has been logged. Restore strict typing.`;
|
|
762
|
-
}
|
|
763
|
-
const allPaid = [];
|
|
764
|
-
for (const filePath of result.vfs.keys()) {
|
|
765
|
-
const posixPath = deps.kernel.resolvePosixPath(filePath);
|
|
766
|
-
const fileContracts = kernelResult.postContracts.get(posixPath);
|
|
767
|
-
const paid = deps.chronos.assessDebtPayments(filePath, fileContracts);
|
|
768
|
-
if (paid.length > 0) {
|
|
769
|
-
allPaid.push(`\`${path.basename(filePath)}\`: ${paid.join(", ")}`);
|
|
770
|
-
}
|
|
771
|
-
const hasRegressionHere = kernelResult.regressions?.some(r => r.filePath === posixPath);
|
|
772
|
-
if (!hasRegressionHere) {
|
|
773
|
-
deps.chronos.recordSuccess(filePath);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
if (allPaid.length > 0) {
|
|
777
|
-
batchTtrdFeedback += `\n\n**TYPE DEBT PAID**\n` +
|
|
778
|
-
`Strict typing restored:\n` +
|
|
779
|
-
`${allPaid.join("\n")}\n` +
|
|
780
|
-
`Friction score reduced.`;
|
|
781
|
-
}
|
|
782
|
-
deps.chronos.syncTechDebt(deps.kernel.getInitialErrorCount(), deps.kernel.getCurrentErrorCount());
|
|
783
|
-
}
|
|
784
|
-
else if (deps.chronos) {
|
|
785
|
-
for (const filePath of result.vfs.keys()) {
|
|
786
|
-
deps.chronos.recordSuccess(filePath);
|
|
787
|
-
}
|
|
788
|
-
deps.chronos.syncTechDebt(deps.kernel.getInitialErrorCount(), deps.kernel.getCurrentErrorCount());
|
|
789
|
-
}
|
|
790
|
-
}
|
|
731
|
+
const committedFiles = Array.from(result.vfs.keys());
|
|
732
|
+
const verifyResult = await processKernelResult(kernelResult, committedFiles, deps, `## Batch Edit: BLOCKED BY NREKI (Layer 2)\n\n` +
|
|
733
|
+
`**Edits attempted:** ${result.editCount}\n` +
|
|
734
|
+
`**Files involved:** ${result.fileCount}\n\n`);
|
|
735
|
+
if (verifyResult.response)
|
|
736
|
+
return verifyResult.response;
|
|
737
|
+
batchTtrdFeedback = verifyResult.ttrdFeedback;
|
|
791
738
|
}
|
|
792
739
|
}
|
|
793
740
|
catch (kernelError) {
|
|
794
741
|
logger.error(`Kernel error during batch verification: ${kernelError}`);
|
|
795
|
-
for (const [filePath, content] of result.vfs.entries()) {
|
|
796
|
-
try {
|
|
797
|
-
saveBackup(process.cwd(), filePath);
|
|
798
|
-
}
|
|
799
|
-
catch { }
|
|
800
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
801
|
-
}
|
|
802
742
|
try {
|
|
803
743
|
await deps.kernel.rollbackAll();
|
|
804
744
|
}
|
|
805
745
|
catch (e) {
|
|
806
746
|
logger.error(`Rollback after kernel crash also failed: ${e}`);
|
|
807
747
|
}
|
|
748
|
+
for (const rp of acquiredLocks)
|
|
749
|
+
releaseFileLock(rp);
|
|
750
|
+
return {
|
|
751
|
+
content: [{
|
|
752
|
+
type: "text",
|
|
753
|
+
text: `## Batch Edit: KERNEL ERROR\n\n` +
|
|
754
|
+
`All ${result.editCount} edits passed AST validation (Layer 1) but the kernel ` +
|
|
755
|
+
`crashed during cross-file semantic verification (Layer 2).\n\n` +
|
|
756
|
+
`**Error:** ${kernelError}\n\n` +
|
|
757
|
+
`No files were modified. The kernel has been reset.\n` +
|
|
758
|
+
`Retry the batch edit, or apply edits individually.\n\n` +
|
|
759
|
+
`[NREKI saved ~0 tokens]`,
|
|
760
|
+
}],
|
|
761
|
+
isError: true,
|
|
762
|
+
};
|
|
808
763
|
}
|
|
809
764
|
}
|
|
810
765
|
// ─── End NREKI Layer 2 ───────────────────────────────────────
|