opencode-hashline 1.1.3 → 1.3.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.
@@ -31,6 +31,10 @@ interface HashlineConfig {
31
31
  prefix?: string | false;
32
32
  /** Enable debug logging to ~/.config/opencode/hashline-debug.log (default: false) */
33
33
  debug?: boolean;
34
+ /** Include file revision hash in annotations (default: true) */
35
+ fileRev?: boolean;
36
+ /** Enable safe reapply — relocate lines by hash when they move (default: false) */
37
+ safeReapply?: boolean;
34
38
  }
35
39
  /** Default exclude patterns */
36
40
  declare const DEFAULT_EXCLUDE_PATTERNS: string[];
@@ -45,6 +49,31 @@ declare const DEFAULT_CONFIG: Required<HashlineConfig>;
45
49
  * @param pluginConfig - optional config from plugin context (e.g. opencode.json)
46
50
  */
47
51
  declare function resolveConfig(config?: HashlineConfig, pluginConfig?: HashlineConfig): Required<HashlineConfig>;
52
+ type HashlineErrorCode = "HASH_MISMATCH" | "FILE_REV_MISMATCH" | "AMBIGUOUS_REAPPLY" | "TARGET_OUT_OF_RANGE" | "INVALID_REF" | "INVALID_RANGE" | "MISSING_REPLACEMENT";
53
+ interface CandidateLine {
54
+ lineNumber: number;
55
+ content: string;
56
+ }
57
+ declare class HashlineError extends Error {
58
+ readonly code: HashlineErrorCode;
59
+ readonly expected?: string;
60
+ readonly actual?: string;
61
+ readonly candidates?: CandidateLine[];
62
+ readonly hint?: string;
63
+ readonly lineNumber?: number;
64
+ readonly filePath?: string;
65
+ constructor(opts: {
66
+ code: HashlineErrorCode;
67
+ message: string;
68
+ expected?: string;
69
+ actual?: string;
70
+ candidates?: CandidateLine[];
71
+ hint?: string;
72
+ lineNumber?: number;
73
+ filePath?: string;
74
+ });
75
+ toDiagnostic(): string;
76
+ }
48
77
  /**
49
78
  * Determine the appropriate hash length based on the number of lines.
50
79
  *
@@ -65,6 +94,34 @@ declare function getAdaptiveHashLength(lineCount: number): number;
65
94
  * @returns lowercase hex string of the specified length
66
95
  */
67
96
  declare function computeLineHash(idx: number, line: string, hashLen?: number): string;
97
+ /**
98
+ * Compute a file-level revision hash from the entire content.
99
+ * Uses FNV-1a on CRLF-normalized content, returns 8-char hex (full 32 bits).
100
+ */
101
+ declare function computeFileRev(content: string): string;
102
+ /**
103
+ * Extract the file revision from annotated content.
104
+ * Looks for a line matching `<prefix>REV:<8-hex>` at the start of the content.
105
+ *
106
+ * @param annotatedContent - content with hashline annotations
107
+ * @param prefix - prefix string (default "#HL "), or false for legacy format
108
+ * @returns the revision hash string, or null if not found
109
+ */
110
+ declare function extractFileRev(annotatedContent: string, prefix?: string | false): string | null;
111
+ /**
112
+ * Verify that the file revision matches the current content.
113
+ * Throws HashlineError with code FILE_REV_MISMATCH if it doesn't match.
114
+ */
115
+ declare function verifyFileRev(expectedRev: string, currentContent: string): void;
116
+ /**
117
+ * Find candidate lines that match the expected hash for a given original line index.
118
+ * Used for safe reapply: if a line moved, find where it went.
119
+ *
120
+ * Since computeLineHash uses `${idx}:${trimmed}`, we check each line in the file
121
+ * computing its hash as if it were at the original index — a match means the content
122
+ * is the same as what was originally at that position.
123
+ */
124
+ declare function findCandidateLines(originalLineNumber: number, expectedHash: string, lines: string[], hashLen?: number): CandidateLine[];
68
125
  /**
69
126
  * Format file content with hashline annotations.
70
127
  *
@@ -78,7 +135,7 @@ declare function computeLineHash(idx: number, line: string, hashLen?: number): s
78
135
  * @param prefix - prefix string (default "#HL "), or false to disable
79
136
  * @returns annotated content with hash prefixes
80
137
  */
81
- declare function formatFileWithHashes(content: string, hashLen?: number, prefix?: string | false): string;
138
+ declare function formatFileWithHashes(content: string, hashLen?: number, prefix?: string | false, includeFileRev?: boolean): string;
82
139
  /**
83
140
  * Strip hashline prefixes to recover original file content.
84
141
  *
@@ -122,6 +179,7 @@ interface HashEditInput {
122
179
  startRef: string;
123
180
  endRef?: string;
124
181
  replacement?: string;
182
+ fileRev?: string;
125
183
  }
126
184
  /**
127
185
  * Result of applying a hash-aware edit.
@@ -151,6 +209,9 @@ interface VerifyHashResult {
151
209
  expected?: string;
152
210
  actual?: string;
153
211
  message?: string;
212
+ code?: HashlineErrorCode;
213
+ candidates?: CandidateLine[];
214
+ relocatedLine?: number;
154
215
  }
155
216
  /**
156
217
  * Verify that a line's hash matches the current content.
@@ -169,7 +230,7 @@ interface VerifyHashResult {
169
230
  * @param lines - optional pre-split lines array to avoid re-splitting
170
231
  * @returns verification result
171
232
  */
