fix(chunk-tree): corrected zero-width insert indent detection logic

- Fixed zero-width insert detection to derive target indent from neighboring lines instead of replaced range.
- Improved indent selection for body-level insertions by picking deeper indent between two neighbors.
- Updated error messages to clarify insert vs splice operations and line range validation.
- Modified assertChecksum() return type to handle cases with default CRC and no explicit selector.
This commit is contained in:
can1357
2026-04-06 20:56:04 +02:00
parent 5e106e22db
commit 6c6e79f0a7
4 changed files with 31 additions and 6 deletions
+4 -1
View File
@@ -535,8 +535,11 @@ export class EditTool implements AgentTool<TInput> {
const normalizedOperations: ChunkEditOperation[] = [];
const assertChecksum = (op: string, crc: string | undefined, sel: string | undefined): string => {
const assertChecksum = (op: string, crc: string | undefined, sel: string | undefined): string | undefined => {
if (crc) return crc.toUpperCase();
// When no explicit sel is given, the default selector/crc from
// the path will be applied downstream by applyChunkEdits.
if (!sel && defaultCrc) return undefined;
if (sel && chunkTree) {
const resolved = resolveChunkPath(chunkTree, sel);
if (resolved) {
+23 -1
View File
@@ -1361,7 +1361,29 @@ export function applyChunkEdits(params: {
const rangeStart = lineStartOffset(offsets, absBeg, state.source);
const rangeEnd = lineEndOffset(offsets, absEnd, state.source);
const replacedRange = state.source.slice(rangeStart, rangeEnd);
const targetIndent = detectCommonIndent(replacedRange).prefix;
// For zero-width inserts the replaced range is empty; derive
// indent from the two neighboring lines, picking the deeper
// indent so body-level insertions stay at body depth even when
// one neighbor is a boundary line (opening/closing brace).
let targetIndent: string;
if (isZeroWidthInsert(absBeg, absEnd)) {
const indA = detectCommonIndent(
state.source.slice(
lineStartOffset(offsets, absEnd, state.source),
lineEndOffset(offsets, absEnd, state.source),
),
).prefix;
const bLine = Math.min(absBeg, state.tree.lineCount);
const indB = detectCommonIndent(
state.source.slice(
lineStartOffset(offsets, bLine, state.source),
lineEndOffset(offsets, bLine, state.source),
),
).prefix;
targetIndent = indA.length >= indB.length ? indA : indB;
} else {
targetIndent = detectCommonIndent(replacedRange).prefix;
}
let replacement = normalizeInsertedContent(operation.content, targetIndent);
if (replacement.length > 0 && !replacement.endsWith("\n") && absEnd < state.tree.lineCount) {
replacement += "\n";
@@ -1048,7 +1048,7 @@ describe("zero-width splice (line insertion)", () => {
content: "noop();",
},
]),
).toThrow(/Invalid zero-width splice L20-L19/);
).toThrow(/Invalid zero-width insert L20-L19/);
});
test("zero-width splice after the chunk preserves the separator gap", () => {
const source = `class Worker {\n\trun(): void {\n\t\twork();\n\t}\n\n\tstop(): void {\n\t\tcleanup();\n\t}\n}\n`;
@@ -1250,7 +1250,7 @@ describe("splice", () => {
content: "replacement",
},
]),
).toThrow(/Invalid splice range L4-L2/);
).toThrow(/Invalid line range L4-L2/);
});
test("splice rejects line 0 before edit application", () => {
@@ -1265,7 +1265,7 @@ describe("splice", () => {
content: "replacement",
},
]),
).toThrow(/Splice beg 0 is invalid/);
).toThrow(/Line 0 is invalid/);
});
test("splice with out-of-range lines throws", () => {
@@ -444,7 +444,7 @@ describe("chunk mode tools", () => {
},
],
}),
).rejects.toThrow(/Invalid splice range L5-L2/);
).rejects.toThrow(/Invalid line range L5-L2/);
expect(await Bun.file(filePath).text()).toBe(originalSource);
});