@withabdul/opencode-hashline
v0.2.0
Published
Compact hashline editing plugin for OpenCode with stale-edit protection and batch operations
Downloads
213
Maintainers
Readme
@withabdul/opencode-hashline
Compact hashline editing plugin for OpenCode. It reduces stale edits in multi-agent workflows while using fewer tokens than content-heavy anchors.
Bahasa Indonesia: README.id.md
Features
- Compact anchor format:
#HL {line}#{HASH} - Auto-tags native
readoutput via hook - Overrides built-in
editvia plugin - Overrides built-in
apply_patchvia plugin - Supports
*** Add File:inapply_patch - 5 edit operations:
insert_after,insert_before,replace,delete,replace_range - Batch edits in a single tool call
- Stale-edit detection when a file changes after being read
- Rich TUI diff metadata for
editandapply_patch
Problem
When multiple agents edit the same file, text-based replacement can become stale and corrupt newer changes. Native fuzzy matching works in many cases, but it can still fail when file contents shift.
Solution
Each line is tagged with a compact hashline reference:
#HL 1#A7B|fn main() {
#HL 2#3C4| let x = 42;
#HL 3#F1D| println!("{}", x);
#HL 4#B2E|}Edits then reference only the compact anchor, not the whole line content:
{
"operations": [
{
"op": "replace",
"startRef": "#HL 2#3C4",
"content": "return x + 1"
}
]
}If the file changed and the hash no longer matches, the edit is rejected with a stale-hash error.
Installation
From npm
Add this to opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@withabdul/opencode-hashline"]
}Or pin a version:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@withabdul/[email protected]"]
}No extra .opencode/tools files are required. The plugin overrides built-in edit and apply_patch directly from the package.
Local development
Build the plugin and load it from a local file:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["file:///absolute/path/to/opencode-plugin/dist/index.js"]
}Build
cd opencode-plugin
bun install
bun run test
bun run buildThe publishable bundle will be generated at dist/index.js.
Quick Start
- Read a file with native
read - Copy compact refs from the output, for example
#HL 12#9BC - Use
editfor direct operations orapply_patchfor multi-change patches
Direct edit example:
{
"filePath": "/absolute/path/to/file.ts",
"operations": [
{
"op": "replace",
"startRef": "#HL 2#9BC",
"content": "return x + 1"
},
{
"op": "insert_before",
"startRef": "#HL 1#A3F",
"content": "console.log(x)"
}
]
}Patch apply_patch example:
*** Begin Patch
*** Update File: /absolute/path/to/file.ts
*** Replace: #HL 2#9BC
+return x + 1
*** Insert Before: #HL 1#A3F
+console.log(x)
*** End PatchCreate-file apply_patch example:
*** Begin Patch
*** Add File: /absolute/path/to/new-file.ts
+export const answer = 42
+export const label = "hashline"
*** End PatchTools
edit
Overrides the built-in edit tool and accepts hashline operations.
apply_patch
Overrides the built-in apply_patch tool and accepts hashline patch directives.
Supported directives:
*** Add File:*** Update File:*** Replace:*** Insert Before:*** Insert After:*** Delete:*** Replace Range:
Notes:
*** Add File:creates a new file and writes the provided+lines as content.- Update directives require an existing file because hashline operations need anchor references.
- Content lines in patch bodies must start with
+.
Supported operations
insert_afterinsert_beforereplacedeletereplace_range
Insert after
{
"operations": [
{
"op": "insert_after",
"startRef": "#HL 2#3C4",
"content": " let y = x * 2;"
}
]
}Insert before
{
"operations": [
{
"op": "insert_before",
"startRef": "#HL 5#D1A",
"content": " console.log(x)"
}
]
}Replace line
{
"operations": [
{
"op": "replace",
"startRef": "#HL 10#B2A",
"content": " bar();"
}
]
}Delete range
{
"operations": [
{
"op": "delete",
"startRef": "#HL 15#C3D",
"endRef": "#HL 18#D4E"
}
]
}Replace range
{
"operations": [
{
"op": "replace_range",
"startRef": "#HL 20#E5F",
"endRef": "#HL 25#F6A",
"content": " new_block();"
}
]
}Batch edit
{
"operations": [
{
"op": "replace",
"startRef": "#HL 2#9BC",
"content": "return x + 1"
},
{
"op": "insert_before",
"startRef": "#HL 1#A3F",
"content": "console.log(x)"
}
]
}Routing
| Situation | Tool | Why |
|---|---|---|
| Create a new file from scratch | write | Best choice when you already know the full file content |
| Create a new file inside a patch flow | apply_patch with *** Add File: | Keeps related patch changes together |
| Replace an entire file | write | Full rewrite |
| Partial edit | edit | Built-in name + hashline semantics + rich diff metadata |
| Multi-change patch | apply_patch | Built-in name + hashline patch directives + rich diff metadata |
| Read before editing | read | Output is auto-tagged |
Hooks
| Hook | Purpose |
|---|---|
| tool | Registers edit and apply_patch |
| tool.execute.after | Auto-tags native read output |
| tool.execute.after | Injects diff metadata for edit and apply_patch |
| experimental.chat.system.transform | Injects hashline instructions |
| tool.execute.before | Caches before-state for diff metadata |
Project Structure
opencode-plugin/
├── package.json
├── README.md
├── README.id.md
├── src/
│ ├── index.ts
│ ├── hashline.ts
│ ├── hashline-patch.ts
│ ├── override-edit.ts
│ ├── override-apply-patch.ts
│ ├── read-output.ts
│ └── tool-metadata.tsPublishing to npm
cd opencode-plugin
bun install
bun run test
bun run build
npm login
npm publish --access publicBefore publishing, verify package contents:
npm packBenchmark
From the harness problem: hashline outperformed patch on 14/16 tested models.
License
MIT
