opencode-hashline 1.0.0
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/LICENSE +21 -0
- package/README.md +442 -0
- package/README.ru.md +417 -0
- package/dist/chunk-C2EVIAGV.js +177 -0
- package/dist/chunk-IVZSANZ4.js +411 -0
- package/dist/hashline-Civwirvf.d.cts +278 -0
- package/dist/hashline-Civwirvf.d.ts +278 -0
- package/dist/hashline-W2FT5QN4.js +44 -0
- package/dist/index.cjs +811 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +197 -0
- package/dist/utils.cjs +637 -0
- package/dist/utils.d.cts +74 -0
- package/dist/utils.d.ts +74 -0
- package/dist/utils.js +54 -0
- package/package.json +56 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core Hashline logic — content-addressable line hashing for precise AI code editing.
|
|
3
|
+
*
|
|
4
|
+
* Each line gets a hex hash tag derived from its index and trimmed content.
|
|
5
|
+
* Hash length adapts to file size: 3 chars (≤4096 lines), 4 chars (>4096).
|
|
6
|
+
* Minimum hash length is 3 to reduce collision risk.
|
|
7
|
+
* Format: `#HL <lineNumber>:<hash>|<originalLine>`
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* #HL 1:a3f|function hello() {
|
|
11
|
+
* #HL 2:f1c| return "world";
|
|
12
|
+
* #HL 3:0e7|}
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Configuration object for Hashline.
|
|
16
|
+
*/
|
|
17
|
+
interface HashlineConfig {
|
|
18
|
+
/** Glob patterns to exclude from processing */
|
|
19
|
+
exclude?: string[];
|
|
20
|
+
/** Maximum file size in bytes to process (default: 1MB) */
|
|
21
|
+
maxFileSize?: number;
|
|
22
|
+
/** Override hash length (3–4). If not set, adaptive length is used. */
|
|
23
|
+
hashLength?: number;
|
|
24
|
+
/** LRU cache size — number of files to cache (default: 100) */
|
|
25
|
+
cacheSize?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Magic prefix for hashline annotations.
|
|
28
|
+
* Default: "#HL " — lines are formatted as `#HL 1:a3f|code here`.
|
|
29
|
+
* Set to `false` to disable prefix (legacy format: `1:a3f|code here`).
|
|
30
|
+
*/
|
|
31
|
+
prefix?: string | false;
|
|
32
|
+
}
|
|
33
|
+
/** Default exclude patterns */
|
|
34
|
+
declare const DEFAULT_EXCLUDE_PATTERNS: string[];
|
|
35
|
+
/** Default prefix for hashline annotations */
|
|
36
|
+
declare const DEFAULT_PREFIX = "#HL ";
|
|
37
|
+
/** Default configuration values */
|
|
38
|
+
declare const DEFAULT_CONFIG: Required<HashlineConfig>;
|
|
39
|
+
/**
|
|
40
|
+
* Merge user config with defaults.
|
|
41
|
+
*
|
|
42
|
+
* @param config - optional partial user config
|
|
43
|
+
* @param pluginConfig - optional config from plugin context (e.g. opencode.json)
|
|
44
|
+
*/
|
|
45
|
+
declare function resolveConfig(config?: HashlineConfig, pluginConfig?: HashlineConfig): Required<HashlineConfig>;
|
|
46
|
+
/**
|
|
47
|
+
* Determine the appropriate hash length based on the number of lines.
|
|
48
|
+
*
|
|
49
|
+
* Minimum hash length is 3 to reduce collision risk.
|
|
50
|
+
* - ≤4096 lines → 3 hex chars (4096 values)
|
|
51
|
+
* - >4096 lines → 4 hex chars (65536 values)
|
|
52
|
+
*/
|
|
53
|
+
declare function getAdaptiveHashLength(lineCount: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* Compute a hex hash for a given line.
|
|
56
|
+
*
|
|
57
|
+
* Uses trimEnd() so that leading whitespace (indentation) IS significant,
|
|
58
|
+
* but trailing whitespace is ignored.
|
|
59
|
+
*
|
|
60
|
+
* @param idx - 0-based line index
|
|
61
|
+
* @param line - the raw line content
|
|
62
|
+
* @param hashLen - number of hex characters (3–4, default 3)
|
|
63
|
+
* @returns lowercase hex string of the specified length
|
|
64
|
+
*/
|
|
65
|
+
declare function computeLineHash(idx: number, line: string, hashLen?: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* Format file content with hashline annotations.
|
|
68
|
+
*
|
|
69
|
+
* Each line becomes: `<prefix><1-based lineNumber>:<hash>|<originalLine>`
|
|
70
|
+
* Hash length adapts to file size unless overridden.
|
|
71
|
+
* Includes collision detection: if two lines produce the same hash,
|
|
72
|
+
* the colliding line gets a longer hash.
|
|
73
|
+
*
|
|
74
|
+
* @param content - raw file content
|
|
75
|
+
* @param hashLen - override hash length (0 or undefined = adaptive)
|
|
76
|
+
* @param prefix - prefix string (default "#HL "), or false to disable
|
|
77
|
+
* @returns annotated content with hash prefixes
|
|
78
|
+
*/
|
|
79
|
+
declare function formatFileWithHashes(content: string, hashLen?: number, prefix?: string | false): string;
|
|
80
|
+
/**
|
|
81
|
+
* Strip hashline prefixes to recover original file content.
|
|
82
|
+
*
|
|
83
|
+
* Recognizes the pattern `<prefix><number>:<2-8 hex>|` at the start of each line.
|
|
84
|
+
* By default looks for the `#HL ` prefix to avoid false positives.
|
|
85
|
+
*
|
|
86
|
+
* @param content - hashline-annotated content
|
|
87
|
+
* @param prefix - prefix string (default "#HL "), or false for legacy format
|
|
88
|
+
* @returns original content without hash prefixes
|
|
89
|
+
*/
|
|
90
|
+
declare function stripHashes(content: string, prefix?: string | false): string;
|
|
91
|
+
/**
|
|
92
|
+
* Parse a hash reference like "2:f1a" or "2:f1a3" into its components.
|
|
93
|
+
*
|
|
94
|
+
* @param ref - reference string in the format "<lineNumber>:<hash>"
|
|
95
|
+
* @returns parsed line number (1-based) and hash string
|
|
96
|
+
*/
|
|
97
|
+
declare function parseHashRef(ref: string): {
|
|
98
|
+
line: number;
|
|
99
|
+
hash: string;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Normalize a hash reference.
|
|
103
|
+
*
|
|
104
|
+
* Accepts:
|
|
105
|
+
* - plain refs: `2:f1c`
|
|
106
|
+
* - annotated refs: `#HL 2:f1c|line content` or `2:f1c|line content`
|
|
107
|
+
*
|
|
108
|
+
* Returns canonical lowercased format: `<line>:<hash>`
|
|
109
|
+
*/
|
|
110
|
+
declare function normalizeHashRef(ref: string): string;
|
|
111
|
+
/**
|
|
112
|
+
* Supported hash-aware edit operations.
|
|
113
|
+
*/
|
|
114
|
+
type HashEditOperation = "replace" | "delete" | "insert_before" | "insert_after";
|
|
115
|
+
/**
|
|
116
|
+
* Input for hash-aware edit application.
|
|
117
|
+
*/
|
|
118
|
+
interface HashEditInput {
|
|
119
|
+
operation: HashEditOperation;
|
|
120
|
+
startRef: string;
|
|
121
|
+
endRef?: string;
|
|
122
|
+
replacement?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Result of applying a hash-aware edit.
|
|
126
|
+
*/
|
|
127
|
+
interface HashEditResult {
|
|
128
|
+
operation: HashEditOperation;
|
|
129
|
+
startLine: number;
|
|
130
|
+
endLine: number;
|
|
131
|
+
content: string;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build a mapping from hash tags to 1-based line numbers.
|
|
135
|
+
*
|
|
136
|
+
* Note: If multiple lines produce the same hash, the last one wins.
|
|
137
|
+
* In practice, collisions are rare since the hash incorporates the line index.
|
|
138
|
+
*
|
|
139
|
+
* @param content - raw file content (without hash prefixes)
|
|
140
|
+
* @param hashLen - override hash length (0 or undefined = adaptive)
|
|
141
|
+
* @returns Map from "<lineNumber>:<hash>" to 1-based line number
|
|
142
|
+
*/
|
|
143
|
+
declare function buildHashMap(content: string, hashLen?: number): Map<string, number>;
|
|
144
|
+
/**
|
|
145
|
+
* Result of hash verification.
|
|
146
|
+
*/
|
|
147
|
+
interface VerifyHashResult {
|
|
148
|
+
valid: boolean;
|
|
149
|
+
expected?: string;
|
|
150
|
+
actual?: string;
|
|
151
|
+
message?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Verify that a line's hash matches the current content.
|
|
155
|
+
*
|
|
156
|
+
* This protects against race conditions — if the file changed between
|
|
157
|
+
* read and edit, the hash won't match.
|
|
158
|
+
*
|
|
159
|
+
* The hash length is determined from the provided hash string itself
|
|
160
|
+
* (hash.length), not from the current file size. This ensures that
|
|
161
|
+
* a reference like "2:f1a" remains valid even if the file has grown.
|
|
162
|
+
*
|
|
163
|
+
* @param lineNumber - 1-based line number
|
|
164
|
+
* @param hash - expected hash from the hash reference
|
|
165
|
+
* @param currentContent - current raw file content (string or pre-split lines)
|
|
166
|
+
* @param hashLen - override hash length (0 or undefined = use hash.length from ref)
|
|
167
|
+
* @param lines - optional pre-split lines array to avoid re-splitting
|
|
168
|
+
* @returns verification result
|
|
169
|
+
*/
|
|
170
|
+
declare function verifyHash(lineNumber: number, hash: string, currentContent: string, hashLen?: number, lines?: string[]): VerifyHashResult;
|
|
171
|
+
/**
|
|
172
|
+
* Result of a range resolution.
|
|
173
|
+
*/
|
|
174
|
+
interface ResolvedRange {
|
|
175
|
+
startLine: number;
|
|
176
|
+
endLine: number;
|
|
177
|
+
lines: string[];
|
|
178
|
+
content: string;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Resolve a range of lines by hash references.
|
|
182
|
+
* Splits content once and passes lines array to verifyHash to avoid redundant splits.
|
|
183
|
+
*
|
|
184
|
+
* @param startRef - start hash reference (e.g. "1:a3f")
|
|
185
|
+
* @param endRef - end hash reference (e.g. "3:0e7")
|
|
186
|
+
* @param content - raw file content
|
|
187
|
+
* @param hashLen - override hash length (0 or undefined = use hash.length from ref)
|
|
188
|
+
* @returns resolved range with line numbers and content
|
|
189
|
+
*/
|
|
190
|
+
declare function resolveRange(startRef: string, endRef: string, content: string, hashLen?: number): ResolvedRange;
|
|
191
|
+
/**
|
|
192
|
+
* Replace a range of lines identified by hash references with new content.
|
|
193
|
+
* Splits content once and reuses the lines array.
|
|
194
|
+
*
|
|
195
|
+
* @param startRef - start hash reference
|
|
196
|
+
* @param endRef - end hash reference
|
|
197
|
+
* @param content - current raw file content
|
|
198
|
+
* @param replacement - new content to replace the range with
|
|
199
|
+
* @param hashLen - override hash length (0 or undefined = use hash.length from ref)
|
|
200
|
+
* @returns new file content with the range replaced
|
|
201
|
+
*/
|
|
202
|
+
declare function replaceRange(startRef: string, endRef: string, content: string, replacement: string, hashLen?: number): string;
|
|
203
|
+
/**
|
|
204
|
+
* Apply a hash-aware edit operation directly against file content.
|
|
205
|
+
*
|
|
206
|
+
* Unlike search/replace tools, this resolves references by line+hash and
|
|
207
|
+
* verifies them before editing, so exact old-string matching is not required.
|
|
208
|
+
*/
|
|
209
|
+
declare function applyHashEdit(input: HashEditInput, content: string, hashLen?: number): HashEditResult;
|
|
210
|
+
/**
|
|
211
|
+
* Simple LRU cache for annotated file content.
|
|
212
|
+
*/
|
|
213
|
+
declare class HashlineCache {
|
|
214
|
+
private cache;
|
|
215
|
+
private maxSize;
|
|
216
|
+
constructor(maxSize?: number);
|
|
217
|
+
/**
|
|
218
|
+
* Get cached annotated content for a file, or null if not cached / stale.
|
|
219
|
+
*/
|
|
220
|
+
get(filePath: string, content: string): string | null;
|
|
221
|
+
/**
|
|
222
|
+
* Store annotated content in the cache.
|
|
223
|
+
*/
|
|
224
|
+
set(filePath: string, content: string, annotated: string): void;
|
|
225
|
+
/**
|
|
226
|
+
* Invalidate a specific file from the cache.
|
|
227
|
+
*/
|
|
228
|
+
invalidate(filePath: string): void;
|
|
229
|
+
/**
|
|
230
|
+
* Clear the entire cache.
|
|
231
|
+
*/
|
|
232
|
+
clear(): void;
|
|
233
|
+
/**
|
|
234
|
+
* Get the current number of cached entries.
|
|
235
|
+
*/
|
|
236
|
+
get size(): number;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Glob matcher using picomatch for full glob support.
|
|
240
|
+
* Supports `*`, `**`, `?`, `{a,b}`, `[abc]`, and all standard glob patterns.
|
|
241
|
+
* Windows paths are normalized to forward slashes.
|
|
242
|
+
*/
|
|
243
|
+
declare function matchesGlob(filePath: string, pattern: string): boolean;
|
|
244
|
+
/**
|
|
245
|
+
* Check if a file path should be excluded based on config patterns.
|
|
246
|
+
*/
|
|
247
|
+
declare function shouldExclude(filePath: string, patterns: string[]): boolean;
|
|
248
|
+
declare function getByteLength(content: string): number;
|
|
249
|
+
/**
|
|
250
|
+
* A Hashline instance with custom configuration.
|
|
251
|
+
*/
|
|
252
|
+
interface HashlineInstance {
|
|
253
|
+
config: Required<HashlineConfig>;
|
|
254
|
+
cache: HashlineCache;
|
|
255
|
+
formatFileWithHashes: (content: string, filePath?: string) => string;
|
|
256
|
+
stripHashes: (content: string) => string;
|
|
257
|
+
computeLineHash: (idx: number, line: string) => string;
|
|
258
|
+
buildHashMap: (content: string) => Map<string, number>;
|
|
259
|
+
verifyHash: (lineNumber: number, hash: string, currentContent: string) => VerifyHashResult;
|
|
260
|
+
resolveRange: (startRef: string, endRef: string, content: string) => ResolvedRange;
|
|
261
|
+
replaceRange: (startRef: string, endRef: string, content: string, replacement: string) => string;
|
|
262
|
+
applyHashEdit: (input: HashEditInput, content: string) => HashEditResult;
|
|
263
|
+
normalizeHashRef: (ref: string) => string;
|
|
264
|
+
parseHashRef: (ref: string) => {
|
|
265
|
+
line: number;
|
|
266
|
+
hash: string;
|
|
267
|
+
};
|
|
268
|
+
shouldExclude: (filePath: string) => boolean;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Create a Hashline instance with custom configuration.
|
|
272
|
+
*
|
|
273
|
+
* @param config - custom configuration options
|
|
274
|
+
* @returns configured Hashline instance
|
|
275
|
+
*/
|
|
276
|
+
declare function createHashline(config?: HashlineConfig): HashlineInstance;
|
|
277
|
+
|
|
278
|
+
export { DEFAULT_CONFIG as D, type HashlineConfig as H, type ResolvedRange as R, type VerifyHashResult as V, type HashEditInput as a, type HashEditOperation as b, type HashEditResult as c, type HashlineInstance as d, HashlineCache as e, DEFAULT_EXCLUDE_PATTERNS as f, DEFAULT_PREFIX as g, applyHashEdit as h, buildHashMap as i, computeLineHash as j, createHashline as k, formatFileWithHashes as l, getAdaptiveHashLength as m, getByteLength as n, matchesGlob as o, normalizeHashRef as p, parseHashRef as q, replaceRange as r, resolveConfig as s, resolveRange as t, shouldExclude as u, stripHashes as v, verifyHash as w };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_CONFIG,
|
|
3
|
+
DEFAULT_EXCLUDE_PATTERNS,
|
|
4
|
+
DEFAULT_PREFIX,
|
|
5
|
+
HashlineCache,
|
|
6
|
+
applyHashEdit,
|
|
7
|
+
buildHashMap,
|
|
8
|
+
computeLineHash,
|
|
9
|
+
createHashline,
|
|
10
|
+
formatFileWithHashes,
|
|
11
|
+
getAdaptiveHashLength,
|
|
12
|
+
getByteLength,
|
|
13
|
+
matchesGlob,
|
|
14
|
+
normalizeHashRef,
|
|
15
|
+
parseHashRef,
|
|
16
|
+
replaceRange,
|
|
17
|
+
resolveConfig,
|
|
18
|
+
resolveRange,
|
|
19
|
+
shouldExclude,
|
|
20
|
+
stripHashes,
|
|
21
|
+
verifyHash
|
|
22
|
+
} from "./chunk-IVZSANZ4.js";
|
|
23
|
+
export {
|
|
24
|
+
DEFAULT_CONFIG,
|
|
25
|
+
DEFAULT_EXCLUDE_PATTERNS,
|
|
26
|
+
DEFAULT_PREFIX,
|
|
27
|
+
HashlineCache,
|
|
28
|
+
applyHashEdit,
|
|
29
|
+
buildHashMap,
|
|
30
|
+
computeLineHash,
|
|
31
|
+
createHashline,
|
|
32
|
+
formatFileWithHashes,
|
|
33
|
+
getAdaptiveHashLength,
|
|
34
|
+
getByteLength,
|
|
35
|
+
matchesGlob,
|
|
36
|
+
normalizeHashRef,
|
|
37
|
+
parseHashRef,
|
|
38
|
+
replaceRange,
|
|
39
|
+
resolveConfig,
|
|
40
|
+
resolveRange,
|
|
41
|
+
shouldExclude,
|
|
42
|
+
stripHashes,
|
|
43
|
+
verifyHash
|
|
44
|
+
};
|