fix(hashline): add autocorrect, batch mismatch reporting, and write anchors
This commit is contained in:
@@ -37,12 +37,12 @@ function generateDiff(oldContent: string, newContent: string, filePath: string):
|
||||
const hash = computeLineHash(lineNum, newLine)
|
||||
|
||||
if (i >= oldLines.length) {
|
||||
diff += `+ ${lineNum}:${hash}|${newLine}\n`
|
||||
diff += `+ ${lineNum}#${hash}:${newLine}\n`
|
||||
} else if (i >= newLines.length) {
|
||||
diff += `- ${lineNum}: |${oldLine}\n`
|
||||
diff += `- ${lineNum}# :${oldLine}\n`
|
||||
} else if (oldLine !== newLine) {
|
||||
diff += `- ${lineNum}: |${oldLine}\n`
|
||||
diff += `+ ${lineNum}:${hash}|${newLine}\n`
|
||||
diff += `- ${lineNum}# :${oldLine}\n`
|
||||
diff += `+ ${lineNum}#${hash}:${newLine}\n`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,41 +51,41 @@ function generateDiff(oldContent: string, newContent: string, filePath: string):
|
||||
|
||||
export function createHashlineEditTool(): ToolDefinition {
|
||||
return tool({
|
||||
description: `Edit files using LINE:HASH format for precise, safe modifications.
|
||||
description: `Edit files using LINE#ID format for precise, safe modifications.
|
||||
|
||||
WORKFLOW:
|
||||
1. Read the file and copy exact LINE:HASH anchors.
|
||||
1. Read the file and copy exact LINE#ID anchors.
|
||||
2. Submit one edit call with all related operations for that file.
|
||||
3. If more edits are needed after success, use the latest anchors from read/edit output.
|
||||
4. Use anchors as "LINE:HASH" only (never include trailing "|content").
|
||||
4. Use anchors as "LINE#ID" only (never include trailing ":content").
|
||||
|
||||
VALIDATION:
|
||||
- Payload shape: { "filePath": string, "edits": [...] }
|
||||
- Each edit must be one of: set_line, replace_lines, insert_after, replace
|
||||
- text/new_text must contain plain replacement text only (no LINE:HASH prefixes, no diff + markers)
|
||||
- text/new_text must contain plain replacement text only (no LINE#ID prefixes, no diff + markers)
|
||||
|
||||
LINE:HASH FORMAT (CRITICAL - READ CAREFULLY):
|
||||
Each line reference must be in "LINE:HASH" format where:
|
||||
LINE#ID FORMAT (CRITICAL - READ CAREFULLY):
|
||||
Each line reference must be in "LINE#ID" format where:
|
||||
- LINE: 1-based line number
|
||||
- HASH: First 2 characters of xxHash32 hash of line content (hex characters 0-9, a-f only)
|
||||
- Example: "5:a3" means line 5 with hash "a3"
|
||||
- WRONG: "2:co" (contains non-hex 'o') - will fail!
|
||||
- CORRECT: "2:e8" (hex characters only)
|
||||
- ID: Two CID letters from the set ZPMQVRWSNKTXJBYH
|
||||
- Example: "5#VK" means line 5 with hash id "VK"
|
||||
- WRONG: "2#aa" (invalid characters) - will fail!
|
||||
- CORRECT: "2#VK"
|
||||
|
||||
GETTING HASHES:
|
||||
Use the read tool - it returns lines in "LINE:HASH|content" format.
|
||||
Successful edit output also includes updated file content in "LINE:HASH|content" format.
|
||||
Use the read tool - it returns lines in "LINE#ID:content" format.
|
||||
Successful edit output also includes updated file content in "LINE#ID:content" format.
|
||||
|
||||
FOUR OPERATION TYPES:
|
||||
|
||||
1. set_line: Replace a single line
|
||||
{ "type": "set_line", "line": "5:a3", "text": "const y = 2" }
|
||||
{ "type": "set_line", "line": "5#VK", "text": "const y = 2" }
|
||||
|
||||
2. replace_lines: Replace a range of lines
|
||||
{ "type": "replace_lines", "start_line": "5:a3", "end_line": "7:b2", "text": "new\\ncontent" }
|
||||
{ "type": "replace_lines", "start_line": "5#VK", "end_line": "7#NP", "text": ["new", "content"] }
|
||||
|
||||
3. insert_after: Insert lines after a specific line
|
||||
{ "type": "insert_after", "line": "5:a3", "text": "console.log('hi')" }
|
||||
{ "type": "insert_after", "line": "5#VK", "text": "console.log('hi')" }
|
||||
|
||||
4. replace: Simple text replacement (no hash validation)
|
||||
{ "type": "replace", "old_text": "foo", "new_text": "bar" }
|
||||
@@ -101,8 +101,10 @@ SEQUENTIAL EDITS (ANTI-FLAKE):
|
||||
BOTTOM-UP APPLICATION:
|
||||
Edits are applied from bottom to top (highest line numbers first) to preserve line number references.
|
||||
|
||||
ESCAPING:
|
||||
Use \\n in text to represent literal newlines.`,
|
||||
CONTENT FORMAT:
|
||||
- text/new_text can be a string (single line) or string[] (multi-line, preferred).
|
||||
- If you pass a multi-line string, it is split by real newline characters.
|
||||
- Literal "\\n" is preserved as text.`,
|
||||
args: {
|
||||
filePath: tool.schema.string().describe("Absolute path to the file to edit"),
|
||||
edits: tool.schema
|
||||
@@ -110,24 +112,32 @@ Use \\n in text to represent literal newlines.`,
|
||||
tool.schema.union([
|
||||
tool.schema.object({
|
||||
type: tool.schema.literal("set_line"),
|
||||
line: tool.schema.string().describe("Line reference in LINE:HASH format"),
|
||||
text: tool.schema.string().describe("New content for the line"),
|
||||
line: tool.schema.string().describe("Line reference in LINE#ID format"),
|
||||
text: tool.schema
|
||||
.union([tool.schema.string(), tool.schema.array(tool.schema.string())])
|
||||
.describe("New content for the line (string or string[] for multiline)"),
|
||||
}),
|
||||
tool.schema.object({
|
||||
type: tool.schema.literal("replace_lines"),
|
||||
start_line: tool.schema.string().describe("Start line in LINE:HASH format"),
|
||||
end_line: tool.schema.string().describe("End line in LINE:HASH format"),
|
||||
text: tool.schema.string().describe("New content to replace the range"),
|
||||
start_line: tool.schema.string().describe("Start line in LINE#ID format"),
|
||||
end_line: tool.schema.string().describe("End line in LINE#ID format"),
|
||||
text: tool.schema
|
||||
.union([tool.schema.string(), tool.schema.array(tool.schema.string())])
|
||||
.describe("New content to replace the range (string or string[] for multiline)"),
|
||||
}),
|
||||
tool.schema.object({
|
||||
type: tool.schema.literal("insert_after"),
|
||||
line: tool.schema.string().describe("Line reference in LINE:HASH format"),
|
||||
text: tool.schema.string().describe("Content to insert after the line"),
|
||||
line: tool.schema.string().describe("Line reference in LINE#ID format"),
|
||||
text: tool.schema
|
||||
.union([tool.schema.string(), tool.schema.array(tool.schema.string())])
|
||||
.describe("Content to insert after the line (string or string[] for multiline)"),
|
||||
}),
|
||||
tool.schema.object({
|
||||
type: tool.schema.literal("replace"),
|
||||
old_text: tool.schema.string().describe("Text to find"),
|
||||
new_text: tool.schema.string().describe("Replacement text"),
|
||||
new_text: tool.schema
|
||||
.union([tool.schema.string(), tool.schema.array(tool.schema.string())])
|
||||
.describe("Replacement text (string or string[] for multiline)"),
|
||||
}),
|
||||
])
|
||||
)
|
||||
@@ -154,7 +164,6 @@ Use \\n in text to represent literal newlines.`,
|
||||
await Bun.write(filePath, newContent)
|
||||
|
||||
const diff = generateDiff(oldContent, newContent, filePath)
|
||||
const oldHashlined = toHashlineContent(oldContent)
|
||||
const newHashlined = toHashlineContent(newContent)
|
||||
|
||||
const unifiedDiff = generateUnifiedDiff(oldContent, newContent, filePath)
|
||||
@@ -171,8 +180,8 @@ Use \\n in text to represent literal newlines.`,
|
||||
file: filePath,
|
||||
path: filePath,
|
||||
filePath,
|
||||
before: oldHashlined,
|
||||
after: newHashlined,
|
||||
before: oldContent,
|
||||
after: newContent,
|
||||
additions,
|
||||
deletions,
|
||||
},
|
||||
@@ -190,12 +199,12 @@ Use \\n in text to represent literal newlines.`,
|
||||
|
||||
${diff}
|
||||
|
||||
Updated file (LINE:HASH|content):
|
||||
Updated file (LINE#ID:content):
|
||||
${newHashlined}`
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
if (message.includes("hash")) {
|
||||
return `Error: Hash mismatch - ${message}\nTip: reuse LINE:HASH entries from the latest read/edit output, or batch related edits in one call.`
|
||||
if (message.toLowerCase().includes("hash")) {
|
||||
return `Error: hash mismatch - ${message}\nTip: reuse LINE#ID entries from the latest read/edit output, or batch related edits in one call.`
|
||||
}
|
||||
return `Error: ${message}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user