172
- declare function verifyHash(lineNumber: number, hash: string, currentContent: string, hashLen?: number, lines?: string[]): VerifyHashResult;
233
+ declare function verifyHash(lineNumber: number, hash: string, currentContent: string, hashLen?: number, lines?: string[], safeReapply?: boolean): VerifyHashResult;
173
234
  /**
174
235
  * Result of a range resolution.
175
236
  */
@@ -189,7 +250,7 @@ interface ResolvedRange {
189
250
  * @param hashLen - override hash length (0 or undefined = use hash.length from ref)
190
251
  * @returns resolved range with line numbers and content
191
252
  */
192
- declare function resolveRange(startRef: string, endRef: string, content: string, hashLen?: number): ResolvedRange;
253
+ declare function resolveRange(startRef: string, endRef: string, content: string, hashLen?: number, safeReapply?: boolean): ResolvedRange;
193
254
  /**
194
255
  * Replace a range of lines identified by hash references with new content.
195
256
  * Splits content once and reuses the lines array.
@@ -208,7 +269,7 @@ declare function replaceRange(startRef: string, endRef: string, content: string,
208
269
  * Unlike search/replace tools, this resolves references by line+hash and
209
270
  * verifies them before editing, so exact old-string matching is not required.
210
271
  */
211
- declare function applyHashEdit(input: HashEditInput, content: string, hashLen?: number): HashEditResult;
272
+ declare function applyHashEdit(input: HashEditInput, content: string, hashLen?: number, safeReapply?: boolean): HashEditResult;
212
273
  /**
213
274
  * Simple LRU cache for annotated file content.
214
275
  */
@@ -268,6 +329,10 @@ interface HashlineInstance {
268
329
  hash: string;
269
330
  };
270
331
  shouldExclude: (filePath: string) => boolean;
332
+ computeFileRev: (content: string) => string;
333
+ verifyFileRev: (expectedRev: string, currentContent: string) => void;
334
+ extractFileRev: (annotatedContent: string) => string | null;
335
+ findCandidateLines: (originalLineNumber: number, expectedHash: string, lines: string[], hashLen?: number) => CandidateLine[];
271
336
  }
272
337
  /**
273
338
  * Create a Hashline instance with custom configuration.
@@ -277,4 +342,4 @@ interface HashlineInstance {
277
342
  */
278
343
  declare function createHashline(config?: HashlineConfig): HashlineInstance;
279
344
 
280
- 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 };
345
+ export { stripHashes as A, verifyFileRev as B, type CandidateLine as C, DEFAULT_CONFIG as D, verifyHash as E, 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 HashlineErrorCode as d, type HashlineInstance as e, HashlineCache as f, DEFAULT_EXCLUDE_PATTERNS as g, DEFAULT_PREFIX as h, HashlineError as i, applyHashEdit as j, buildHashMap as k, computeFileRev as l, computeLineHash as m, createHashline as n, extractFileRev as o, findCandidateLines as p, formatFileWithHashes as q, getAdaptiveHashLength as r, getByteLength as s, matchesGlob as t, normalizeHashRef as u, parseHashRef as v, replaceRange as w, resolveConfig as x, resolveRange as y, shouldExclude as z };
@@ -3,11 +3,15 @@ import {
3
3
  DEFAULT_EXCLUDE_PATTERNS,
4
4
  DEFAULT_PREFIX,
5
5
  HashlineCache,
6
+ HashlineError,
6
7
  applyHashEdit,
7
8
  buildHashMap,
9
+ computeFileRev,
8
10
  computeLineHash,
9
11
  createHashline,
10
12
  detectLineEnding,
13
+ extractFileRev,
14
+ findCandidateLines,
11
15
  formatFileWithHashes,
12
16
  getAdaptiveHashLength,
13
17
  getByteLength,
@@ -19,18 +23,23 @@ import {
19
23
  resolveRange,
20
24
  shouldExclude,
21
25
  stripHashes,
26
+ verifyFileRev,
22
27
  verifyHash
23
- } from "./chunk-I6RACR3D.js";
28
+ } from "./chunk-DOR4YDIS.js";
24
29
  export {
25
30
  DEFAULT_CONFIG,
26
31
  DEFAULT_EXCLUDE_PATTERNS,
27
32
  DEFAULT_PREFIX,
28
33
  HashlineCache,
34
+ HashlineError,
29
35
  applyHashEdit,
30
36
  buildHashMap,
37
+ computeFileRev,
31
38
  computeLineHash,
32
39
  createHashline,
33
40
  detectLineEnding,
41
+ extractFileRev,
42
+ findCandidateLines,
34
43
  formatFileWithHashes,
35
44
  getAdaptiveHashLength,
36
45
  getByteLength,
@@ -42,5 +51,6 @@ export {
42
51
  resolveRange,
43
52
  shouldExclude,
44
53
  stripHashes,
54
+ verifyFileRev,
45
55
  verifyHash
46
56
  };