opencode-hashline 1.3.1 → 1.3.2
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-3ED7MDEC.js} +23 -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 +56 -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 +48 -22
- package/dist/utils.d.cts +2 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +1 -1
|
@@ -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,22 @@ 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
|
+
set = /* @__PURE__ */ new Set();
|
|
20
|
+
has(value) {
|
|
21
|
+
return this.set.has(value);
|
|
22
|
+
}
|
|
23
|
+
add(value) {
|
|
24
|
+
if (this.set.size >= this.maxSize) {
|
|
25
|
+
const first = this.set.values().next().value;
|
|
26
|
+
if (first !== void 0) this.set.delete(first);
|
|
22
27
|
}
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
}
|
|
28
|
+
this.set.add(value);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
27
31
|
var debugEnabled = false;
|
|
28
32
|
function setDebug(enabled) {
|
|
29
33
|
debugEnabled = enabled;
|
|
@@ -58,7 +62,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
58
62
|
const resolved = config ?? resolveConfig();
|
|
59
63
|
const hashLen = resolved.hashLength || 0;
|
|
60
64
|
const prefix = resolved.prefix;
|
|
61
|
-
const processedCallIds =
|
|
65
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
62
66
|
return async (input, output) => {
|
|
63
67
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
64
68
|
if (input.callID) {
|
|
@@ -67,6 +71,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
67
71
|
return;
|
|
68
72
|
}
|
|
69
73
|
processedCallIds.add(input.callID);
|
|
74
|
+
} else {
|
|
75
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
70
76
|
}
|
|
71
77
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
72
78
|
debug("skipped: not a file-read tool");
|
|
@@ -105,13 +111,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
105
111
|
function createFileEditBeforeHook(config) {
|
|
106
112
|
const resolved = config ?? resolveConfig();
|
|
107
113
|
const prefix = resolved.prefix;
|
|
108
|
-
const processedCallIds =
|
|
114
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
109
115
|
return async (input, output) => {
|
|
110
116
|
if (input.callID) {
|
|
111
117
|
if (processedCallIds.has(input.callID)) {
|
|
118
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
112
119
|
return;
|
|
113
120
|
}
|
|
114
121
|
processedCallIds.add(input.callID);
|
|
122
|
+
} else {
|
|
123
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
115
124
|
}
|
|
116
125
|
const toolName = input.tool.toLowerCase();
|
|
117
126
|
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,22 @@ 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
|
+
set = /* @__PURE__ */ new Set();
|
|
753
|
+
has(value) {
|
|
754
|
+
return this.set.has(value);
|
|
755
|
+
}
|
|
756
|
+
add(value) {
|
|
757
|
+
if (this.set.size >= this.maxSize) {
|
|
758
|
+
const first = this.set.values().next().value;
|
|
759
|
+
if (first !== void 0) this.set.delete(first);
|
|
737
760
|
}
|
|
738
|
-
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
}
|
|
761
|
+
this.set.add(value);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
742
764
|
var debugEnabled = false;
|
|
743
765
|
function setDebug(enabled) {
|
|
744
766
|
debugEnabled = enabled;
|
|
@@ -773,7 +795,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
773
795
|
const resolved = config ?? resolveConfig();
|
|
774
796
|
const hashLen = resolved.hashLength || 0;
|
|
775
797
|
const prefix = resolved.prefix;
|
|
776
|
-
const processedCallIds =
|
|
798
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
777
799
|
return async (input, output) => {
|
|
778
800
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
779
801
|
if (input.callID) {
|
|
@@ -782,6 +804,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
782
804
|
return;
|
|
783
805
|
}
|
|
784
806
|
processedCallIds.add(input.callID);
|
|
807
|
+
} else {
|
|
808
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
785
809
|
}
|
|
786
810
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
787
811
|
debug("skipped: not a file-read tool");
|
|
@@ -820,13 +844,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
820
844
|
function createFileEditBeforeHook(config) {
|
|
821
845
|
const resolved = config ?? resolveConfig();
|
|
822
846
|
const prefix = resolved.prefix;
|
|
823
|
-
const processedCallIds =
|
|
847
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
824
848
|
return async (input, output) => {
|
|
825
849
|
if (input.callID) {
|
|
826
850
|
if (processedCallIds.has(input.callID)) {
|
|
851
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
827
852
|
return;
|
|
828
853
|
}
|
|
829
854
|
processedCallIds.add(input.callID);
|
|
855
|
+
} else {
|
|
856
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
830
857
|
}
|
|
831
858
|
const toolName = input.tool.toLowerCase();
|
|
832
859
|
const isFileEdit = FILE_EDIT_TOOLS.some(
|
|
@@ -1110,9 +1137,7 @@ function sanitizeConfig(raw) {
|
|
|
1110
1137
|
const r = raw;
|
|
1111
1138
|
const result = {};
|
|
1112
1139
|
if (Array.isArray(r.exclude)) {
|
|
1113
|
-
result.exclude = r.exclude.filter(
|
|
1114
|
-
(p) => typeof p === "string" && p.length <= 512
|
|
1115
|
-
);
|
|
1140
|
+
result.exclude = r.exclude.filter((p) => typeof p === "string" && p.length <= 512).slice(0, 1e3);
|
|
1116
1141
|
}
|
|
1117
1142
|
if (typeof r.maxFileSize === "number" && Number.isFinite(r.maxFileSize) && r.maxFileSize >= 0) {
|
|
1118
1143
|
result.maxFileSize = r.maxFileSize;
|
|
@@ -1164,14 +1189,13 @@ function loadConfig(projectDir, userConfig) {
|
|
|
1164
1189
|
}
|
|
1165
1190
|
function createHashlinePlugin(userConfig) {
|
|
1166
1191
|
return async (input) => {
|
|
1167
|
-
const projectDir = input
|
|
1168
|
-
const worktree = input.worktree;
|
|
1192
|
+
const { directory: projectDir, worktree } = input;
|
|
1169
1193
|
const fileConfig = loadConfig(projectDir, userConfig);
|
|
1170
1194
|
const config = resolveConfig(fileConfig);
|
|
1171
1195
|
const cache = new HashlineCache(config.cacheSize);
|
|
1172
1196
|
setDebug(config.debug);
|
|
1173
|
-
const { appendFileSync: writeLog } = await import("fs");
|
|
1174
1197
|
const debugLog = (0, import_path3.join)((0, import_os2.homedir)(), ".config", "opencode", "hashline-debug.log");
|
|
1198
|
+
const writeLog = import_fs3.appendFileSync;
|
|
1175
1199
|
if (config.debug) {
|
|
1176
1200
|
try {
|
|
1177
1201
|
writeLog(debugLog, `[${(/* @__PURE__ */ new Date()).toISOString()}] plugin loaded, prefix: ${JSON.stringify(config.prefix)}, maxFileSize: ${config.maxFileSize}, projectDir: ${projectDir}
|
|
@@ -1264,5 +1288,6 @@ var src_default = HashlinePlugin;
|
|
|
1264
1288
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1265
1289
|
0 && (module.exports = {
|
|
1266
1290
|
HashlinePlugin,
|
|
1267
|
-
createHashlinePlugin
|
|
1291
|
+
createHashlinePlugin,
|
|
1292
|
+
sanitizeConfig
|
|
1268
1293
|
});
|
|
@@ -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-3ED7MDEC.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,22 @@ 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
|
+
set = /* @__PURE__ */ new Set();
|
|
734
|
+
has(value) {
|
|
735
|
+
return this.set.has(value);
|
|
736
|
+
}
|
|
737
|
+
add(value) {
|
|
738
|
+
if (this.set.size >= this.maxSize) {
|
|
739
|
+
const first = this.set.values().next().value;
|
|
740
|
+
if (first !== void 0) this.set.delete(first);
|
|
719
741
|
}
|
|
720
|
-
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
}
|
|
742
|
+
this.set.add(value);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
724
745
|
var debugEnabled = false;
|
|
725
746
|
function debug(...args) {
|
|
726
747
|
if (!debugEnabled) return;
|
|
@@ -752,7 +773,7 @@ function createFileReadAfterHook(cache, config) {
|
|
|
752
773
|
const resolved = config ?? resolveConfig();
|
|
753
774
|
const hashLen = resolved.hashLength || 0;
|
|
754
775
|
const prefix = resolved.prefix;
|
|
755
|
-
const processedCallIds =
|
|
776
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
756
777
|
return async (input, output) => {
|
|
757
778
|
debug("tool.execute.after:", input.tool, "args:", input.args);
|
|
758
779
|
if (input.callID) {
|
|
@@ -761,6 +782,8 @@ function createFileReadAfterHook(cache, config) {
|
|
|
761
782
|
return;
|
|
762
783
|
}
|
|
763
784
|
processedCallIds.add(input.callID);
|
|
785
|
+
} else {
|
|
786
|
+
debug("no callID \u2014 deduplication disabled for this call");
|
|
764
787
|
}
|
|
765
788
|
if (!isFileReadTool(input.tool, input.args)) {
|
|
766
789
|
debug("skipped: not a file-read tool");
|
|
@@ -799,13 +822,16 @@ function createFileReadAfterHook(cache, config) {
|
|
|
799
822
|
function createFileEditBeforeHook(config) {
|
|
800
823
|
const resolved = config ?? resolveConfig();
|
|
801
824
|
const prefix = resolved.prefix;
|
|
802
|
-
const processedCallIds =
|
|
825
|
+
const processedCallIds = new BoundedSet(MAX_PROCESSED_IDS);
|
|
803
826
|
return async (input, output) => {
|
|
804
827
|
if (input.callID) {
|
|
805
828
|
if (processedCallIds.has(input.callID)) {
|
|
829
|
+
debug("skipped: duplicate callID (edit)", input.callID);
|
|
806
830
|
return;
|
|
807
831
|
}
|
|
808
832
|
processedCallIds.add(input.callID);
|
|
833
|
+
} else {
|
|
834
|
+
debug("no callID \u2014 deduplication disabled for this edit call");
|
|
809
835
|
}
|
|
810
836
|
const toolName = input.tool.toLowerCase();
|
|
811
837
|
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-3ED7MDEC.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.2",
|
|
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",
|