fix(hashline): add autocorrect, batch mismatch reporting, and write anchors

This commit is contained in:
YeonGyu-Kim
2026-02-20 11:02:07 +09:00
parent f3e6cab2f8
commit 40dccd6118
8 changed files with 518 additions and 1168 deletions
+62 -15
View File
@@ -1,15 +1,21 @@
import type { PluginInput } from "@opencode-ai/plugin"
import { computeLineHash } from "../../tools/hashline-edit/hash-computation"
import { toHashlineContent } from "../../tools/hashline-edit/diff-utils"
interface HashlineReadEnhancerConfig {
hashline_edit?: { enabled: boolean }
}
const READ_LINE_PATTERN = /^(\d+): (.*)$/
const CONTENT_OPEN_TAG = "<content>"
const CONTENT_CLOSE_TAG = "</content>"
function isReadTool(toolName: string): boolean {
const lower = toolName.toLowerCase()
return lower === "read" || lower === "write"
return toolName.toLowerCase() === "read"
}
function isWriteTool(toolName: string): boolean {
return toolName.toLowerCase() === "write"
}
function shouldProcess(config: HashlineReadEnhancerConfig): boolean {
@@ -29,35 +35,73 @@ function transformLine(line: string): string {
const lineNumber = parseInt(match[1], 10)
const content = match[2]
const hash = computeLineHash(lineNumber, content)
return `${lineNumber}:${hash}|${content}`
return `${lineNumber}#${hash}:${content}`
}
function transformOutput(output: string): string {
if (!output) {
return output
}
if (!isTextFile(output)) {
const lines = output.split("\n")
const contentStart = lines.indexOf(CONTENT_OPEN_TAG)
const contentEnd = lines.indexOf(CONTENT_CLOSE_TAG)
if (contentStart === -1 || contentEnd === -1 || contentEnd <= contentStart + 1) {
return output
}
const lines = output.split("\n")
const fileLines = lines.slice(contentStart + 1, contentEnd)
if (!isTextFile(fileLines[0] ?? "")) {
return output
}
const result: string[] = []
for (const line of lines) {
for (const line of fileLines) {
if (!READ_LINE_PATTERN.test(line)) {
result.push(line)
result.push(...lines.slice(result.length))
result.push(...fileLines.slice(result.length))
break
}
result.push(transformLine(line))
}
return result.join("\n")
return [...lines.slice(0, contentStart + 1), ...result, ...lines.slice(contentEnd)].join("\n")
}
function transformWriteOutput(output: string): string {
if (!output) {
return output
function extractFilePath(metadata: unknown): string | undefined {
if (!metadata || typeof metadata !== "object") {
return undefined
}
const lines = output.split("\n")
return lines.map((line) => (READ_LINE_PATTERN.test(line) ? transformLine(line) : line)).join("\n")
const objectMeta = metadata as Record<string, unknown>
const candidates = [objectMeta.filepath, objectMeta.filePath, objectMeta.path, objectMeta.file]
for (const candidate of candidates) {
if (typeof candidate === "string" && candidate.length > 0) {
return candidate
}
}
return undefined
}
async function appendWriteHashlineOutput(output: { output: string; metadata: unknown }): Promise<void> {
if (output.output.includes("Updated file (LINE#ID:content):")) {
return
}
const filePath = extractFilePath(output.metadata)
if (!filePath) {
return
}
const file = Bun.file(filePath)
if (!(await file.exists())) {
return
}
const content = await file.text()
const hashlined = toHashlineContent(content)
output.output = `${output.output}\n\nUpdated file (LINE#ID:content):\n${hashlined}`
}
export function createHashlineReadEnhancerHook(
@@ -70,6 +114,9 @@ export function createHashlineReadEnhancerHook(
output: { title: string; output: string; metadata: unknown }
) => {
if (!isReadTool(input.tool)) {
if (isWriteTool(input.tool) && typeof output.output === "string" && shouldProcess(config)) {
await appendWriteHashlineOutput(output)
}
return
}
if (typeof output.output !== "string") {
@@ -78,7 +125,7 @@ export function createHashlineReadEnhancerHook(
if (!shouldProcess(config)) {
return
}
output.output = input.tool.toLowerCase() === "write" ? transformWriteOutput(output.output) : transformOutput(output.output)
output.output = transformOutput(output.output)
},
}
}