dsh-hashline-edit 0.2.0 → 0.2.2
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/README.md +59 -55
- package/cordis.patch.yml +1 -1
- package/lib/index.js +7225 -6445
- package/lib/types/dsh/contracts.d.ts +3 -12
- package/lib/types/dsh/fs.d.ts +0 -3
- package/lib/types/dsh/guidance.d.ts +5 -5
- package/lib/types/dsh/host-compat.d.ts +78 -7
- package/lib/types/dsh/index.d.ts +7 -17
- package/lib/types/dsh/metrics/events.d.ts +1 -1
- package/lib/types/dsh/metrics/observer.d.ts +1 -1
- package/lib/types/dsh/sandbox.d.ts +2 -2
- package/lib/types/dsh/shell-hook.d.ts +1 -1
- package/lib/types/dsh/tools/edit.d.ts +10 -0
- package/lib/types/dsh/tools/read.d.ts +3 -3
- package/lib/types/src/domain-errors.d.ts +2 -16
- package/lib/types/src/edit-tool.d.ts +41 -0
- package/lib/types/src/edit-undo.d.ts +2 -11
- package/lib/types/src/file-content/index.d.ts +1 -1
- package/lib/types/src/file-content/preview.d.ts +1 -1
- package/lib/types/src/hash-store.d.ts +2 -2
- package/lib/types/src/hashline/alphabet.d.ts +16 -1
- package/lib/types/src/hashline/hash-identity.d.ts +10 -4
- package/lib/types/src/mutation-engine/engine.d.ts +31 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +1 -8
- package/lib/types/src/mutation-engine/types.d.ts +50 -40
- package/lib/types/src/payload-contract.d.ts +135 -0
- package/lib/types/src/served-session/serve-render.d.ts +0 -6
- package/lib/types/src/undo-store.d.ts +1 -17
- package/package.json +21 -10
- package/prompts/edit-guidelines.md +10 -0
- package/prompts/edit-snippet.md +1 -0
- package/prompts/edit.md +1 -0
- package/prompts/read-guidelines.md +4 -4
- package/prompts/read.md +1 -1
- package/prompts/undo-last-edit-guidelines.md +2 -2
- package/prompts/undo-last-edit-snippet.md +1 -1
- package/prompts/undo-last-edit.md +1 -1
- package/src/bash-classifier.ts +1626 -0
- package/src/bash-conversion/decide.ts +812 -0
- package/src/bash-display-transform.ts +50 -0
- package/src/bash-multifile-config.ts +239 -0
- package/src/bash-view-config.ts +158 -0
- package/src/cut-repair.ts +11 -77
- package/src/domain-errors.ts +3 -35
- package/src/edit-normalize.ts +8 -0
- package/src/edit-render.ts +161 -0
- package/src/edit-tool.ts +118 -0
- package/src/edit-undo.ts +25 -379
- package/src/edit.ts +136 -0
- package/src/file-content/index.ts +1 -1
- package/src/file-content/preview.ts +1 -1
- package/src/file-content/served-header.ts +1 -1
- package/src/hash-store.ts +1 -18
- package/src/hashline/alphabet.ts +42 -1
- package/src/hashline/hash-identity.ts +12 -3
- package/src/integrations/pi-lens/command.ts +712 -0
- package/src/integrations/pi-lens/config.ts +148 -0
- package/src/integrations/pi-lens/io-bridge-adapter.ts +234 -0
- package/src/integrations/pi-lens/io-bridge.ts +112 -0
- package/src/integrations/pi-lens/settings-overlay.ts +272 -0
- package/src/lifecycle-hooks/index.ts +1992 -0
- package/src/lifecycle-hooks/types.ts +64 -0
- package/src/mutated-files.ts +75 -0
- package/src/mutation-engine/engine.ts +239 -0
- package/src/mutation-engine/index.ts +17 -0
- package/src/mutation-engine/pipeline.ts +27 -198
- package/src/mutation-engine/types.ts +57 -39
- package/src/mutation-queue.ts +1 -1
- package/src/payload-contract.ts +685 -0
- package/src/preview-controller.ts +84 -0
- package/src/prompts.ts +43 -10
- package/src/read.ts +865 -0
- package/src/search-hook.ts +41 -0
- package/src/served-session/serve-render.ts +2 -2
- package/src/store-config.ts +73 -0
- package/src/store-janitor-warning.ts +169 -0
- package/src/store-janitor.ts +539 -0
- package/src/store-kernel/settings.ts +233 -0
- package/src/store.ts +154 -0
- package/src/tui-presenter.ts +134 -0
- package/src/undo-store.ts +5 -74
- package/src/write-hook.ts +130 -0
- package/lib/types/dsh/hide.d.ts +0 -78
- package/lib/types/dsh/tools/apply-patch.d.ts +0 -38
- package/lib/types/src/apply-patch/commit.d.ts +0 -49
- package/lib/types/src/apply-patch/grammar.d.ts +0 -29
- package/lib/types/src/apply-patch/lifecycle.d.ts +0 -32
- package/lib/types/src/apply-patch/plan.d.ts +0 -50
- package/lib/types/src/apply-patch/render.d.ts +0 -51
- package/lib/types/src/apply-patch/resolve.d.ts +0 -129
- package/lib/types/src/apply-patch/tool.d.ts +0 -54
- package/lib/types/src/apply-patch/types.d.ts +0 -362
- package/lib/types/src/fs-lifecycle.d.ts +0 -44
- package/lib/types/src/host-utils.d.ts +0 -63
- package/lib/types/src/mutation-engine/edit-request.d.ts +0 -34
- package/prompts/apply-patch-guidelines.md +0 -10
- package/prompts/apply-patch.md +0 -1
- package/src/apply-patch/commit.ts +0 -190
- package/src/apply-patch/grammar.ts +0 -620
- package/src/apply-patch/index.ts +0 -17
- package/src/apply-patch/lifecycle.ts +0 -195
- package/src/apply-patch/plan.ts +0 -800
- package/src/apply-patch/render.ts +0 -137
- package/src/apply-patch/resolve.ts +0 -470
- package/src/apply-patch/tool.ts +0 -257
- package/src/apply-patch/types.ts +0 -387
- package/src/fs-lifecycle.ts +0 -201
- package/src/host-utils.ts +0 -224
- package/src/mutation-engine/edit-request.ts +0 -39
|
@@ -85,16 +85,12 @@ import { saveCutIntent, dropCutIntent } from "../undo-store.js";
|
|
|
85
85
|
import { repairCutIntents } from "../cut-repair.js";
|
|
86
86
|
import { withSortedMutationQueues } from "../mutation-queue.js";
|
|
87
87
|
import { resolveTarget, writeAtomic, readBytes } from "../fs-write.js";
|
|
88
|
-
import {
|
|
89
|
-
missingAncestorDirs,
|
|
90
|
-
pathKind,
|
|
91
|
-
pruneEmptyDirs,
|
|
92
|
-
removeFile,
|
|
93
|
-
renameFile,
|
|
94
|
-
assertPathWithinWorkspace,
|
|
95
|
-
} from "../fs-lifecycle.js";
|
|
96
88
|
import { toCwd } from "../paths.js";
|
|
97
|
-
import type {
|
|
89
|
+
import type {
|
|
90
|
+
DesiredContent,
|
|
91
|
+
NormalizedEditItem,
|
|
92
|
+
NormalizedEditRequest,
|
|
93
|
+
} from "../payload-contract.js";
|
|
98
94
|
import {
|
|
99
95
|
batchAbortFor,
|
|
100
96
|
batchAbortForMany,
|
|
@@ -103,13 +99,7 @@ import {
|
|
|
103
99
|
wrapParseFailure,
|
|
104
100
|
} from "../edit-response.js";
|
|
105
101
|
import { DEFERRED_STORE_SYNC_WARNING } from "../constants.js";
|
|
106
|
-
import type {
|
|
107
|
-
PipelineOptions,
|
|
108
|
-
ProcessedEditFile,
|
|
109
|
-
ForeignCutRecord,
|
|
110
|
-
MemberLifecycle,
|
|
111
|
-
TransactionRecoveryReport,
|
|
112
|
-
} from "./types.js";
|
|
102
|
+
import type { PipelineOptions, ProcessedEditFile, ForeignCutRecord } from "./types.js";
|
|
113
103
|
import {
|
|
114
104
|
loadEditFile,
|
|
115
105
|
loadForeignServedView,
|
|
@@ -1318,108 +1308,6 @@ async function runCutTransaction(
|
|
|
1318
1308
|
* (those are reported and never committed).
|
|
1319
1309
|
* @returns the batch outcome, anchored on the primary file.
|
|
1320
1310
|
*/
|
|
1321
|
-
function memberLifecycleOf(plan: ProcessedEditFile): MemberLifecycle {
|
|
1322
|
-
return plan.lifecycle ?? { kind: "keep" };
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
function memberPostBytes(plan: ProcessedEditFile): Buffer {
|
|
1326
|
-
return Buffer.from(plan.bom + restoreEndings(plan.result, plan.originalEnding), "utf-8");
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* Publish one already-validated member. This is the only place a lifecycle action reaches the
|
|
1331
|
-
* filesystem: content writes keep using writeAtomic (one serializer); add/delete/move use the
|
|
1332
|
-
* fs-lifecycle primitives that writeAtomic deliberately does not express.
|
|
1333
|
-
*/
|
|
1334
|
-
async function commitMember(
|
|
1335
|
-
plan: ProcessedEditFile,
|
|
1336
|
-
rawPreBytes: ReadonlyMap<string, Buffer>,
|
|
1337
|
-
onPublished: (plan: ProcessedEditFile) => void,
|
|
1338
|
-
workspaceRoot: string | undefined,
|
|
1339
|
-
onAfterMoveRename?: (toAbsolutePath: string) => void | Promise<void>,
|
|
1340
|
-
): Promise<void> {
|
|
1341
|
-
const lifecycle = memberLifecycleOf(plan);
|
|
1342
|
-
switch (lifecycle.kind) {
|
|
1343
|
-
case "keep":
|
|
1344
|
-
case "add":
|
|
1345
|
-
if (workspaceRoot !== undefined) {
|
|
1346
|
-
await assertPathWithinWorkspace(workspaceRoot, plan.absolutePath);
|
|
1347
|
-
}
|
|
1348
|
-
await writeAtomic(plan.absolutePath, memberPostBytes(plan));
|
|
1349
|
-
onPublished(plan);
|
|
1350
|
-
return;
|
|
1351
|
-
case "delete":
|
|
1352
|
-
await removeFile(plan.absolutePath);
|
|
1353
|
-
onPublished(plan);
|
|
1354
|
-
return;
|
|
1355
|
-
case "move": {
|
|
1356
|
-
// WHY: rename first, then (only for an edited move) rewrite the destination. A pure move
|
|
1357
|
-
// WHY: must stay a rename: re-serializing would launder the file's raw line endings.
|
|
1358
|
-
await renameFile(
|
|
1359
|
-
lifecycle.fromAbsolutePath,
|
|
1360
|
-
lifecycle.toAbsolutePath,
|
|
1361
|
-
undefined,
|
|
1362
|
-
workspaceRoot,
|
|
1363
|
-
);
|
|
1364
|
-
// WHY: the rename is already a publication: a later content-write failure must roll this
|
|
1365
|
-
// WHY: member back or the source disappears while the failure claims nothing changed.
|
|
1366
|
-
onPublished(plan);
|
|
1367
|
-
if (onAfterMoveRename !== undefined) await onAfterMoveRename(lifecycle.toAbsolutePath);
|
|
1368
|
-
const post = memberPostBytes(plan);
|
|
1369
|
-
// WHY: a pure rename must not re-serialize: the normalized text equals the baseline text,
|
|
1370
|
-
// WHY: so the destination keeps the exact bytes the OS rename carried.
|
|
1371
|
-
if (
|
|
1372
|
-
plan.originalNormalized !== plan.result &&
|
|
1373
|
-
!post.equals(rawPreBytes.get(plan.absolutePath)!)
|
|
1374
|
-
) {
|
|
1375
|
-
await writeAtomic(lifecycle.toAbsolutePath, post);
|
|
1376
|
-
}
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
/**
|
|
1383
|
-
* Restore one member that the commit had already published. A rollback is a BYTE restore, never
|
|
1384
|
-
* a re-serialization: keep/delete write the captured pre-image buffer back; add removes what it
|
|
1385
|
-
* created (and the empty directories it made); move renames back first, then restores the bytes.
|
|
1386
|
-
*/
|
|
1387
|
-
async function rollbackMember(
|
|
1388
|
-
plan: ProcessedEditFile,
|
|
1389
|
-
rawPreBytes: ReadonlyMap<string, Buffer>,
|
|
1390
|
-
workspaceRoot: string | undefined,
|
|
1391
|
-
): Promise<void> {
|
|
1392
|
-
const lifecycle = memberLifecycleOf(plan);
|
|
1393
|
-
const pre = rawPreBytes.get(plan.absolutePath)!;
|
|
1394
|
-
switch (lifecycle.kind) {
|
|
1395
|
-
case "add":
|
|
1396
|
-
if ((await pathKind(plan.absolutePath)) !== "absent") await removeFile(plan.absolutePath);
|
|
1397
|
-
await pruneEmptyDirs(lifecycle.createdDirs);
|
|
1398
|
-
return;
|
|
1399
|
-
case "delete":
|
|
1400
|
-
await writeAtomic(plan.absolutePath, pre);
|
|
1401
|
-
return;
|
|
1402
|
-
case "move": {
|
|
1403
|
-
if ((await pathKind(lifecycle.toAbsolutePath)) !== "absent") {
|
|
1404
|
-
await renameFile(
|
|
1405
|
-
lifecycle.toAbsolutePath,
|
|
1406
|
-
lifecycle.fromAbsolutePath,
|
|
1407
|
-
undefined,
|
|
1408
|
-
workspaceRoot,
|
|
1409
|
-
);
|
|
1410
|
-
}
|
|
1411
|
-
// WHY: the rename carries the pre bytes for a pure move and for a failed content write; only
|
|
1412
|
-
// WHY: rewrite when the destination really holds different bytes (a completed edit rolled back).
|
|
1413
|
-
if (!(await readBytes(lifecycle.fromAbsolutePath)).equals(pre)) {
|
|
1414
|
-
await writeAtomic(lifecycle.fromAbsolutePath, pre);
|
|
1415
|
-
}
|
|
1416
|
-
await pruneEmptyDirs(lifecycle.createdDirs);
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
case "keep":
|
|
1420
|
-
await writeAtomic(plan.absolutePath, pre);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
1311
|
async function runCorrelatedTransaction(
|
|
1424
1312
|
writePlans: ProcessedEditFile[],
|
|
1425
1313
|
sectionPlans: ProcessedEditFile[],
|
|
@@ -1439,60 +1327,18 @@ async function runCorrelatedTransaction(
|
|
|
1439
1327
|
// WHY: (ticket-04b §11: after an abort EVERY file is byte-identical to its pre bytes).
|
|
1440
1328
|
const rawPreBytes = new Map<string, Buffer>();
|
|
1441
1329
|
for (const plan of writePlans) {
|
|
1442
|
-
const lifecycle = plan.lifecycle;
|
|
1443
|
-
if (lifecycle?.kind === "add") {
|
|
1444
|
-
// WHY: an add has no pre-image by construction. A dry buffer keeps every consumer of the
|
|
1445
|
-
// WHY: map total (rollback reads it, undo carries it as NULL through its lifecycle arm).
|
|
1446
|
-
rawPreBytes.set(plan.absolutePath, Buffer.alloc(0));
|
|
1447
|
-
plan.lifecycle = { ...lifecycle, createdDirs: await missingAncestorDirs(plan.absolutePath) };
|
|
1448
|
-
continue;
|
|
1449
|
-
}
|
|
1450
|
-
if (lifecycle?.kind === "move") {
|
|
1451
|
-
rawPreBytes.set(plan.absolutePath, await readBytes(lifecycle.fromAbsolutePath));
|
|
1452
|
-
plan.lifecycle = {
|
|
1453
|
-
...lifecycle,
|
|
1454
|
-
createdDirs: await missingAncestorDirs(lifecycle.toAbsolutePath),
|
|
1455
|
-
};
|
|
1456
|
-
continue;
|
|
1457
|
-
}
|
|
1458
1330
|
rawPreBytes.set(plan.absolutePath, await readBytes(plan.absolutePath));
|
|
1459
1331
|
}
|
|
1460
1332
|
const saved: { restore: () => Promise<void> }[] = [];
|
|
1461
1333
|
for (const plan of writePlans) {
|
|
1462
|
-
const lifecycle = plan.lifecycle;
|
|
1463
|
-
const isAdd = lifecycle?.kind === "add";
|
|
1464
|
-
const isDelete = lifecycle?.kind === "delete";
|
|
1465
|
-
const isMove = lifecycle?.kind === "move";
|
|
1466
|
-
const rawPost = Buffer.from(
|
|
1467
|
-
plan.bom + restoreEndings(plan.result, plan.originalEnding),
|
|
1468
|
-
"utf-8",
|
|
1469
|
-
);
|
|
1470
|
-
// WHY: a pure move carries the raw pre bytes to the destination; recording the canonical
|
|
1471
|
-
// WHY: fold as its post image would make undo's byte oracle reject a legal mixed-ending file.
|
|
1472
|
-
const storedRawPost =
|
|
1473
|
-
isMove && plan.originalNormalized === plan.result
|
|
1474
|
-
? rawPreBytes.get(plan.absolutePath)!
|
|
1475
|
-
: rawPost;
|
|
1476
1334
|
const undo = await saveUndo(plan.absolutePath, {
|
|
1477
|
-
content:
|
|
1335
|
+
content: plan.originalNormalized,
|
|
1478
1336
|
bom: plan.bom,
|
|
1479
1337
|
originalEnding: plan.originalEnding,
|
|
1480
1338
|
hashes: plan.originalHashes,
|
|
1481
|
-
resultContent:
|
|
1339
|
+
resultContent: plan.result,
|
|
1482
1340
|
transactionId: txnId,
|
|
1483
|
-
rawPre:
|
|
1484
|
-
...(isMove
|
|
1485
|
-
? {
|
|
1486
|
-
lifecycle: "move" as const,
|
|
1487
|
-
fromPath: lifecycle.fromAbsolutePath,
|
|
1488
|
-
toPath: lifecycle.toAbsolutePath,
|
|
1489
|
-
rawPost: storedRawPost.toString("utf-8"),
|
|
1490
|
-
}
|
|
1491
|
-
: isAdd
|
|
1492
|
-
? { lifecycle: "add" as const, createdDirs: lifecycle.createdDirs }
|
|
1493
|
-
: isDelete
|
|
1494
|
-
? { lifecycle: "delete" as const }
|
|
1495
|
-
: { lifecycle: "edit" as const }),
|
|
1341
|
+
rawPre: rawPreBytes.get(plan.absolutePath)!.toString("utf-8"),
|
|
1496
1342
|
});
|
|
1497
1343
|
if (!undo.persisted) {
|
|
1498
1344
|
for (const prior of saved) await prior.restore();
|
|
@@ -1520,45 +1366,38 @@ async function runCorrelatedTransaction(
|
|
|
1520
1366
|
await options.onBeforeFirstCutWrite();
|
|
1521
1367
|
}
|
|
1522
1368
|
abortIf(options?.signal);
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1369
|
+
await writeAtomic(
|
|
1370
|
+
primary.absolutePath,
|
|
1371
|
+
primary.bom + restoreEndings(primary.result, primary.originalEnding),
|
|
1372
|
+
);
|
|
1373
|
+
written.push(primary);
|
|
1374
|
+
for (const plan of writePlans.slice(1)) {
|
|
1375
|
+
if (options?.onCutBetweenWrites) {
|
|
1526
1376
|
// WHY: test-only fault-injection seam (ticket-04b §3): fires INSIDE the two-rename
|
|
1527
1377
|
// WHY: window, after the previous file's rename is durable.
|
|
1528
1378
|
await options.onCutBetweenWrites(written[written.length - 1]!.absolutePath);
|
|
1529
1379
|
}
|
|
1530
1380
|
abortIf(options?.signal);
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
await commitMember(
|
|
1535
|
-
plan,
|
|
1536
|
-
rawPreBytes,
|
|
1537
|
-
publish,
|
|
1538
|
-
options?.workspaceRoot,
|
|
1539
|
-
options?.onAfterMoveRename,
|
|
1381
|
+
await writeAtomic(
|
|
1382
|
+
plan.absolutePath,
|
|
1383
|
+
plan.bom + restoreEndings(plan.result, plan.originalEnding),
|
|
1540
1384
|
);
|
|
1385
|
+
written.push(plan);
|
|
1541
1386
|
}
|
|
1542
1387
|
} catch (error) {
|
|
1543
|
-
|
|
1544
|
-
// WHY: proven back at its pre-image, remaining = rollback did not run / did not hold,
|
|
1545
|
-
// WHY: uncertain = the rollback attempt itself threw (we cannot claim either state).
|
|
1546
|
-
const report: TransactionRecoveryReport = { restored: [], remaining: [], uncertain: [] };
|
|
1388
|
+
let rolledBack = true;
|
|
1547
1389
|
for (const plan of written) {
|
|
1548
1390
|
try {
|
|
1549
|
-
await
|
|
1550
|
-
report.restored.push(plan.path);
|
|
1391
|
+
await writeAtomic(plan.absolutePath, rawPreBytes.get(plan.absolutePath)!);
|
|
1551
1392
|
} catch (rollbackError) {
|
|
1552
|
-
|
|
1553
|
-
report.uncertain.push(plan.path);
|
|
1393
|
+
rolledBack = false;
|
|
1554
1394
|
reportDiagnostic(
|
|
1555
1395
|
"mutation-engine",
|
|
1556
|
-
"Failed to roll back a transaction
|
|
1396
|
+
"Failed to roll back a cut-transaction file:",
|
|
1557
1397
|
rollbackError,
|
|
1558
1398
|
);
|
|
1559
1399
|
}
|
|
1560
1400
|
}
|
|
1561
|
-
const rolledBack = report.remaining.length === 0;
|
|
1562
1401
|
if (rolledBack) {
|
|
1563
1402
|
for (const prior of saved) await prior.restore();
|
|
1564
1403
|
try {
|
|
@@ -1572,8 +1411,8 @@ async function runCorrelatedTransaction(
|
|
|
1572
1411
|
}
|
|
1573
1412
|
}
|
|
1574
1413
|
// WHY: a rollback that did not hold LEAVES the intent row and the transaction-tagged undo
|
|
1575
|
-
// WHY: rows: next-run repair resolves to complete-the-
|
|
1576
|
-
(
|
|
1414
|
+
// WHY: rows: next-run repair resolves to complete-the-cut or restore-both from the stored
|
|
1415
|
+
// WHY: pre/post bytes (ADR-0028) — never a silent loss, never a fake atomicity claim.
|
|
1577
1416
|
throw error;
|
|
1578
1417
|
}
|
|
1579
1418
|
|
|
@@ -1590,13 +1429,10 @@ async function runCorrelatedTransaction(
|
|
|
1590
1429
|
// WHY: is REPORTED but never committed: its bytes did not move, so it owns no store write.
|
|
1591
1430
|
const sections = [];
|
|
1592
1431
|
for (const plan of sectionPlans) {
|
|
1593
|
-
// WHY: a deleted file has no post-image to snapshot or serve; every other member commits its
|
|
1594
|
-
// WHY: new anchors through the same finishCommittedFile the single-file edit uses.
|
|
1595
|
-
const materializes = plan.appliedCount > 0 && memberLifecycleOf(plan).kind !== "delete";
|
|
1596
1432
|
sections.push(
|
|
1597
1433
|
toSection(
|
|
1598
1434
|
plan,
|
|
1599
|
-
|
|
1435
|
+
plan.appliedCount > 0 ? await finishCommittedFile(plan, options, sessionKey) : undefined,
|
|
1600
1436
|
),
|
|
1601
1437
|
);
|
|
1602
1438
|
}
|
|
@@ -1981,10 +1817,3 @@ export async function execEdits(
|
|
|
1981
1817
|
): Promise<ProcessedEditFile> {
|
|
1982
1818
|
return runMutations(request, cwd, options);
|
|
1983
1819
|
}
|
|
1984
|
-
|
|
1985
|
-
/**
|
|
1986
|
-
* Visibility-only export of the correlated transaction for apply_patch's commit seam. The
|
|
1987
|
-
* function body is the one the cut path already uses; apply_patch members carry an optional
|
|
1988
|
-
* lifecycle action read by that body. No engine semantics change.
|
|
1989
|
-
*/
|
|
1990
|
-
export { runCorrelatedTransaction as commitTransaction };
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import type { LineEnding } from "../edit-diff.js";
|
|
9
9
|
import type { ResolvedRange } from "../served-session/serve-render.js";
|
|
10
10
|
import type { HashStore } from "../hash-store.js";
|
|
11
|
+
import type { EditDetails, RMetrics } from "../edit-response.js";
|
|
12
|
+
import type { DomainErrorCode, RangeCause } from "../domain-errors.js";
|
|
13
|
+
import type { ServedRow } from "../domain-errors.js";
|
|
11
14
|
|
|
12
15
|
// WHY: Re-export pipeline-facing options — validated once at admission (edit.ts),
|
|
13
16
|
// WHY: trusted inside the engine. No `any`.
|
|
14
17
|
export interface PipelineOptions {
|
|
15
|
-
/** Workspace root used to re-assert lifecycle path boundaries at commit time. */
|
|
16
|
-
workspaceRoot?: string;
|
|
17
18
|
accessMode?: number;
|
|
18
19
|
signal?: AbortSignal;
|
|
19
20
|
store?: HashStore;
|
|
@@ -35,22 +36,6 @@ export interface PipelineOptions {
|
|
|
35
36
|
* window cannot show that.
|
|
36
37
|
*/
|
|
37
38
|
onBeforeFirstCutWrite?: () => void | Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* TEST-ONLY mid-member fault seam: called after a move's rename is durable and before its
|
|
40
|
-
* destination content is rewritten, so the rollback of that in-flight member is exercised.
|
|
41
|
-
*/
|
|
42
|
-
onAfterMoveRename?: (toAbsolutePath: string) => void | Promise<void>;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* The honest outcome of a failed multi-member commit's rollback attempt. `restored` members are
|
|
47
|
-
* proven back at their pre-images; `remaining` members were not rolled back; `uncertain` members
|
|
48
|
-
* are those whose rollback attempt itself threw, so neither state can be claimed for them.
|
|
49
|
-
*/
|
|
50
|
-
export interface TransactionRecoveryReport {
|
|
51
|
-
restored: string[];
|
|
52
|
-
remaining: string[];
|
|
53
|
-
uncertain: string[];
|
|
54
39
|
}
|
|
55
40
|
|
|
56
41
|
/**
|
|
@@ -66,24 +51,6 @@ export interface ForeignCutRecord {
|
|
|
66
51
|
spanTo: string;
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
/**
|
|
70
|
-
* The file-lifecycle action one transaction member carries.
|
|
71
|
-
*
|
|
72
|
-
* This is the engine-local vocabulary: apply_patch's plan IR maps onto it in
|
|
73
|
-
* `src/apply-patch/commit.ts` so the mutation engine never imports the apply-patch layer.
|
|
74
|
-
* Absent is an ordinary in-place edit.
|
|
75
|
-
*/
|
|
76
|
-
export type MemberLifecycle =
|
|
77
|
-
| { kind: "keep" }
|
|
78
|
-
| { kind: "add"; createdDirs: readonly string[] }
|
|
79
|
-
| { kind: "delete" }
|
|
80
|
-
| {
|
|
81
|
-
kind: "move";
|
|
82
|
-
fromAbsolutePath: string;
|
|
83
|
-
toAbsolutePath: string;
|
|
84
|
-
createdDirs: readonly string[];
|
|
85
|
-
};
|
|
86
|
-
|
|
87
54
|
// WHY: Internal: the engine's view of one file's mutation outcome.
|
|
88
55
|
// WHY: Mirrors `ProcessedEditFile` from the old pipeline — kept here as the engine's owned fact.
|
|
89
56
|
export interface ProcessedEditFile {
|
|
@@ -129,9 +96,60 @@ export interface ProcessedEditFile {
|
|
|
129
96
|
* correlated two-file transaction; the in-memory buffer itself never touches these files.
|
|
130
97
|
*/
|
|
131
98
|
foreignCuts: ForeignCutRecord[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// WHY: Discriminated success/failure for the deep seam.
|
|
102
|
+
// WHY: Callers use exhaustive switch on `ok` — no `isError` flag checks,
|
|
103
|
+
// WHY: no `any` threading.
|
|
104
|
+
export interface MutationSuccess {
|
|
105
|
+
ok: true;
|
|
106
|
+
/** SAFETY: Normalized result content (LF). */
|
|
107
|
+
result: string;
|
|
108
|
+
/** SAFETY: Unified diff (hash-anchored) for model consumption. */
|
|
109
|
+
diff: string;
|
|
110
|
+
/** SAFETY: User-facing drift notice, if any (details only, not model content). */
|
|
111
|
+
drift: string | undefined;
|
|
112
|
+
/** SAFETY: Metrics for telemetry. */
|
|
113
|
+
metrics: RMetrics;
|
|
114
|
+
raw: ProcessedEditFile;
|
|
115
|
+
/** SAFETY: Full tool result (content + details) for pi's tool_result hook. */
|
|
116
|
+
toolResult: {
|
|
117
|
+
content: Array<{ type: "text"; text: string }>;
|
|
118
|
+
details: EditDetails;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MutationFailure {
|
|
123
|
+
ok: false;
|
|
124
|
+
/**
|
|
125
|
+
* SAFETY: Machine code — a registry member (`DomainErrorCode`). `toFailure` validates every
|
|
126
|
+
* caught code through the envelope reader (`src/error-envelope.ts`): non-registry strings
|
|
127
|
+
* (errno pass-through included) route to `E_UNKNOWN`, so the union is honest.
|
|
128
|
+
*/
|
|
129
|
+
code: DomainErrorCode;
|
|
130
|
+
/** SAFETY: Human message — model-facing signal when applicable. */
|
|
131
|
+
message: string;
|
|
132
|
+
/** SAFETY: Fresh served block for retry when available (reject-and-serve). */
|
|
133
|
+
servedBlock?: string;
|
|
134
|
+
servedRows?: ServedRow[];
|
|
132
135
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
136
|
+
* SAFETY: the RAW `E_BAD_PAYLOAD` message behind a rendered refusal, when the route is one, so
|
|
137
|
+
* SAFETY: the tool seam can name the invoking tool instead of falling back to the neutral
|
|
138
|
+
* SAFETY: wording. Present only for `code: "E_BAD_PAYLOAD"`.
|
|
135
139
|
*/
|
|
136
|
-
|
|
140
|
+
payloadMessage?: string;
|
|
141
|
+
/** SAFETY: User-facing diagnosis, never a model remedy — a GLOSSARY.md glossary term. */
|
|
142
|
+
cause?: RangeCause;
|
|
143
|
+
details?: { code: DomainErrorCode; cause: RangeCause };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type MutationResult = MutationSuccess | MutationFailure;
|
|
147
|
+
|
|
148
|
+
// WHY: Narrowing helpers — keep call sites exhaustive.
|
|
149
|
+
export function isMutationSuccess(r: MutationResult): r is MutationSuccess {
|
|
150
|
+
return r.ok === true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isMutationFailure(r: MutationResult): r is MutationFailure {
|
|
154
|
+
return r.ok === false;
|
|
137
155
|
}
|
package/src/mutation-queue.ts
CHANGED