opencode-hashline 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +55 -6
- package/README.md +55 -6
- package/README.ru.md +7 -6
- package/dist/{chunk-I6RACR3D.js → chunk-GKXY5ZBM.js} +266 -57
- package/dist/{chunk-VPCMHCTB.js → chunk-VSVVWPET.js} +25 -3
- package/dist/{hashline-5PFAXY3H.js → hashline-37RYBX5A.js} +11 -1
- package/dist/{hashline-yhMw1Abs.d.ts → hashline-A7k2yn3G.d.cts} +70 -5
- package/dist/{hashline-yhMw1Abs.d.cts → hashline-A7k2yn3G.d.ts} +70 -5
- package/dist/opencode-hashline.cjs +342 -81
- package/dist/opencode-hashline.d.cts +2 -2
- package/dist/opencode-hashline.d.ts +2 -2
- package/dist/opencode-hashline.js +56 -25
- package/dist/utils.cjs +295 -59
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +12 -2
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import { H as HashlineConfig } from './hashline-
|
|
3
|
-
export { a as HashEditInput, b as HashEditOperation, c as HashEditResult, d as HashlineInstance, R as ResolvedRange, V as VerifyHashResult } from './hashline-
|
|
2
|
+
import { H as HashlineConfig } from './hashline-A7k2yn3G.cjs';
|
|
3
|
+
export { C as CandidateLine, a as HashEditInput, b as HashEditOperation, c as HashEditResult, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult } from './hashline-A7k2yn3G.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* opencode-hashline — Hashline plugin for OpenCode
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import { H as HashlineConfig } from './hashline-
|
|
3
|
-
export { a as HashEditInput, b as HashEditOperation, c as HashEditResult, d as HashlineInstance, R as ResolvedRange, V as VerifyHashResult } from './hashline-
|
|
2
|
+
import { H as HashlineConfig } from './hashline-A7k2yn3G.js';
|
|
3
|
+
export { C as CandidateLine, a as HashEditInput, b as HashEditOperation, c as HashEditResult, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult } from './hashline-A7k2yn3G.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* opencode-hashline — Hashline plugin for OpenCode
|
|
@@ -3,18 +3,20 @@ import {
|
|
|
3
3
|
createFileReadAfterHook,
|
|
4
4
|
createSystemPromptHook,
|
|
5
5
|
setDebug
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VSVVWPET.js";
|
|
7
7
|
import {
|
|
8
8
|
HashlineCache,
|
|
9
|
+
HashlineError,
|
|
9
10
|
applyHashEdit,
|
|
10
11
|
getByteLength,
|
|
11
12
|
resolveConfig
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-GKXY5ZBM.js";
|
|
13
14
|
|
|
14
15
|
// src/index.ts
|
|
15
|
-
import { readFileSync as readFileSync2, realpathSync as realpathSync2,
|
|
16
|
+
import { readFileSync as readFileSync2, realpathSync as realpathSync2, writeFileSync as writeFileSync2, mkdtempSync, openSync, closeSync, rmSync, constants as fsConstants } from "fs";
|
|
16
17
|
import { join, resolve as resolve2, sep as sep2 } from "path";
|
|
17
18
|
import { homedir, tmpdir } from "os";
|
|
19
|
+
import { randomBytes } from "crypto";
|
|
18
20
|
import { fileURLToPath } from "url";
|
|
19
21
|
|
|
20
22
|
// src/hashline-tool.ts
|
|
@@ -29,10 +31,12 @@ function createHashlineEditTool(config, cache) {
|
|
|
29
31
|
operation: z.enum(["replace", "delete", "insert_before", "insert_after"]).describe("Edit operation"),
|
|
30
32
|
startRef: z.string().describe('Start hash reference, e.g. "5:a3f" or "#HL 5:a3f|const x = 1;"'),
|
|
31
33
|
endRef: z.string().optional().describe("End hash reference for range operations. Defaults to startRef when omitted."),
|
|
32
|
-
replacement: z.string().max(1e7).optional().describe("Replacement/inserted content. Required for replace/insert operations.")
|
|
34
|
+
replacement: z.string().max(1e7).optional().describe("Replacement/inserted content. Required for replace/insert operations."),
|
|
35
|
+
fileRev: z.string().optional().describe("File revision hash (8-char hex from #HL REV:<hash>). When provided, verifies the file hasn't changed before editing."),
|
|
36
|
+
safeReapply: z.boolean().optional().describe("Enable safe reapply: if a line moved, attempt to find it by content hash. Fails on ambiguous matches.")
|
|
33
37
|
},
|
|
34
38
|
async execute(args, context) {
|
|
35
|
-
const { path, operation, startRef, endRef, replacement } = args;
|
|
39
|
+
const { path, operation, startRef, endRef, replacement, fileRev, safeReapply } = args;
|
|
36
40
|
const absPath = isAbsolute(path) ? path : resolve(context.directory, path);
|
|
37
41
|
const realDirectory = realpathSync(resolve(context.directory));
|
|
38
42
|
const realWorktree = realpathSync(resolve(context.worktree));
|
|
@@ -86,15 +90,21 @@ function createHashlineEditTool(config, cache) {
|
|
|
86
90
|
operation,
|
|
87
91
|
startRef,
|
|
88
92
|
endRef,
|
|
89
|
-
replacement
|
|
93
|
+
replacement,
|
|
94
|
+
fileRev
|
|
90
95
|
},
|
|
91
96
|
current,
|
|
92
|
-
config.hashLength || void 0
|
|
97
|
+
config.hashLength || void 0,
|
|
98
|
+
safeReapply ?? config.safeReapply
|
|
93
99
|
);
|
|
94
100
|
nextContent = result.content;
|
|
95
101
|
startLine = result.startLine;
|
|
96
102
|
endLine = result.endLine;
|
|
97
103
|
} catch (error) {
|
|
104
|
+
if (error instanceof HashlineError) {
|
|
105
|
+
throw new Error(`Hashline edit failed for "${displayPath}":
|
|
106
|
+
${error.toDiagnostic()}`);
|
|
107
|
+
}
|
|
98
108
|
const reason = error instanceof Error ? error.message : String(error);
|
|
99
109
|
throw new Error(`Hashline edit failed for "${displayPath}": ${reason}`);
|
|
100
110
|
}
|
|
@@ -131,6 +141,33 @@ function createHashlineEditTool(config, cache) {
|
|
|
131
141
|
|
|
132
142
|
// src/index.ts
|
|
133
143
|
var CONFIG_FILENAME = "opencode-hashline.json";
|
|
144
|
+
var tempDirs = /* @__PURE__ */ new Set();
|
|
145
|
+
var exitListenerRegistered = false;
|
|
146
|
+
function registerTempDir(dir) {
|
|
147
|
+
tempDirs.add(dir);
|
|
148
|
+
if (!exitListenerRegistered) {
|
|
149
|
+
exitListenerRegistered = true;
|
|
150
|
+
process.on("exit", () => {
|
|
151
|
+
for (const d of tempDirs) {
|
|
152
|
+
try {
|
|
153
|
+
rmSync(d, { recursive: true, force: true });
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function writeTempFile(tempDir, content) {
|
|
161
|
+
const name = `hl-${randomBytes(16).toString("hex")}.txt`;
|
|
162
|
+
const tmpPath = join(tempDir, name);
|
|
163
|
+
const fd = openSync(tmpPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
|
|
164
|
+
try {
|
|
165
|
+
writeFileSync2(fd, content, "utf-8");
|
|
166
|
+
} finally {
|
|
167
|
+
closeSync(fd);
|
|
168
|
+
}
|
|
169
|
+
return tmpPath;
|
|
170
|
+
}
|
|
134
171
|
function sanitizeConfig(raw) {
|
|
135
172
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
136
173
|
const r = raw;
|
|
@@ -159,6 +196,12 @@ function sanitizeConfig(raw) {
|
|
|
159
196
|
if (typeof r.debug === "boolean") {
|
|
160
197
|
result.debug = r.debug;
|
|
161
198
|
}
|
|
199
|
+
if (typeof r.fileRev === "boolean") {
|
|
200
|
+
result.fileRev = r.fileRev;
|
|
201
|
+
}
|
|
202
|
+
if (typeof r.safeReapply === "boolean") {
|
|
203
|
+
result.safeReapply = r.safeReapply;
|
|
204
|
+
}
|
|
162
205
|
return result;
|
|
163
206
|
}
|
|
164
207
|
function loadConfigFile(filePath) {
|
|
@@ -199,17 +242,8 @@ function createHashlinePlugin(userConfig) {
|
|
|
199
242
|
} catch {
|
|
200
243
|
}
|
|
201
244
|
}
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
for (const f of tempFiles) {
|
|
205
|
-
try {
|
|
206
|
-
unlinkSync(f);
|
|
207
|
-
} catch {
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
tempFiles.clear();
|
|
211
|
-
};
|
|
212
|
-
process.on("exit", cleanupTempFiles);
|
|
245
|
+
const instanceTmpDir = mkdtempSync(join(tmpdir(), "hashline-"));
|
|
246
|
+
registerTempDir(instanceTmpDir);
|
|
213
247
|
return {
|
|
214
248
|
tool: {
|
|
215
249
|
hashline_edit: createHashlineEditTool(config, cache)
|
|
@@ -222,7 +256,7 @@ function createHashlinePlugin(userConfig) {
|
|
|
222
256
|
const out = output;
|
|
223
257
|
const hashLen = config.hashLength || 0;
|
|
224
258
|
const prefix = config.prefix;
|
|
225
|
-
const { formatFileWithHashes, shouldExclude, getByteLength: getByteLength2 } = await import("./hashline-
|
|
259
|
+
const { formatFileWithHashes, shouldExclude, getByteLength: getByteLength2 } = await import("./hashline-37RYBX5A.js");
|
|
226
260
|
for (const p of out.parts ?? []) {
|
|
227
261
|
if (p.type !== "file") continue;
|
|
228
262
|
if (!p.url || !p.mime?.startsWith("text/")) continue;
|
|
@@ -252,9 +286,7 @@ function createHashlinePlugin(userConfig) {
|
|
|
252
286
|
if (config.maxFileSize > 0 && getByteLength2(content) > config.maxFileSize) continue;
|
|
253
287
|
const cached = cache.get(filePath, content);
|
|
254
288
|
if (cached) {
|
|
255
|
-
const tmpPath2 =
|
|
256
|
-
writeFileSync2(tmpPath2, cached, "utf-8");
|
|
257
|
-
tempFiles.add(tmpPath2);
|
|
289
|
+
const tmpPath2 = writeTempFile(instanceTmpDir, cached);
|
|
258
290
|
p.url = `file://${tmpPath2}`;
|
|
259
291
|
if (config.debug) {
|
|
260
292
|
try {
|
|
@@ -265,10 +297,9 @@ function createHashlinePlugin(userConfig) {
|
|
|
265
297
|
}
|
|
266
298
|
continue;
|
|
267
299
|
}
|
|
268
|
-
const annotated = formatFileWithHashes(content, hashLen || void 0, prefix);
|
|
300
|
+
const annotated = formatFileWithHashes(content, hashLen || void 0, prefix, config.fileRev);
|
|
269
301
|
cache.set(filePath, content, annotated);
|
|
270
|
-
const tmpPath =
|
|
271
|
-
writeFileSync2(tmpPath, annotated, "utf-8");
|
|
302
|
+
const tmpPath = writeTempFile(instanceTmpDir, annotated);
|
|
272
303
|
p.url = `file://${tmpPath}`;
|
|
273
304
|
if (config.debug) {
|
|
274
305
|
try {
|