opencode-hashline 1.3.1 → 1.3.3
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/dist/{chunk-VSVVWPET.js → chunk-2FSVSE7C.js} +24 -14
- package/dist/{chunk-GKXY5ZBM.js → chunk-C323JLG3.js} +26 -9
- package/dist/{hashline-37RYBX5A.js → hashline-DDPVX355.js} +1 -1
- package/dist/{hashline-A7k2yn3G.d.ts → hashline-DWndArr4.d.cts} +6 -1
- package/dist/{hashline-A7k2yn3G.d.cts → hashline-DWndArr4.d.ts} +6 -1
- package/dist/opencode-hashline.cjs +57 -31
- package/dist/opencode-hashline.d.cts +10 -3
- package/dist/opencode-hashline.d.ts +10 -3
- package/dist/opencode-hashline.js +9 -11
- package/dist/utils.cjs +49 -22
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
resolveConfig,
|
|
5
5
|
shouldExclude,
|
|
6
6
|
stripHashes
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-C323JLG3.js";
|
|
8
8
|
|
|
9
9
|
// src/hooks.ts
|
|
10
10
|
import { appendFileSync } from "fs";
|
|
@@ -12,18 +12,23 @@ import { join } from "path";
|
|
|
12
12
|
import { homedir } from "os";
|
|
13
13
|
var DEBUG_LOG = join(homedir(), ".config", "opencode", "hashline-debug.log");
|
|
14
14
|
var MAX_PROCESSED_IDS = 1e4;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
var BoundedSet = class {
|
|
16
|
+
constructor(maxSize) {
|
|
17
|
+
this.maxSize = maxSize;
|
|
18
|
+
}
|
|
19
|
+
maxSize;
|
|
20
|
+
set = /* @__PURE__ */ new Set();
|
|
21
|
+
has(value) {
|
|
22
|
+
return this.set.has(value);
|
|
23
|
+
}
|
|
24
|
+
add(value) {
|
|
25
|
+
if (this.set.size >= this.maxSize) {
|
|
26
|
+
const first = this.set.values().next().value;
|
|
27
|
+
if (first !== void 0) this.set.delete(first);
|
|
22
28
|
}
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
}
|
|
29
|
+
this.set.add(value);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
27
32
|
var debugEnabled = false;
|
|
28
33
|
function setDebug(enabled) {
|
|
29
34
|
debugEnabled = enabled;
|
|
@@ -58,7 +63,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
58
63
|
const resolved = config ?? resolveConfig();
|
|
59
64
|
const hashLen = resolved.hashLength || 0;
|
|
60
65
|
const prefix = resolved.prefix;
|
|
61
|
-
const processedCallIds =
|
|
66
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
62
67
|
return async (input, output) => {
|
|
63
68
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
64
69
|
if (input.callID) {
|
|
@@ -67,6 +72,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
67
72
|
return;
|
|
68
73
|
}
|
|
69
74
|
processedCallIds.add(input.callID);
|
|
75
|
+
} else {
|
|
76
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
70
77
|
}
|
|
71
78
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
72
79
|
debug("skipped: not a file-read tool");
|
|
@@ -105,13 +112,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
105
112
|
function createFileEditBeforeHook(config) {
|
|
106
113
|
const resolved = config ?? resolveConfig();
|
|
107
114
|
const prefix = resolved.prefix;
|
|
108
|
-
const processedCallIds =
|
|
115
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
109
116
|
return async (input, output) => {
|
|
110
117
|
if (input.callID) {
|
|
111
118
|
if (processedCallIds.has(input.callID)) {
|
|
119
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
112
120
|
return;
|
|
113
121
|
}
|
|
114
122
|
processedCallIds.add(input.callID);
|
|
123
|
+
} else {
|
|
124
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
115
125
|
}
|
|
116
126
|
const toolName = input.tool.toLowerCase();
|
|
117
127
|
const isFileEdit = FILE_EDIT_TOOLS.some(
|
|
@@ -204,6 +204,7 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
204
204
|
for (let idx = 0; idx < lines.length; idx++) {
|
|
205
205
|
hashes[idx] = computeLineHash(idx, lines[idx], effectiveLen);
|
|
206
206
|
}
|
|
207
|
+
let dirtyIndices = null;
|
|
207
208
|
let hasCollisions = true;
|
|
208
209
|
while (hasCollisions) {
|
|
209
210
|
hasCollisions = false;
|
|
@@ -217,16 +218,29 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
217
218
|
seen.set(h, [idx]);
|
|
218
219
|
}
|
|
219
220
|
}
|
|
221
|
+
const nextDirty = /* @__PURE__ */ new Set();
|
|
220
222
|
for (const [, group] of seen) {
|
|
221
223
|
if (group.length < 2) continue;
|
|
224
|
+
if (dirtyIndices !== null && !group.some((idx) => dirtyIndices.has(idx))) continue;
|
|
222
225
|
for (const idx of group) {
|
|
223
226
|
const newLen = Math.min(hashLens[idx] + 1, 8);
|
|
224
227
|
if (newLen === hashLens[idx]) continue;
|
|
225
228
|
hashLens[idx] = newLen;
|
|
226
229
|
hashes[idx] = computeLineHash(idx, lines[idx], newLen);
|
|
230
|
+
nextDirty.add(idx);
|
|
227
231
|
hasCollisions = true;
|
|
228
232
|
}
|
|
229
233
|
}
|
|
234
|
+
dirtyIndices = nextDirty;
|
|
235
|
+
}
|
|
236
|
+
const finalSeen = /* @__PURE__ */ new Map();
|
|
237
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
238
|
+
const existing = finalSeen.get(hashes[idx]);
|
|
239
|
+
if (existing !== void 0) {
|
|
240
|
+
hashes[idx] = `${hashes[idx]}${idx.toString(16)}`;
|
|
241
|
+
} else {
|
|
242
|
+
finalSeen.set(hashes[idx], idx);
|
|
243
|
+
}
|
|
230
244
|
}
|
|
231
245
|
const annotatedLines = lines.map((line, idx) => {
|
|
232
246
|
return `${effectivePrefix}${idx + 1}:${hashes[idx]}|${line}`;
|
|
@@ -241,12 +255,16 @@ var stripRegexCache = /* @__PURE__ */ new Map();
|
|
|
241
255
|
function stripHashes(content, prefix) {
|
|
242
256
|
const effectivePrefix = prefix === void 0 ? DEFAULT_PREFIX : prefix === false ? "" : prefix;
|
|
243
257
|
const escapedPrefix = effectivePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
244
|
-
let
|
|
245
|
-
if (!
|
|
246
|
-
|
|
247
|
-
|
|
258
|
+
let cached = stripRegexCache.get(escapedPrefix);
|
|
259
|
+
if (!cached) {
|
|
260
|
+
cached = {
|
|
261
|
+
hashLine: new RegExp(`^([+ \\-])?${escapedPrefix}\\d+:[0-9a-f]{2,8}\\|`),
|
|
262
|
+
rev: new RegExp(`^${escapedPrefix}REV:[0-9a-f]{8}$`)
|
|
263
|
+
};
|
|
264
|
+
stripRegexCache.set(escapedPrefix, cached);
|
|
248
265
|
}
|
|
249
|
-
const
|
|
266
|
+
const hashLinePattern = cached.hashLine;
|
|
267
|
+
const revPattern = cached.rev;
|
|
250
268
|
const lineEnding = detectLineEnding(content);
|
|
251
269
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
252
270
|
const result = normalized.split("\n").filter((line) => !revPattern.test(line)).map((line) => {
|
|
@@ -395,10 +413,10 @@ function resolveRange(startRef, endRef, content, hashLen, safeReapply) {
|
|
|
395
413
|
content: rangeLines.join(lineEnding)
|
|
396
414
|
};
|
|
397
415
|
}
|
|
398
|
-
function replaceRange(startRef, endRef, content, replacement, hashLen) {
|
|
416
|
+
function replaceRange(startRef, endRef, content, replacement, hashLen, safeReapply) {
|
|
399
417
|
const lineEnding = detectLineEnding(content);
|
|
400
418
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
401
|
-
const range = resolveRange(startRef, endRef, normalized, hashLen);
|
|
419
|
+
const range = resolveRange(startRef, endRef, normalized, hashLen, safeReapply);
|
|
402
420
|
const lines = normalized.split("\n");
|
|
403
421
|
const before = lines.slice(0, range.startLine - 1);
|
|
404
422
|
const after = lines.slice(range.endLine);
|
|
@@ -568,9 +586,8 @@ function matchesGlob(filePath, pattern) {
|
|
|
568
586
|
function shouldExclude(filePath, patterns) {
|
|
569
587
|
return patterns.some((pattern) => matchesGlob(filePath, pattern));
|
|
570
588
|
}
|
|
571
|
-
var textEncoder = new TextEncoder();
|
|
572
589
|
function getByteLength(content) {
|
|
573
|
-
return
|
|
590
|
+
return Buffer.byteLength(content, "utf-8");
|
|
574
591
|
}
|
|
575
592
|
function detectLineEnding(content) {
|
|
576
593
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -262,7 +262,7 @@ declare function resolveRange(startRef: string, endRef: string, content: string,
|
|
|
262
262
|
* @param hashLen - override hash length (0 or undefined = use hash.length from ref)
|
|
263
263
|
* @returns new file content with the range replaced
|
|
264
264
|
*/
|
|
265
|
-
declare function replaceRange(startRef: string, endRef: string, content: string, replacement: string, hashLen?: number): string;
|
|
265
|
+
declare function replaceRange(startRef: string, endRef: string, content: string, replacement: string, hashLen?: number, safeReapply?: boolean): string;
|
|
266
266
|
/**
|
|
267
267
|
* Apply a hash-aware edit operation directly against file content.
|
|
268
268
|
*
|
|
@@ -308,6 +308,11 @@ declare function matchesGlob(filePath: string, pattern: string): boolean;
|
|
|
308
308
|
* Check if a file path should be excluded based on config patterns.
|
|
309
309
|
*/
|
|
310
310
|
declare function shouldExclude(filePath: string, patterns: string[]): boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Get the UTF-8 byte length of a string.
|
|
313
|
+
* Uses TextEncoder for accurate UTF-8 byte counting.
|
|
314
|
+
* This correctly handles multi-byte characters (Cyrillic, CJK, emoji, etc.).
|
|
315
|
+
*/
|
|
311
316
|
declare function getByteLength(content: string): number;
|
|
312
317
|
/**
|
|
313
318
|
* A Hashline instance with custom configuration.
|
|
@@ -262,7 +262,7 @@ declare function resolveRange(startRef: string, endRef: string, content: string,
|
|
|
262
262
|
* @param hashLen - override hash length (0 or undefined = use hash.length from ref)
|
|
263
263
|
* @returns new file content with the range replaced
|
|
264
264
|
*/
|
|
265
|
-
declare function replaceRange(startRef: string, endRef: string, content: string, replacement: string, hashLen?: number): string;
|
|
265
|
+
declare function replaceRange(startRef: string, endRef: string, content: string, replacement: string, hashLen?: number, safeReapply?: boolean): string;
|
|
266
266
|
/**
|
|
267
267
|
* Apply a hash-aware edit operation directly against file content.
|
|
268
268
|
*
|
|
@@ -308,6 +308,11 @@ declare function matchesGlob(filePath: string, pattern: string): boolean;
|
|
|
308
308
|
* Check if a file path should be excluded based on config patterns.
|
|
309
309
|
*/
|
|
310
310
|
declare function shouldExclude(filePath: string, patterns: string[]): boolean;
|
|
311
|
+
/**
|
|
312
|
+
* Get the UTF-8 byte length of a string.
|
|
313
|
+
* Uses TextEncoder for accurate UTF-8 byte counting.
|
|
314
|
+
* This correctly handles multi-byte characters (Cyrillic, CJK, emoji, etc.).
|
|
315
|
+
*/
|
|
311
316
|
declare function getByteLength(content: string): number;
|
|
312
317
|
/**
|
|
313
318
|
* A Hashline instance with custom configuration.
|
|
@@ -159,6 +159,7 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
159
159
|
for (let idx = 0; idx < lines.length; idx++) {
|
|
160
160
|
hashes[idx] = computeLineHash(idx, lines[idx], effectiveLen);
|
|
161
161
|
}
|
|
162
|
+
let dirtyIndices = null;
|
|
162
163
|
let hasCollisions = true;
|
|
163
164
|
while (hasCollisions) {
|
|
164
165
|
hasCollisions = false;
|
|
@@ -172,16 +173,29 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
172
173
|
seen.set(h, [idx]);
|
|
173
174
|
}
|
|
174
175
|
}
|
|
176
|
+
const nextDirty = /* @__PURE__ */ new Set();
|
|
175
177
|
for (const [, group] of seen) {
|
|
176
178
|
if (group.length < 2) continue;
|
|
179
|
+
if (dirtyIndices !== null && !group.some((idx) => dirtyIndices.has(idx))) continue;
|
|
177
180
|
for (const idx of group) {
|
|
178
181
|
const newLen = Math.min(hashLens[idx] + 1, 8);
|
|
179
182
|
if (newLen === hashLens[idx]) continue;
|
|
180
183
|
hashLens[idx] = newLen;
|
|
181
184
|
hashes[idx] = computeLineHash(idx, lines[idx], newLen);
|
|
185
|
+
nextDirty.add(idx);
|
|
182
186
|
hasCollisions = true;
|
|
183
187
|
}
|
|
184
188
|
}
|
|
189
|
+
dirtyIndices = nextDirty;
|
|
190
|
+
}
|
|
191
|
+
const finalSeen = /* @__PURE__ */ new Map();
|
|
192
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
193
|
+
const existing = finalSeen.get(hashes[idx]);
|
|
194
|
+
if (existing !== void 0) {
|
|
195
|
+
hashes[idx] = `${hashes[idx]}${idx.toString(16)}`;
|
|
196
|
+
} else {
|
|
197
|
+
finalSeen.set(hashes[idx], idx);
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
const annotatedLines = lines.map((line, idx) => {
|
|
187
201
|
return `${effectivePrefix}${idx + 1}:${hashes[idx]}|${line}`;
|
|
@@ -195,12 +209,16 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
195
209
|
function stripHashes(content, prefix) {
|
|
196
210
|
const effectivePrefix = prefix === void 0 ? DEFAULT_PREFIX : prefix === false ? "" : prefix;
|
|
197
211
|
const escapedPrefix = effectivePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
198
|
-
let
|
|
199
|
-
if (!
|
|
200
|
-
|
|
201
|
-
|
|
212
|
+
let cached = stripRegexCache.get(escapedPrefix);
|
|
213
|
+
if (!cached) {
|
|
214
|
+
cached = {
|
|
215
|
+
hashLine: new RegExp(`^([+ \\-])?${escapedPrefix}\\d+:[0-9a-f]{2,8}\\|`),
|
|
216
|
+
rev: new RegExp(`^${escapedPrefix}REV:[0-9a-f]{8}$`)
|
|
217
|
+
};
|
|
218
|
+
stripRegexCache.set(escapedPrefix, cached);
|
|
202
219
|
}
|
|
203
|
-
const
|
|
220
|
+
const hashLinePattern = cached.hashLine;
|
|
221
|
+
const revPattern = cached.rev;
|
|
204
222
|
const lineEnding = detectLineEnding(content);
|
|
205
223
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
206
224
|
const result = normalized.split("\n").filter((line) => !revPattern.test(line)).map((line) => {
|
|
@@ -349,10 +367,10 @@ function resolveRange(startRef, endRef, content, hashLen, safeReapply) {
|
|
|
349
367
|
content: rangeLines.join(lineEnding)
|
|
350
368
|
};
|
|
351
369
|
}
|
|
352
|
-
function replaceRange(startRef, endRef, content, replacement, hashLen) {
|
|
370
|
+
function replaceRange(startRef, endRef, content, replacement, hashLen, safeReapply) {
|
|
353
371
|
const lineEnding = detectLineEnding(content);
|
|
354
372
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
355
|
-
const range = resolveRange(startRef, endRef, normalized, hashLen);
|
|
373
|
+
const range = resolveRange(startRef, endRef, normalized, hashLen, safeReapply);
|
|
356
374
|
const lines = normalized.split("\n");
|
|
357
375
|
const before = lines.slice(0, range.startLine - 1);
|
|
358
376
|
const after = lines.slice(range.endLine);
|
|
@@ -463,7 +481,7 @@ function shouldExclude(filePath, patterns) {
|
|
|
463
481
|
return patterns.some((pattern) => matchesGlob(filePath, pattern));
|
|
464
482
|
}
|
|
465
483
|
function getByteLength(content) {
|
|
466
|
-
return
|
|
484
|
+
return Buffer.byteLength(content, "utf-8");
|
|
467
485
|
}
|
|
468
486
|
function detectLineEnding(content) {
|
|
469
487
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -531,7 +549,7 @@ function createHashline(config) {
|
|
|
531
549
|
}
|
|
532
550
|
};
|
|
533
551
|
}
|
|
534
|
-
var import_picomatch, DEFAULT_EXCLUDE_PATTERNS, DEFAULT_PREFIX, DEFAULT_CONFIG, HashlineError, modulusCache, stripRegexCache, HashlineCache, globMatcherCache
|
|
552
|
+
var import_picomatch, DEFAULT_EXCLUDE_PATTERNS, DEFAULT_PREFIX, DEFAULT_CONFIG, HashlineError, modulusCache, stripRegexCache, HashlineCache, globMatcherCache;
|
|
535
553
|
var init_hashline = __esm({
|
|
536
554
|
"src/hashline.ts"() {
|
|
537
555
|
"use strict";
|
|
@@ -702,7 +720,6 @@ var init_hashline = __esm({
|
|
|
702
720
|
}
|
|
703
721
|
};
|
|
704
722
|
globMatcherCache = /* @__PURE__ */ new Map();
|
|
705
|
-
textEncoder = new TextEncoder();
|
|
706
723
|
}
|
|
707
724
|
});
|
|
708
725
|
|
|
@@ -711,7 +728,8 @@ var src_exports = {};
|
|
|
711
728
|
__export(src_exports, {
|
|
712
729
|
HashlinePlugin: () => HashlinePlugin,
|
|
713
730
|
createHashlinePlugin: () => createHashlinePlugin,
|
|
714
|
-
default: () => src_default
|
|
731
|
+
default: () => src_default,
|
|
732
|
+
sanitizeConfig: () => sanitizeConfig
|
|
715
733
|
});
|
|
716
734
|
module.exports = __toCommonJS(src_exports);
|
|
717
735
|
var import_fs3 = require("fs");
|
|
@@ -727,18 +745,23 @@ var import_os = require("os");
|
|
|
727
745
|
init_hashline();
|
|
728
746
|
var DEBUG_LOG = (0, import_path.join)((0, import_os.homedir)(), ".config", "opencode", "hashline-debug.log");
|
|
729
747
|
var MAX_PROCESSED_IDS = 1e4;
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
748
|
+
var BoundedSet = class {
|
|
749
|
+
constructor(maxSize) {
|
|
750
|
+
this.maxSize = maxSize;
|
|
751
|
+
}
|
|
752
|
+
maxSize;
|
|
753
|
+
set = /* @__PURE__ */ new Set();
|
|
754
|
+
has(value) {
|
|
755
|
+
return this.set.has(value);
|
|
756
|
+
}
|
|
757
|
+
add(value) {
|
|
758
|
+
if (this.set.size >= this.maxSize) {
|
|
759
|
+
const first = this.set.values().next().value;
|
|
760
|
+
if (first !== void 0) this.set.delete(first);
|
|
737
761
|
}
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
}
|
|
762
|
+
this.set.add(value);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
742
765
|
var debugEnabled = false;
|
|
743
766
|
function setDebug(enabled) {
|
|
744
767
|
debugEnabled = enabled;
|
|
@@ -773,7 +796,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
773
796
|
const resolved = config ?? resolveConfig();
|
|
774
797
|
const hashLen = resolved.hashLength || 0;
|
|
775
798
|
const prefix = resolved.prefix;
|
|
776
|
-
const processedCallIds =
|
|
799
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
777
800
|
return async (input, output) => {
|
|
778
801
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
779
802
|
if (input.callID) {
|
|
@@ -782,6 +805,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
782
805
|
return;
|
|
783
806
|
}
|
|
784
807
|
processedCallIds.add(input.callID);
|
|
808
|
+
} else {
|
|
809
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
785
810
|
}
|
|
786
811
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
787
812
|
debug("skipped: not a file-read tool");
|
|
@@ -820,13 +845,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
820
845
|
function createFileEditBeforeHook(config) {
|
|
821
846
|
const resolved = config ?? resolveConfig();
|
|
822
847
|
const prefix = resolved.prefix;
|
|
823
|
-
const processedCallIds =
|
|
848
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
824
849
|
return async (input, output) => {
|
|
825
850
|
if (input.callID) {
|
|
826
851
|
if (processedCallIds.has(input.callID)) {
|
|
852
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
827
853
|
return;
|
|
828
854
|
}
|
|
829
855
|
processedCallIds.add(input.callID);
|
|
856
|
+
} else {
|
|
857
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
830
858
|
}
|
|
831
859
|
const toolName = input.tool.toLowerCase();
|
|
832
860
|
const isFileEdit = FILE_EDIT_TOOLS.some(
|
|
@@ -1110,9 +1138,7 @@ function sanitizeConfig(raw) {
|
|
|
1110
1138
|
const r = raw;
|
|
1111
1139
|
const result = {};
|
|
1112
1140
|
if (Array.isArray(r.exclude)) {
|
|
1113
|
-
result.exclude = r.exclude.filter(
|
|
1114
|
-
(p) => typeof p === "string" && p.length <= 512
|
|
1115
|
-
);
|
|
1141
|
+
result.exclude = r.exclude.filter((p) => typeof p === "string" && p.length <= 512).slice(0, 1e3);
|
|
1116
1142
|
}
|
|
1117
1143
|
if (typeof r.maxFileSize === "number" && Number.isFinite(r.maxFileSize) && r.maxFileSize >= 0) {
|
|
1118
1144
|
result.maxFileSize = r.maxFileSize;
|
|
@@ -1164,14 +1190,13 @@ function loadConfig(projectDir, userConfig) {
|
|
|
1164
1190
|
}
|
|
1165
1191
|
function createHashlinePlugin(userConfig) {
|
|
1166
1192
|
return async (input) => {
|
|
1167
|
-
const projectDir = input
|
|
1168
|
-
const worktree = input.worktree;
|
|
1193
|
+
const { directory: projectDir, worktree } = input;
|
|
1169
1194
|
const fileConfig = loadConfig(projectDir, userConfig);
|
|
1170
1195
|
const config = resolveConfig(fileConfig);
|
|
1171
1196
|
const cache = new HashlineCache(config.cacheSize);
|
|
1172
1197
|
setDebug(config.debug);
|
|
1173
|
-
const { appendFileSync: writeLog } = await import("fs");
|
|
1174
1198
|
const debugLog = (0, import_path3.join)((0, import_os2.homedir)(), ".config", "opencode", "hashline-debug.log");
|
|
1199
|
+
const writeLog = import_fs3.appendFileSync;
|
|
1175
1200
|
if (config.debug) {
|
|
1176
1201
|
try {
|
|
1177
1202
|
writeLog(debugLog, `[${(/* @__PURE__ */ new Date()).toISOString()}] plugin loaded, prefix: ${JSON.stringify(config.prefix)}, maxFileSize: ${config.maxFileSize}, projectDir: ${projectDir}
|
|
@@ -1264,5 +1289,6 @@ var src_default = HashlinePlugin;
|
|
|
1264
1289
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1265
1290
|
0 && (module.exports = {
|
|
1266
1291
|
HashlinePlugin,
|
|
1267
|
-
createHashlinePlugin
|
|
1292
|
+
createHashlinePlugin,
|
|
1293
|
+
sanitizeConfig
|
|
1268
1294
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import { H as HashlineConfig } from './hashline-
|
|
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-
|
|
2
|
+
import { H as HashlineConfig } from './hashline-DWndArr4.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-DWndArr4.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* opencode-hashline — Hashline plugin for OpenCode
|
|
@@ -14,6 +14,13 @@ export { C as CandidateLine, a as HashEditInput, b as HashEditOperation, c as Ha
|
|
|
14
14
|
* constants, import from "opencode-hashline/utils".
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize and validate a raw parsed config object.
|
|
19
|
+
* Accepts only known keys with expected types; silently drops invalid values.
|
|
20
|
+
* This prevents prototype pollution, type confusion, and prompt injection via
|
|
21
|
+
* a malicious or hand-crafted config file.
|
|
22
|
+
*/
|
|
23
|
+
declare function sanitizeConfig(raw: unknown): HashlineConfig;
|
|
17
24
|
/**
|
|
18
25
|
* Create a Hashline plugin instance with optional user configuration.
|
|
19
26
|
*
|
|
@@ -45,4 +52,4 @@ declare function createHashlinePlugin(userConfig?: HashlineConfig): Plugin;
|
|
|
45
52
|
*/
|
|
46
53
|
declare const HashlinePlugin: Plugin;
|
|
47
54
|
|
|
48
|
-
export { HashlineConfig, HashlinePlugin, createHashlinePlugin, HashlinePlugin as default };
|
|
55
|
+
export { HashlineConfig, HashlinePlugin, createHashlinePlugin, HashlinePlugin as default, sanitizeConfig };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@opencode-ai/plugin';
|
|
2
|
-
import { H as HashlineConfig } from './hashline-
|
|
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-
|
|
2
|
+
import { H as HashlineConfig } from './hashline-DWndArr4.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-DWndArr4.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* opencode-hashline — Hashline plugin for OpenCode
|
|
@@ -14,6 +14,13 @@ export { C as CandidateLine, a as HashEditInput, b as HashEditOperation, c as Ha
|
|
|
14
14
|
* constants, import from "opencode-hashline/utils".
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize and validate a raw parsed config object.
|
|
19
|
+
* Accepts only known keys with expected types; silently drops invalid values.
|
|
20
|
+
* This prevents prototype pollution, type confusion, and prompt injection via
|
|
21
|
+
* a malicious or hand-crafted config file.
|
|
22
|
+
*/
|
|
23
|
+
declare function sanitizeConfig(raw: unknown): HashlineConfig;
|
|
17
24
|
/**
|
|
18
25
|
* Create a Hashline plugin instance with optional user configuration.
|
|
19
26
|
*
|
|
@@ -45,4 +52,4 @@ declare function createHashlinePlugin(userConfig?: HashlineConfig): Plugin;
|
|
|
45
52
|
*/
|
|
46
53
|
declare const HashlinePlugin: Plugin;
|
|
47
54
|
|
|
48
|
-
export { HashlineConfig, HashlinePlugin, createHashlinePlugin, HashlinePlugin as default };
|
|
55
|
+
export { HashlineConfig, HashlinePlugin, createHashlinePlugin, HashlinePlugin as default, sanitizeConfig };
|
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
createFileReadAfterHook,
|
|
4
4
|
createSystemPromptHook,
|
|
5
5
|
setDebug
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2FSVSE7C.js";
|
|
7
7
|
import {
|
|
8
8
|
HashlineCache,
|
|
9
9
|
HashlineError,
|
|
10
10
|
applyHashEdit,
|
|
11
11
|
getByteLength,
|
|
12
12
|
resolveConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-C323JLG3.js";
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
|
-
import { readFileSync as readFileSync2, realpathSync as realpathSync2, writeFileSync as writeFileSync2, mkdtempSync, openSync, closeSync, rmSync, constants as fsConstants } from "fs";
|
|
16
|
+
import { readFileSync as readFileSync2, realpathSync as realpathSync2, writeFileSync as writeFileSync2, appendFileSync, mkdtempSync, openSync, closeSync, rmSync, constants as fsConstants } from "fs";
|
|
17
17
|
import { join, resolve as resolve2, sep as sep2 } from "path";
|
|
18
18
|
import { homedir, tmpdir } from "os";
|
|
19
19
|
import { randomBytes } from "crypto";
|
|
@@ -173,9 +173,7 @@ function sanitizeConfig(raw) {
|
|
|
173
173
|
const r = raw;
|
|
174
174
|
const result = {};
|
|
175
175
|
if (Array.isArray(r.exclude)) {
|
|
176
|
-
result.exclude = r.exclude.filter(
|
|
177
|
-
(p) => typeof p === "string" && p.length <= 512
|
|
178
|
-
);
|
|
176
|
+
result.exclude = r.exclude.filter((p) => typeof p === "string" && p.length <= 512).slice(0, 1e3);
|
|
179
177
|
}
|
|
180
178
|
if (typeof r.maxFileSize === "number" && Number.isFinite(r.maxFileSize) && r.maxFileSize >= 0) {
|
|
181
179
|
result.maxFileSize = r.maxFileSize;
|
|
@@ -227,14 +225,13 @@ function loadConfig(projectDir, userConfig) {
|
|
|
227
225
|
}
|
|
228
226
|
function createHashlinePlugin(userConfig) {
|
|
229
227
|
return async (input) => {
|
|
230
|
-
const projectDir = input
|
|
231
|
-
const worktree = input.worktree;
|
|
228
|
+
const { directory: projectDir, worktree } = input;
|
|
232
229
|
const fileConfig = loadConfig(projectDir, userConfig);
|
|
233
230
|
const config = resolveConfig(fileConfig);
|
|
234
231
|
const cache = new HashlineCache(config.cacheSize);
|
|
235
232
|
setDebug(config.debug);
|
|
236
|
-
const { appendFileSync: writeLog } = await import("fs");
|
|
237
233
|
const debugLog = join(homedir(), ".config", "opencode", "hashline-debug.log");
|
|
234
|
+
const writeLog = appendFileSync;
|
|
238
235
|
if (config.debug) {
|
|
239
236
|
try {
|
|
240
237
|
writeLog(debugLog, `[${(/* @__PURE__ */ new Date()).toISOString()}] plugin loaded, prefix: ${JSON.stringify(config.prefix)}, maxFileSize: ${config.maxFileSize}, projectDir: ${projectDir}
|
|
@@ -256,7 +253,7 @@ function createHashlinePlugin(userConfig) {
|
|
|
256
253
|
const out = output;
|
|
257
254
|
const hashLen = config.hashLength || 0;
|
|
258
255
|
const prefix = config.prefix;
|
|
259
|
-
const { formatFileWithHashes, shouldExclude, getByteLength: getByteLength2 } = await import("./hashline-
|
|
256
|
+
const { formatFileWithHashes, shouldExclude, getByteLength: getByteLength2 } = await import("./hashline-DDPVX355.js");
|
|
260
257
|
for (const p of out.parts ?? []) {
|
|
261
258
|
if (p.type !== "file") continue;
|
|
262
259
|
if (!p.url || !p.mime?.startsWith("text/")) continue;
|
|
@@ -327,5 +324,6 @@ var src_default = HashlinePlugin;
|
|
|
327
324
|
export {
|
|
328
325
|
HashlinePlugin,
|
|
329
326
|
createHashlinePlugin,
|
|
330
|
-
src_default as default
|
|
327
|
+
src_default as default,
|
|
328
|
+
sanitizeConfig
|
|
331
329
|
};
|
package/dist/utils.cjs
CHANGED
|
@@ -268,6 +268,7 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
268
268
|
for (let idx = 0; idx < lines.length; idx++) {
|
|
269
269
|
hashes[idx] = computeLineHash(idx, lines[idx], effectiveLen);
|
|
270
270
|
}
|
|
271
|
+
let dirtyIndices = null;
|
|
271
272
|
let hasCollisions = true;
|
|
272
273
|
while (hasCollisions) {
|
|
273
274
|
hasCollisions = false;
|
|
@@ -281,16 +282,29 @@ function formatFileWithHashes(content, hashLen, prefix, includeFileRev) {
|
|
|
281
282
|
seen.set(h, [idx]);
|
|
282
283
|
}
|
|
283
284
|
}
|
|
285
|
+
const nextDirty = /* @__PURE__ */ new Set();
|
|
284
286
|
for (const [, group] of seen) {
|
|
285
287
|
if (group.length < 2) continue;
|
|
288
|
+
if (dirtyIndices !== null && !group.some((idx) => dirtyIndices.has(idx))) continue;
|
|
286
289
|
for (const idx of group) {
|
|
287
290
|
const newLen = Math.min(hashLens[idx] + 1, 8);
|
|
288
291
|
if (newLen === hashLens[idx]) continue;
|
|
289
292
|
hashLens[idx] = newLen;
|
|
290
293
|
hashes[idx] = computeLineHash(idx, lines[idx], newLen);
|
|
294
|
+
nextDirty.add(idx);
|
|
291
295
|
hasCollisions = true;
|
|
292
296
|
}
|
|
293
297
|
}
|
|
298
|
+
dirtyIndices = nextDirty;
|
|
299
|
+
}
|
|
300
|
+
const finalSeen = /* @__PURE__ */ new Map();
|
|
301
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
302
|
+
const existing = finalSeen.get(hashes[idx]);
|
|
303
|
+
if (existing !== void 0) {
|
|
304
|
+
hashes[idx] = `${hashes[idx]}${idx.toString(16)}`;
|
|
305
|
+
} else {
|
|
306
|
+
finalSeen.set(hashes[idx], idx);
|
|
307
|
+
}
|
|
294
308
|
}
|
|
295
309
|
const annotatedLines = lines.map((line, idx) => {
|
|
296
310
|
return `${effectivePrefix}${idx + 1}:${hashes[idx]}|${line}`;
|
|
@@ -305,12 +319,16 @@ var stripRegexCache = /* @__PURE__ */ new Map();
|
|
|
305
319
|
function stripHashes(content, prefix) {
|
|
306
320
|
const effectivePrefix = prefix === void 0 ? DEFAULT_PREFIX : prefix === false ? "" : prefix;
|
|
307
321
|
const escapedPrefix = effectivePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
308
|
-
let
|
|
309
|
-
if (!
|
|
310
|
-
|
|
311
|
-
|
|
322
|
+
let cached = stripRegexCache.get(escapedPrefix);
|
|
323
|
+
if (!cached) {
|
|
324
|
+
cached = {
|
|
325
|
+
hashLine: new RegExp(`^([+ \\-])?${escapedPrefix}\\d+:[0-9a-f]{2,8}\\|`),
|
|
326
|
+
rev: new RegExp(`^${escapedPrefix}REV:[0-9a-f]{8}$`)
|
|
327
|
+
};
|
|
328
|
+
stripRegexCache.set(escapedPrefix, cached);
|
|
312
329
|
}
|
|
313
|
-
const
|
|
330
|
+
const hashLinePattern = cached.hashLine;
|
|
331
|
+
const revPattern = cached.rev;
|
|
314
332
|
const lineEnding = detectLineEnding(content);
|
|
315
333
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
316
334
|
const result = normalized.split("\n").filter((line) => !revPattern.test(line)).map((line) => {
|
|
@@ -459,10 +477,10 @@ function resolveRange(startRef, endRef, content, hashLen, safeReapply) {
|
|
|
459
477
|
content: rangeLines.join(lineEnding)
|
|
460
478
|
};
|
|
461
479
|
}
|
|
462
|
-
function replaceRange(startRef, endRef, content, replacement, hashLen) {
|
|
480
|
+
function replaceRange(startRef, endRef, content, replacement, hashLen, safeReapply) {
|
|
463
481
|
const lineEnding = detectLineEnding(content);
|
|
464
482
|
const normalized = lineEnding === "\r\n" ? content.replace(/\r\n/g, "\n") : content;
|
|
465
|
-
const range = resolveRange(startRef, endRef, normalized, hashLen);
|
|
483
|
+
const range = resolveRange(startRef, endRef, normalized, hashLen, safeReapply);
|
|
466
484
|
const lines = normalized.split("\n");
|
|
467
485
|
const before = lines.slice(0, range.startLine - 1);
|
|
468
486
|
const after = lines.slice(range.endLine);
|
|
@@ -632,9 +650,8 @@ function matchesGlob(filePath, pattern) {
|
|
|
632
650
|
function shouldExclude(filePath, patterns) {
|
|
633
651
|
return patterns.some((pattern) => matchesGlob(filePath, pattern));
|
|
634
652
|
}
|
|
635
|
-
var textEncoder = new TextEncoder();
|
|
636
653
|
function getByteLength(content) {
|
|
637
|
-
return
|
|
654
|
+
return Buffer.byteLength(content, "utf-8");
|
|
638
655
|
}
|
|
639
656
|
function detectLineEnding(content) {
|
|
640
657
|
return content.includes("\r\n") ? "\r\n" : "\n";
|
|
@@ -709,18 +726,23 @@ var import_path = require("path");
|
|
|
709
726
|
var import_os = require("os");
|
|
710
727
|
var DEBUG_LOG = (0, import_path.join)((0, import_os.homedir)(), ".config", "opencode", "hashline-debug.log");
|
|
711
728
|
var MAX_PROCESSED_IDS = 1e4;
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
729
|
+
var BoundedSet = class {
|
|
730
|
+
constructor(maxSize) {
|
|
731
|
+
this.maxSize = maxSize;
|
|
732
|
+
}
|
|
733
|
+
maxSize;
|
|
734
|
+
set = /* @__PURE__ */ new Set();
|
|
735
|
+
has(value) {
|
|
736
|
+
return this.set.has(value);
|
|
737
|
+
}
|
|
738
|
+
add(value) {
|
|
739
|
+
if (this.set.size >= this.maxSize) {
|
|
740
|
+
const first = this.set.values().next().value;
|
|
741
|
+
if (first !== void 0) this.set.delete(first);
|
|
719
742
|
}
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
}
|
|
743
|
+
this.set.add(value);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
724
746
|
var debugEnabled = false;
|
|
725
747
|
function debug(...args) {
|
|
726
748
|
if (!debugEnabled) return;
|
|
@@ -752,7 +774,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
752
774
|
const resolved = config ?? resolveConfig();
|
|
753
775
|
const hashLen = resolved.hashLength || 0;
|
|
754
776
|
const prefix = resolved.prefix;
|
|
755
|
-
const processedCallIds =
|
|
777
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
756
778
|
return async (input, output) => {
|
|
757
779
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
758
780
|
if (input.callID) {
|
|
@@ -761,6 +783,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
761
783
|
return;
|
|
762
784
|
}
|
|
763
785
|
processedCallIds.add(input.callID);
|
|
786
|
+
} else {
|
|
787
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
764
788
|
}
|
|
765
789
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
766
790
|
debug("skipped: not a file-read tool");
|
|
@@ -799,13 +823,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
799
823
|
function createFileEditBeforeHook(config) {
|
|
800
824
|
const resolved = config ?? resolveConfig();
|
|
801
825
|
const prefix = resolved.prefix;
|
|
802
|
-
const processedCallIds =
|
|
826
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
803
827
|
return async (input, output) => {
|
|
804
828
|
if (input.callID) {
|
|
805
829
|
if (processedCallIds.has(input.callID)) {
|
|
830
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
806
831
|
return;
|
|
807
832
|
}
|
|
808
833
|
processedCallIds.add(input.callID);
|
|
834
|
+
} else {
|
|
835
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
809
836
|
}
|
|
810
837
|
const toolName = input.tool.toLowerCase();
|
|
811
838
|
const isFileEdit = FILE_EDIT_TOOLS.some(
|
package/dist/utils.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HashlineConfig, f as HashlineCache } from './hashline-
|
|
2
|
-
export { C as CandidateLine, D as DEFAULT_CONFIG, g as DEFAULT_EXCLUDE_PATTERNS, h as DEFAULT_PREFIX, a as HashEditInput, b as HashEditOperation, c as HashEditResult, i as HashlineError, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult, j as applyHashEdit, k as buildHashMap, l as computeFileRev, m as computeLineHash, n as createHashline, o as extractFileRev, p as findCandidateLines, q as formatFileWithHashes, r as getAdaptiveHashLength, s as getByteLength, t as matchesGlob, u as normalizeHashRef, v as parseHashRef, w as replaceRange, x as resolveConfig, y as resolveRange, z as shouldExclude, A as stripHashes, B as verifyFileRev, E as verifyHash } from './hashline-
|
|
1
|
+
import { H as HashlineConfig, f as HashlineCache } from './hashline-DWndArr4.cjs';
|
|
2
|
+
export { C as CandidateLine, D as DEFAULT_CONFIG, g as DEFAULT_EXCLUDE_PATTERNS, h as DEFAULT_PREFIX, a as HashEditInput, b as HashEditOperation, c as HashEditResult, i as HashlineError, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult, j as applyHashEdit, k as buildHashMap, l as computeFileRev, m as computeLineHash, n as createHashline, o as extractFileRev, p as findCandidateLines, q as formatFileWithHashes, r as getAdaptiveHashLength, s as getByteLength, t as matchesGlob, u as normalizeHashRef, v as parseHashRef, w as replaceRange, x as resolveConfig, y as resolveRange, z as shouldExclude, A as stripHashes, B as verifyFileRev, E as verifyHash } from './hashline-DWndArr4.cjs';
|
|
3
3
|
import { Hooks } from '@opencode-ai/plugin';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HashlineConfig, f as HashlineCache } from './hashline-
|
|
2
|
-
export { C as CandidateLine, D as DEFAULT_CONFIG, g as DEFAULT_EXCLUDE_PATTERNS, h as DEFAULT_PREFIX, a as HashEditInput, b as HashEditOperation, c as HashEditResult, i as HashlineError, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult, j as applyHashEdit, k as buildHashMap, l as computeFileRev, m as computeLineHash, n as createHashline, o as extractFileRev, p as findCandidateLines, q as formatFileWithHashes, r as getAdaptiveHashLength, s as getByteLength, t as matchesGlob, u as normalizeHashRef, v as parseHashRef, w as replaceRange, x as resolveConfig, y as resolveRange, z as shouldExclude, A as stripHashes, B as verifyFileRev, E as verifyHash } from './hashline-
|
|
1
|
+
import { H as HashlineConfig, f as HashlineCache } from './hashline-DWndArr4.js';
|
|
2
|
+
export { C as CandidateLine, D as DEFAULT_CONFIG, g as DEFAULT_EXCLUDE_PATTERNS, h as DEFAULT_PREFIX, a as HashEditInput, b as HashEditOperation, c as HashEditResult, i as HashlineError, d as HashlineErrorCode, e as HashlineInstance, R as ResolvedRange, V as VerifyHashResult, j as applyHashEdit, k as buildHashMap, l as computeFileRev, m as computeLineHash, n as createHashline, o as extractFileRev, p as findCandidateLines, q as formatFileWithHashes, r as getAdaptiveHashLength, s as getByteLength, t as matchesGlob, u as normalizeHashRef, v as parseHashRef, w as replaceRange, x as resolveConfig, y as resolveRange, z as shouldExclude, A as stripHashes, B as verifyFileRev, E as verifyHash } from './hashline-DWndArr4.js';
|
|
3
3
|
import { Hooks } from '@opencode-ai/plugin';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createFileReadAfterHook,
|
|
4
4
|
createSystemPromptHook,
|
|
5
5
|
isFileReadTool
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-2FSVSE7C.js";
|
|
7
7
|
import {
|
|
8
8
|
DEFAULT_CONFIG,
|
|
9
9
|
DEFAULT_EXCLUDE_PATTERNS,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
stripHashes,
|
|
31
31
|
verifyFileRev,
|
|
32
32
|
verifyHash
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-C323JLG3.js";
|
|
34
34
|
export {
|
|
35
35
|
DEFAULT_CONFIG,
|
|
36
36
|
DEFAULT_EXCLUDE_PATTERNS,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-hashline",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Hashline plugin for OpenCode — content-addressable line hashing for precise AI code editing",
|
|
5
5
|
"main": "dist/opencode-hashline.cjs",
|
|
6
6
|
"module": "dist/opencode-hashline.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"tsup": "^8.5.1",
|
|
54
54
|
"typescript": "^5.9.3",
|
|
55
55
|
"vitest": "^4.0.18",
|
|
56
|
-
"zod": "
|
|
56
|
+
"zod": "~4.1.8"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"picomatch": "^4.0.3"
|