feat(patch): added boundary duplication detection to prevent off-by-one range errors
- Added boundary duplication warning to detect off-by-one range errors in replace_range and replace_line operations. - Updated hashline tool documentation with boundary duplication trap guidance to prevent closing delimiter duplication.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Renamed hashline edit operation types: `append` → `append_at`, `prepend` → `prepend_at`, `append_eof` → `append_file`, `prepend_bof` → `prepend_file`
|
||||
@@ -11,6 +10,7 @@
|
||||
|
||||
### Added
|
||||
|
||||
- Added boundary duplication warning when replace_range or replace_line operations include a last inserted line that matches the next surviving line, helping detect off-by-one range errors
|
||||
- Added git branch isolation for autoresearch sessions via `ensureAutoresearchBranch()` to safely revert failed experiments
|
||||
- Added branch status line to autoresearch initialization and resume prompts showing created or reused branch name
|
||||
- Added `Files in Scope`, `Off Limits`, and `Constraints` sections to autoresearch.md template for explicit scope definition
|
||||
|
||||
@@ -539,6 +539,38 @@ export function applyHashlineEdits(
|
||||
}
|
||||
maybeAutocorrectEscapedTabIndentation(edits, warnings);
|
||||
maybeWarnSuspiciousUnicodeEscapePlaceholder(edits, warnings);
|
||||
|
||||
// Warn when a replace_range/replace_line's last inserted line duplicates the next surviving line.
|
||||
// This catches the common boundary-overreach pattern where the agent includes a closing delimiter
|
||||
// in the replacement but sets `end` to the line before the delimiter, causing duplication.
|
||||
for (const edit of edits) {
|
||||
let endLine: number;
|
||||
switch (edit.op) {
|
||||
case "replace_line":
|
||||
endLine = edit.pos.line;
|
||||
break;
|
||||
case "replace_range":
|
||||
endLine = edit.end.line;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
if (edit.lines.length === 0) continue;
|
||||
const nextSurvivingIdx = endLine; // 0-indexed: endLine (1-indexed) is the next line after `end`
|
||||
if (nextSurvivingIdx >= originalFileLines.length) continue;
|
||||
const nextSurvivingLine = originalFileLines[nextSurvivingIdx];
|
||||
const lastInsertedLine = edit.lines[edit.lines.length - 1];
|
||||
const trimmedNext = nextSurvivingLine.trim();
|
||||
const trimmedLast = lastInsertedLine.trim();
|
||||
// Only warn for non-trivial lines to avoid false positives on blank lines or bare punctuation
|
||||
if (trimmedLast.length > 0 && trimmedLast === trimmedNext) {
|
||||
const tag = formatLineTag(endLine + 1, nextSurvivingLine);
|
||||
warnings.push(
|
||||
`Possible boundary duplication: your last replacement line \`${trimmedLast}\` is identical to the next surviving line ${tag}. ` +
|
||||
`If you meant to replace the entire block, set \`end\` to ${tag} instead.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
// Deduplicate identical edits targeting the same line(s)
|
||||
const seenEditKeys = new Map<string, number>();
|
||||
const dedupIndices = new Set<number>();
|
||||
|
||||
@@ -127,6 +127,7 @@ When adding a sibling declaration, prefer `prepend_at` on the next declaration.
|
||||
- `replace_range` requires both `pos` and `end`. All other anchored ops require `pos` only.
|
||||
- `append_file` and `prepend_file` do not take anchors.
|
||||
- Replace exactly the owned span. If `lines` re-emits content beyond `end`, it will duplicate.
|
||||
- **Boundary duplication trap**: when replacing a block, `end` must be the **last line of the block** (e.g. the closing `}`), not the last *content* line before it. Otherwise the closing delimiter survives and your replacement adds a second copy.
|
||||
- Do not target shared boundary lines such as `} else {`, `} catch (…) {`, `}),`, or `},{`.
|
||||
- For a block, either replace only the body or replace the whole block. Do not split block boundaries.
|
||||
- `lines` must be literal file content with matching indentation. If the file uses tabs, use real tabs.
|
||||
|
||||
Reference in New Issue
Block a user