dsh-hashline-edit 0.2.2 → 0.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/README.md +57 -45
- package/cordis.patch.yml +9 -3
- package/lib/client.js +231 -0
- package/lib/index.js +3298 -3957
- package/lib/types/dsh/contracts.d.ts +12 -3
- package/lib/types/dsh/fs.d.ts +3 -0
- package/lib/types/dsh/guidance.d.ts +12 -6
- package/lib/types/dsh/hide.d.ts +79 -0
- package/lib/types/dsh/host-compat.d.ts +7 -78
- package/lib/types/dsh/index.d.ts +53 -10
- 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/apply-patch.d.ts +38 -0
- package/lib/types/dsh/tools/read.d.ts +3 -3
- package/lib/types/src/apply-patch/commit.d.ts +49 -0
- package/lib/types/src/apply-patch/grammar.d.ts +29 -0
- package/lib/types/src/apply-patch/lifecycle.d.ts +32 -0
- package/lib/types/src/apply-patch/plan.d.ts +50 -0
- package/lib/types/src/apply-patch/render.d.ts +51 -0
- package/lib/types/src/apply-patch/resolve.d.ts +129 -0
- package/lib/types/src/apply-patch/tool.d.ts +54 -0
- package/lib/types/src/apply-patch/types.d.ts +362 -0
- package/lib/types/src/domain-errors.d.ts +16 -2
- package/lib/types/src/edit-undo.d.ts +11 -2
- 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/fs-lifecycle.d.ts +44 -0
- package/lib/types/src/hashline/alphabet.d.ts +4 -3
- package/lib/types/src/hashline/hash-identity.d.ts +12 -7
- package/lib/types/src/host-utils.d.ts +63 -0
- package/lib/types/src/mutation-engine/edit-request.d.ts +34 -0
- package/lib/types/src/mutation-engine/pipeline.d.ts +8 -1
- package/lib/types/src/mutation-engine/types.d.ts +40 -50
- package/lib/types/src/served-session/serve-render.d.ts +6 -0
- package/lib/types/src/undo-store.d.ts +17 -1
- package/package.json +37 -21
- package/prompts/apply-patch-guidelines.md +4 -0
- package/prompts/apply-patch.md +1 -0
- package/prompts/read-guidelines.md +6 -6
- 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/prompts/zh/apply-patch-guidelines.md +4 -0
- package/prompts/zh/apply-patch.md +1 -0
- package/prompts/zh/read-guidelines.md +6 -0
- package/prompts/zh/read-snippet.md +1 -0
- package/prompts/zh/read.md +1 -0
- package/prompts/zh/undo-last-edit-guidelines.md +2 -0
- package/prompts/zh/undo-last-edit-snippet.md +1 -0
- package/prompts/zh/undo-last-edit.md +1 -0
- package/src/apply-patch/commit.ts +190 -0
- package/src/apply-patch/grammar.ts +620 -0
- package/src/apply-patch/index.ts +17 -0
- package/src/apply-patch/lifecycle.ts +195 -0
- package/src/apply-patch/plan.ts +800 -0
- package/src/apply-patch/render.ts +137 -0
- package/src/apply-patch/resolve.ts +470 -0
- package/src/apply-patch/tool.ts +257 -0
- package/src/apply-patch/types.ts +387 -0
- package/src/cut-repair.ts +77 -11
- package/src/domain-errors.ts +35 -3
- package/src/edit-undo.ts +379 -25
- 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/fs-lifecycle.ts +201 -0
- package/src/hash-store.ts +18 -1
- package/src/hashline/alphabet.ts +7 -7
- package/src/hashline/hash-identity.ts +12 -7
- package/src/host-utils.ts +224 -0
- package/src/mutation-engine/edit-request.ts +39 -0
- package/src/mutation-engine/pipeline.ts +198 -27
- package/src/mutation-engine/types.ts +39 -57
- package/src/mutation-queue.ts +1 -1
- package/src/prompts.ts +3 -30
- package/src/served-session/serve-render.ts +2 -2
- package/src/undo-store.ts +74 -5
- package/lib/types/dsh/tools/edit.d.ts +0 -10
- package/lib/types/src/edit-tool.d.ts +0 -41
- package/lib/types/src/mutation-engine/engine.d.ts +0 -31
- package/lib/types/src/payload-contract.d.ts +0 -135
- package/prompts/edit-guidelines.md +0 -10
- package/prompts/edit-snippet.md +0 -1
- package/prompts/edit.md +0 -1
- package/prompts/read-snippet.md +0 -1
- package/src/bash-classifier.ts +0 -1626
- package/src/bash-conversion/decide.ts +0 -812
- package/src/bash-display-transform.ts +0 -50
- package/src/bash-multifile-config.ts +0 -239
- package/src/bash-view-config.ts +0 -158
- package/src/edit-normalize.ts +0 -8
- package/src/edit-render.ts +0 -161
- package/src/edit-tool.ts +0 -118
- package/src/edit.ts +0 -136
- package/src/integrations/pi-lens/command.ts +0 -712
- package/src/integrations/pi-lens/config.ts +0 -148
- package/src/integrations/pi-lens/io-bridge-adapter.ts +0 -234
- package/src/integrations/pi-lens/io-bridge.ts +0 -112
- package/src/integrations/pi-lens/settings-overlay.ts +0 -272
- package/src/lifecycle-hooks/index.ts +0 -1992
- package/src/lifecycle-hooks/types.ts +0 -64
- package/src/mutated-files.ts +0 -75
- package/src/mutation-engine/engine.ts +0 -239
- package/src/mutation-engine/index.ts +0 -17
- package/src/payload-contract.ts +0 -685
- package/src/preview-controller.ts +0 -84
- package/src/read.ts +0 -865
- package/src/search-hook.ts +0 -41
- package/src/store-config.ts +0 -73
- package/src/store-janitor-warning.ts +0 -169
- package/src/store-janitor.ts +0 -539
- package/src/store-kernel/settings.ts +0 -233
- package/src/store.ts +0 -154
- package/src/tui-presenter.ts +0 -134
- package/src/write-hook.ts +0 -130
|
@@ -85,12 +85,16 @@ 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";
|
|
88
96
|
import { toCwd } from "../paths.js";
|
|
89
|
-
import type {
|
|
90
|
-
DesiredContent,
|
|
91
|
-
NormalizedEditItem,
|
|
92
|
-
NormalizedEditRequest,
|
|
93
|
-
} from "../payload-contract.js";
|
|
97
|
+
import type { DesiredContent, NormalizedEditItem, NormalizedEditRequest } from "./edit-request.js";
|
|
94
98
|
import {
|
|
95
99
|
batchAbortFor,
|
|
96
100
|
batchAbortForMany,
|
|
@@ -99,7 +103,13 @@ import {
|
|
|
99
103
|
wrapParseFailure,
|
|
100
104
|
} from "../edit-response.js";
|
|
101
105
|
import { DEFERRED_STORE_SYNC_WARNING } from "../constants.js";
|
|
102
|
-
import type {
|
|
106
|
+
import type {
|
|
107
|
+
PipelineOptions,
|
|
108
|
+
ProcessedEditFile,
|
|
109
|
+
ForeignCutRecord,
|
|
110
|
+
MemberLifecycle,
|
|
111
|
+
TransactionRecoveryReport,
|
|
112
|
+
} from "./types.js";
|
|
103
113
|
import {
|
|
104
114
|
loadEditFile,
|
|
105
115
|
loadForeignServedView,
|
|
@@ -1308,6 +1318,108 @@ async function runCutTransaction(
|
|
|
1308
1318
|
* (those are reported and never committed).
|
|
1309
1319
|
* @returns the batch outcome, anchored on the primary file.
|
|
1310
1320
|
*/
|
|
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
|
+
}
|
|
1311
1423
|
async function runCorrelatedTransaction(
|
|
1312
1424
|
writePlans: ProcessedEditFile[],
|
|
1313
1425
|
sectionPlans: ProcessedEditFile[],
|
|
@@ -1327,18 +1439,60 @@ async function runCorrelatedTransaction(
|
|
|
1327
1439
|
// WHY: (ticket-04b §11: after an abort EVERY file is byte-identical to its pre bytes).
|
|
1328
1440
|
const rawPreBytes = new Map<string, Buffer>();
|
|
1329
1441
|
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
|
+
}
|
|
1330
1458
|
rawPreBytes.set(plan.absolutePath, await readBytes(plan.absolutePath));
|
|
1331
1459
|
}
|
|
1332
1460
|
const saved: { restore: () => Promise<void> }[] = [];
|
|
1333
1461
|
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;
|
|
1334
1476
|
const undo = await saveUndo(plan.absolutePath, {
|
|
1335
|
-
content: plan.originalNormalized,
|
|
1477
|
+
content: isAdd ? "" : plan.originalNormalized,
|
|
1336
1478
|
bom: plan.bom,
|
|
1337
1479
|
originalEnding: plan.originalEnding,
|
|
1338
1480
|
hashes: plan.originalHashes,
|
|
1339
|
-
resultContent: plan.result,
|
|
1481
|
+
resultContent: isDelete ? "" : plan.result,
|
|
1340
1482
|
transactionId: txnId,
|
|
1341
|
-
rawPre: rawPreBytes.get(plan.absolutePath)!.toString("utf-8"),
|
|
1483
|
+
rawPre: isAdd ? null : rawPreBytes.get(plan.absolutePath)!.toString("utf-8"),
|
|
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 }),
|
|
1342
1496
|
});
|
|
1343
1497
|
if (!undo.persisted) {
|
|
1344
1498
|
for (const prior of saved) await prior.restore();
|
|
@@ -1366,38 +1520,45 @@ async function runCorrelatedTransaction(
|
|
|
1366
1520
|
await options.onBeforeFirstCutWrite();
|
|
1367
1521
|
}
|
|
1368
1522
|
abortIf(options?.signal);
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
);
|
|
1373
|
-
written.push(primary);
|
|
1374
|
-
for (const plan of writePlans.slice(1)) {
|
|
1375
|
-
if (options?.onCutBetweenWrites) {
|
|
1523
|
+
for (let index = 0; index < writePlans.length; index++) {
|
|
1524
|
+
const plan = writePlans[index]!;
|
|
1525
|
+
if (index > 0 && options?.onCutBetweenWrites) {
|
|
1376
1526
|
// WHY: test-only fault-injection seam (ticket-04b §3): fires INSIDE the two-rename
|
|
1377
1527
|
// WHY: window, after the previous file's rename is durable.
|
|
1378
1528
|
await options.onCutBetweenWrites(written[written.length - 1]!.absolutePath);
|
|
1379
1529
|
}
|
|
1380
1530
|
abortIf(options?.signal);
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1531
|
+
const publish = (member: ProcessedEditFile): void => {
|
|
1532
|
+
written.push(member);
|
|
1533
|
+
};
|
|
1534
|
+
await commitMember(
|
|
1535
|
+
plan,
|
|
1536
|
+
rawPreBytes,
|
|
1537
|
+
publish,
|
|
1538
|
+
options?.workspaceRoot,
|
|
1539
|
+
options?.onAfterMoveRename,
|
|
1384
1540
|
);
|
|
1385
|
-
written.push(plan);
|
|
1386
1541
|
}
|
|
1387
1542
|
} catch (error) {
|
|
1388
|
-
|
|
1543
|
+
// WHY: the recovery report is the honest three-way split the contract requires: restored =
|
|
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: [] };
|
|
1389
1547
|
for (const plan of written) {
|
|
1390
1548
|
try {
|
|
1391
|
-
await
|
|
1549
|
+
await rollbackMember(plan, rawPreBytes, options?.workspaceRoot);
|
|
1550
|
+
report.restored.push(plan.path);
|
|
1392
1551
|
} catch (rollbackError) {
|
|
1393
|
-
|
|
1552
|
+
report.remaining.push(plan.path);
|
|
1553
|
+
report.uncertain.push(plan.path);
|
|
1394
1554
|
reportDiagnostic(
|
|
1395
1555
|
"mutation-engine",
|
|
1396
|
-
"Failed to roll back a
|
|
1556
|
+
"Failed to roll back a transaction member:",
|
|
1397
1557
|
rollbackError,
|
|
1398
1558
|
);
|
|
1399
1559
|
}
|
|
1400
1560
|
}
|
|
1561
|
+
const rolledBack = report.remaining.length === 0;
|
|
1401
1562
|
if (rolledBack) {
|
|
1402
1563
|
for (const prior of saved) await prior.restore();
|
|
1403
1564
|
try {
|
|
@@ -1411,8 +1572,8 @@ async function runCorrelatedTransaction(
|
|
|
1411
1572
|
}
|
|
1412
1573
|
}
|
|
1413
1574
|
// WHY: a rollback that did not hold LEAVES the intent row and the transaction-tagged undo
|
|
1414
|
-
// WHY: rows: next-run repair resolves to complete-the-
|
|
1415
|
-
|
|
1575
|
+
// WHY: rows: next-run repair resolves to complete-the-transaction or restore-from-rows.
|
|
1576
|
+
(error as Error & { recovery?: TransactionRecoveryReport }).recovery = report;
|
|
1416
1577
|
throw error;
|
|
1417
1578
|
}
|
|
1418
1579
|
|
|
@@ -1429,10 +1590,13 @@ async function runCorrelatedTransaction(
|
|
|
1429
1590
|
// WHY: is REPORTED but never committed: its bytes did not move, so it owns no store write.
|
|
1430
1591
|
const sections = [];
|
|
1431
1592
|
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";
|
|
1432
1596
|
sections.push(
|
|
1433
1597
|
toSection(
|
|
1434
1598
|
plan,
|
|
1435
|
-
|
|
1599
|
+
materializes ? await finishCommittedFile(plan, options, sessionKey) : undefined,
|
|
1436
1600
|
),
|
|
1437
1601
|
);
|
|
1438
1602
|
}
|
|
@@ -1817,3 +1981,10 @@ export async function execEdits(
|
|
|
1817
1981
|
): Promise<ProcessedEditFile> {
|
|
1818
1982
|
return runMutations(request, cwd, options);
|
|
1819
1983
|
}
|
|
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,13 +8,12 @@
|
|
|
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";
|
|
14
11
|
|
|
15
12
|
// WHY: Re-export pipeline-facing options — validated once at admission (edit.ts),
|
|
16
13
|
// WHY: trusted inside the engine. No `any`.
|
|
17
14
|
export interface PipelineOptions {
|
|
15
|
+
/** Workspace root used to re-assert lifecycle path boundaries at commit time. */
|
|
16
|
+
workspaceRoot?: string;
|
|
18
17
|
accessMode?: number;
|
|
19
18
|
signal?: AbortSignal;
|
|
20
19
|
store?: HashStore;
|
|
@@ -36,6 +35,22 @@ export interface PipelineOptions {
|
|
|
36
35
|
* window cannot show that.
|
|
37
36
|
*/
|
|
38
37
|
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[];
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
/**
|
|
@@ -51,6 +66,24 @@ export interface ForeignCutRecord {
|
|
|
51
66
|
spanTo: string;
|
|
52
67
|
}
|
|
53
68
|
|
|
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
|
+
|
|
54
87
|
// WHY: Internal: the engine's view of one file's mutation outcome.
|
|
55
88
|
// WHY: Mirrors `ProcessedEditFile` from the old pipeline — kept here as the engine's owned fact.
|
|
56
89
|
export interface ProcessedEditFile {
|
|
@@ -96,60 +129,9 @@ export interface ProcessedEditFile {
|
|
|
96
129
|
* correlated two-file transaction; the in-memory buffer itself never touches these files.
|
|
97
130
|
*/
|
|
98
131
|
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[];
|
|
135
132
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* SAFETY: wording. Present only for `code: "E_BAD_PAYLOAD"`.
|
|
133
|
+
* apply_patch lifecycle action for this member; absent is an ordinary edit. The correlated
|
|
134
|
+
* transaction reads it for pre-image capture, commit ordering, rollback and undo-row shape.
|
|
139
135
|
*/
|
|
140
|
-
|
|
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;
|
|
136
|
+
lifecycle?: MemberLifecycle;
|
|
155
137
|
}
|
package/src/mutation-queue.ts
CHANGED
package/src/prompts.ts
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { HASH_LEN } from "./hashline/alphabet.js";
|
|
3
|
-
import {
|
|
4
|
-
ANCHOR_EXAMPLE,
|
|
5
|
-
EDIT_DESCRIPTION,
|
|
6
|
-
EDIT_SNIPPET,
|
|
7
|
-
EDIT_GUIDELINES,
|
|
8
|
-
} from "./payload-contract.js";
|
|
9
3
|
|
|
10
4
|
/**
|
|
11
5
|
* Resolve template variables in shipped prompt prose. Beyond caller-supplied replacements, the
|
|
12
|
-
* anchor-width token
|
|
13
|
-
*
|
|
6
|
+
* anchor-width token is ALWAYS resolved from the live `HASH_LEN`: the width is a build-time setting
|
|
7
|
+
* (ADR-0020), so any prompt that quotes it must not hardcode a digit.
|
|
14
8
|
*/
|
|
15
9
|
function applyReplacements(content: string, replacements?: Record<string, string>): string {
|
|
16
10
|
let out = content;
|
|
@@ -19,36 +13,15 @@ function applyReplacements(content: string, replacements?: Record<string, string
|
|
|
19
13
|
out = out.split(`{{${key}}}`).join(value);
|
|
20
14
|
}
|
|
21
15
|
}
|
|
22
|
-
return out
|
|
23
|
-
.split("{{HASH_LEN}}")
|
|
24
|
-
.join(String(HASH_LEN))
|
|
25
|
-
.split("{{ANCHOR_EXAMPLE}}")
|
|
26
|
-
.join(ANCHOR_EXAMPLE);
|
|
16
|
+
return out.split("{{HASH_LEN}}").join(String(HASH_LEN));
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
const EDIT_PROMPT_FILES = new Set([
|
|
30
|
-
"../prompts/edit.md",
|
|
31
|
-
"prompts/edit.md",
|
|
32
|
-
"../prompts/edit-snippet.md",
|
|
33
|
-
"prompts/edit-snippet.md",
|
|
34
|
-
]);
|
|
35
|
-
|
|
36
|
-
const EDIT_GUIDE_FILES = new Set(["../prompts/edit-guidelines.md", "prompts/edit-guidelines.md"]);
|
|
37
|
-
|
|
38
19
|
export function loadP(relativePath: string, replacements?: Record<string, string>): string {
|
|
39
|
-
if (EDIT_PROMPT_FILES.has(relativePath)) {
|
|
40
|
-
const content = relativePath.includes("snippet") ? EDIT_SNIPPET : EDIT_DESCRIPTION;
|
|
41
|
-
return applyReplacements(content, replacements);
|
|
42
|
-
}
|
|
43
20
|
const content = readFileSync(new URL(relativePath, import.meta.url), "utf-8").trim();
|
|
44
21
|
return applyReplacements(content, replacements);
|
|
45
22
|
}
|
|
46
23
|
|
|
47
24
|
export function loadGuide(relativePath: string, replacements?: Record<string, string>): string[] {
|
|
48
|
-
if (EDIT_GUIDE_FILES.has(relativePath)) {
|
|
49
|
-
const guidelines = [...EDIT_GUIDELINES];
|
|
50
|
-
return guidelines.map((line) => applyReplacements(line, replacements));
|
|
51
|
-
}
|
|
52
25
|
const content = readFileSync(new URL(relativePath, import.meta.url), "utf-8");
|
|
53
26
|
return applyReplacements(content, replacements)
|
|
54
27
|
.split("\n")
|
|
@@ -90,7 +90,7 @@ export function fmtServedRows(rows: ServedRow[], fileLines: string[]): string {
|
|
|
90
90
|
|
|
91
91
|
// WHY: the one continuation a rejected edit offers (read-chrome): the `windows` offset, not a bare
|
|
92
92
|
// WHY: `offset`, because `offset` alone no longer continues anything a page footer used to name.
|
|
93
|
-
function paginationHint(nextOffset: number, more: number): string {
|
|
93
|
+
export function paginationHint(nextOffset: number, more: number): string {
|
|
94
94
|
return `[... ${more} more — use windows: [{ offset: ${nextOffset} }]]`;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -107,7 +107,7 @@ export function servedPositionsOf(served: (string | null)[], hash: string): numb
|
|
|
107
107
|
// WHY: ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
109
|
/** Builds the reject-and-serve served rows + rendered block for a current on-disk range. */
|
|
110
|
-
function buildRangeServeBlock(
|
|
110
|
+
export function buildRangeServeBlock(
|
|
111
111
|
startLine: number,
|
|
112
112
|
endLine: number,
|
|
113
113
|
fileHashes: string[],
|
package/src/undo-store.ts
CHANGED
|
@@ -29,8 +29,25 @@ export interface UndoRecord {
|
|
|
29
29
|
* `null` is a row from before this column, or an ordinary single-file edit.
|
|
30
30
|
*/
|
|
31
31
|
rawPre?: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The lifecycle action this row reverses. Absent/null is an ordinary edit (back-compatible).
|
|
34
|
+
* add = the file did not exist before; delete = the file does not exist after; move = a pure
|
|
35
|
+
* rename with an optional content edit.
|
|
36
|
+
*/
|
|
37
|
+
lifecycle?: UndoLifecycle | null;
|
|
38
|
+
/** Move: the absolute path the file had before the transaction. */
|
|
39
|
+
fromPath?: string | null;
|
|
40
|
+
/** Move: the absolute path the file has after the transaction. */
|
|
41
|
+
toPath?: string | null;
|
|
42
|
+
/** The RAW post-transaction text when the commit wrote non-canonical bytes (pure move). */
|
|
43
|
+
rawPost?: string | null;
|
|
44
|
+
/** Add: the directories the add created, outermost first, JSON-encoded in the column. */
|
|
45
|
+
createdDirs?: readonly string[] | null;
|
|
32
46
|
}
|
|
33
47
|
|
|
48
|
+
/** The lifecycle action one undo row reverses. */
|
|
49
|
+
export type UndoLifecycle = "edit" | "add" | "delete" | "move";
|
|
50
|
+
|
|
34
51
|
export type CutIntentDirection = "revert" | null;
|
|
35
52
|
|
|
36
53
|
export interface CutIntent {
|
|
@@ -50,6 +67,11 @@ export interface UndoStmts {
|
|
|
50
67
|
snapshotHash: string | null,
|
|
51
68
|
transactionId: string | null,
|
|
52
69
|
rawPre: string | null,
|
|
70
|
+
lifecycle: string | null,
|
|
71
|
+
fromPath: string | null,
|
|
72
|
+
toPath: string | null,
|
|
73
|
+
rawPost: string | null,
|
|
74
|
+
createdDirs: string | null,
|
|
53
75
|
anchorGeneration: number,
|
|
54
76
|
updatedAt: number,
|
|
55
77
|
) => void;
|
|
@@ -75,15 +97,15 @@ export function undoStmts(db: DatabaseSync): UndoStmts {
|
|
|
75
97
|
|
|
76
98
|
function buildStmts(db: DatabaseSync): UndoStmts {
|
|
77
99
|
const undoUpsertStmt = db.prepare(
|
|
78
|
-
"INSERT INTO file_undo (path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
|
79
|
-
"ON CONFLICT(path) DO UPDATE SET content = excluded.content, bom = excluded.bom, ending = excluded.ending, hashes = excluded.hashes, result_content = excluded.result_content, snapshot_hash = excluded.snapshot_hash, transaction_id = excluded.transaction_id, raw_pre = excluded.raw_pre, anchor_generation = excluded.anchor_generation, updated_at = excluded.updated_at",
|
|
100
|
+
"INSERT INTO file_undo (path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
|
101
|
+
"ON CONFLICT(path) DO UPDATE SET content = excluded.content, bom = excluded.bom, ending = excluded.ending, hashes = excluded.hashes, result_content = excluded.result_content, snapshot_hash = excluded.snapshot_hash, transaction_id = excluded.transaction_id, raw_pre = excluded.raw_pre, lifecycle = excluded.lifecycle, from_path = excluded.from_path, to_path = excluded.to_path, raw_post = excluded.raw_post, created_dirs = excluded.created_dirs, anchor_generation = excluded.anchor_generation, updated_at = excluded.updated_at",
|
|
80
102
|
);
|
|
81
103
|
const undoGetStmt = db.prepare(
|
|
82
|
-
"SELECT content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation FROM file_undo WHERE path = ?",
|
|
104
|
+
"SELECT content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation FROM file_undo WHERE path = ?",
|
|
83
105
|
);
|
|
84
106
|
const undoDelStmt = db.prepare("DELETE FROM file_undo WHERE path = ?");
|
|
85
107
|
const undoGetTransactionStmt = db.prepare(
|
|
86
|
-
"SELECT path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, anchor_generation FROM file_undo WHERE transaction_id = ? ORDER BY path",
|
|
108
|
+
"SELECT path, content, bom, ending, hashes, result_content, snapshot_hash, transaction_id, raw_pre, lifecycle, from_path, to_path, raw_post, created_dirs, anchor_generation FROM file_undo WHERE transaction_id = ? ORDER BY path",
|
|
87
109
|
);
|
|
88
110
|
const undoDelTransactionStmt = db.prepare("DELETE FROM file_undo WHERE transaction_id = ?");
|
|
89
111
|
const intentUpsertStmt = db.prepare(
|
|
@@ -105,6 +127,11 @@ function buildStmts(db: DatabaseSync): UndoStmts {
|
|
|
105
127
|
snapshotHash,
|
|
106
128
|
transactionId,
|
|
107
129
|
rawPre,
|
|
130
|
+
lifecycle,
|
|
131
|
+
fromPath,
|
|
132
|
+
toPath,
|
|
133
|
+
rawPost,
|
|
134
|
+
createdDirs,
|
|
108
135
|
anchorGeneration,
|
|
109
136
|
updatedAt,
|
|
110
137
|
) => {
|
|
@@ -119,6 +146,11 @@ function buildStmts(db: DatabaseSync): UndoStmts {
|
|
|
119
146
|
snapshotHash,
|
|
120
147
|
transactionId,
|
|
121
148
|
rawPre,
|
|
149
|
+
lifecycle,
|
|
150
|
+
fromPath,
|
|
151
|
+
toPath,
|
|
152
|
+
rawPost,
|
|
153
|
+
createdDirs,
|
|
122
154
|
anchorGeneration,
|
|
123
155
|
updatedAt,
|
|
124
156
|
);
|
|
@@ -164,12 +196,25 @@ onStoreOpen((db) => {
|
|
|
164
196
|
ensureFileUndoSchema(db);
|
|
165
197
|
});
|
|
166
198
|
|
|
199
|
+
/** Parse the JSON-encoded created-directory list; a corrupt value degrades to an empty list. */
|
|
200
|
+
function parseCreatedDirs(value: unknown): string[] | null {
|
|
201
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
202
|
+
try {
|
|
203
|
+
const parsed: unknown = JSON.parse(value);
|
|
204
|
+
return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")
|
|
205
|
+
? (parsed as string[])
|
|
206
|
+
: null;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
167
212
|
function parseUndoRow(row: Record<string, unknown> | undefined): UndoRecord | undefined {
|
|
168
213
|
if (!row) return undefined;
|
|
169
214
|
try {
|
|
170
215
|
const parsed = JSON.parse(row.hashes as string);
|
|
171
216
|
if (!isValidHashList(parsed)) return undefined;
|
|
172
|
-
|
|
217
|
+
const record: UndoRecord = {
|
|
173
218
|
content: row.content as string,
|
|
174
219
|
bom: row.bom as string,
|
|
175
220
|
ending: row.ending as string,
|
|
@@ -180,6 +225,23 @@ function parseUndoRow(row: Record<string, unknown> | undefined): UndoRecord | un
|
|
|
180
225
|
rawPre: (row.raw_pre as string | null) ?? null,
|
|
181
226
|
anchorGeneration: (row.anchor_generation as number | null) ?? 0,
|
|
182
227
|
};
|
|
228
|
+
// WHY: the lifecycle fields are additive; an ordinary row omits them entirely so its
|
|
229
|
+
// WHY: pre-extension shape stays byte-for-byte what existing consumers assert.
|
|
230
|
+
if (row.lifecycle !== null && row.lifecycle !== undefined) {
|
|
231
|
+
record.lifecycle = row.lifecycle as UndoLifecycle;
|
|
232
|
+
}
|
|
233
|
+
if (row.from_path !== null && row.from_path !== undefined) {
|
|
234
|
+
record.fromPath = row.from_path as string;
|
|
235
|
+
}
|
|
236
|
+
if (row.to_path !== null && row.to_path !== undefined) {
|
|
237
|
+
record.toPath = row.to_path as string;
|
|
238
|
+
}
|
|
239
|
+
if (row.raw_post !== null && row.raw_post !== undefined) {
|
|
240
|
+
record.rawPost = row.raw_post as string;
|
|
241
|
+
}
|
|
242
|
+
const createdDirs = parseCreatedDirs(row.created_dirs);
|
|
243
|
+
if (createdDirs !== null) record.createdDirs = createdDirs;
|
|
244
|
+
return record;
|
|
183
245
|
} catch {
|
|
184
246
|
return undefined;
|
|
185
247
|
}
|
|
@@ -196,6 +258,13 @@ export function upsertUndo(store: HashStore, path: string, entry: UndoRecord): v
|
|
|
196
258
|
entry.snapshotHash ?? null,
|
|
197
259
|
entry.transactionId ?? null,
|
|
198
260
|
entry.rawPre ?? null,
|
|
261
|
+
entry.lifecycle ?? null,
|
|
262
|
+
entry.fromPath ?? null,
|
|
263
|
+
entry.toPath ?? null,
|
|
264
|
+
entry.rawPost ?? null,
|
|
265
|
+
entry.createdDirs === null || entry.createdDirs === undefined
|
|
266
|
+
? null
|
|
267
|
+
: JSON.stringify(entry.createdDirs),
|
|
199
268
|
// WHY: ADR-0031 §4 (refuse, do not launder) — the stamp comes from the payload, never
|
|
200
269
|
// WHY: hard-coded: a replay of a pre-generation row preserves its generation so the
|
|
201
270
|
// WHY: restore gate re-derives, while a fresh write (absent generation) defaults current.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
2
|
-
import type { ToolDependencies } from "../contracts.js";
|
|
3
|
-
/** dsh tool name; the agent-layer registration shadows the preset's built-in `edit`. */
|
|
4
|
-
export declare const EDIT_TOOL_NAME = "edit";
|
|
5
|
-
/**
|
|
6
|
-
* Build the dsh `edit` tool for one agent install.
|
|
7
|
-
* @param deps - the host seams this tool may touch; `sandbox` advertises and resolves escalation.
|
|
8
|
-
* @returns a registry-ready dsh tool definition named `edit`.
|
|
9
|
-
*/
|
|
10
|
-
export declare function buildEditTool(deps: ToolDependencies): ToolDefinition;
|