@chances-ai/engine 28.0.0 → 30.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.
Files changed (151) hide show
  1. package/dist/agents/index.js +1 -1
  2. package/dist/ai/adapters/ai-sdk-stream.js +15 -0
  3. package/dist/ai/adapters/ai-sdk-stream.js.map +1 -1
  4. package/dist/ai/adapters/ai-sdk.js +1 -1
  5. package/dist/ai/adapters/ai-sdk.js.map +1 -1
  6. package/dist/ai/setup.d.ts +1 -1
  7. package/dist/ai/setup.js +1 -1
  8. package/dist/ai/types.d.ts +13 -1
  9. package/dist/ai/types.d.ts.map +1 -1
  10. package/dist/core/engine.d.ts +17 -3
  11. package/dist/core/engine.d.ts.map +1 -1
  12. package/dist/core/engine.js +49 -3
  13. package/dist/core/engine.js.map +1 -1
  14. package/dist/core/task-tool.d.ts +1 -1
  15. package/dist/core/task-tool.js +1 -1
  16. package/dist/core/workspace-query.d.ts +1 -1
  17. package/dist/core/workspace-query.js +1 -1
  18. package/dist/hashline/apply.d.ts +9 -0
  19. package/dist/hashline/apply.d.ts.map +1 -0
  20. package/dist/hashline/apply.js +523 -0
  21. package/dist/hashline/apply.js.map +1 -0
  22. package/dist/hashline/block.d.ts +25 -0
  23. package/dist/hashline/block.d.ts.map +1 -0
  24. package/dist/hashline/block.js +71 -0
  25. package/dist/hashline/block.js.map +1 -0
  26. package/dist/hashline/format.d.ts +106 -0
  27. package/dist/hashline/format.d.ts.map +1 -0
  28. package/dist/hashline/format.js +191 -0
  29. package/dist/hashline/format.js.map +1 -0
  30. package/dist/hashline/fs.d.ts +87 -0
  31. package/dist/hashline/fs.d.ts.map +1 -0
  32. package/dist/hashline/fs.js +123 -0
  33. package/dist/hashline/fs.js.map +1 -0
  34. package/dist/hashline/index.d.ts +27 -0
  35. package/dist/hashline/index.d.ts.map +1 -0
  36. package/dist/hashline/index.js +27 -0
  37. package/dist/hashline/index.js.map +1 -0
  38. package/dist/hashline/input.d.ts +101 -0
  39. package/dist/hashline/input.d.ts.map +1 -0
  40. package/dist/hashline/input.js +378 -0
  41. package/dist/hashline/input.js.map +1 -0
  42. package/dist/hashline/messages.d.ts +87 -0
  43. package/dist/hashline/messages.d.ts.map +1 -0
  44. package/dist/hashline/messages.js +94 -0
  45. package/dist/hashline/messages.js.map +1 -0
  46. package/dist/hashline/mismatch.d.ts +45 -0
  47. package/dist/hashline/mismatch.d.ts.map +1 -0
  48. package/dist/hashline/mismatch.js +118 -0
  49. package/dist/hashline/mismatch.js.map +1 -0
  50. package/dist/hashline/normalize.d.ts +22 -0
  51. package/dist/hashline/normalize.d.ts.map +1 -0
  52. package/dist/hashline/normalize.js +31 -0
  53. package/dist/hashline/normalize.js.map +1 -0
  54. package/dist/hashline/parser.d.ts +24 -0
  55. package/dist/hashline/parser.d.ts.map +1 -0
  56. package/dist/hashline/parser.js +295 -0
  57. package/dist/hashline/parser.js.map +1 -0
  58. package/dist/hashline/patcher.d.ts +111 -0
  59. package/dist/hashline/patcher.d.ts.map +1 -0
  60. package/dist/hashline/patcher.js +332 -0
  61. package/dist/hashline/patcher.js.map +1 -0
  62. package/dist/hashline/recovery.d.ts +41 -0
  63. package/dist/hashline/recovery.d.ts.map +1 -0
  64. package/dist/hashline/recovery.js +175 -0
  65. package/dist/hashline/recovery.js.map +1 -0
  66. package/dist/hashline/snapshots.d.ts +62 -0
  67. package/dist/hashline/snapshots.d.ts.map +1 -0
  68. package/dist/hashline/snapshots.js +127 -0
  69. package/dist/hashline/snapshots.js.map +1 -0
  70. package/dist/hashline/tokenizer.d.ts +66 -0
  71. package/dist/hashline/tokenizer.d.ts.map +1 -0
  72. package/dist/hashline/tokenizer.js +408 -0
  73. package/dist/hashline/tokenizer.js.map +1 -0
  74. package/dist/hashline/types.d.ts +117 -0
  75. package/dist/hashline/types.d.ts.map +1 -0
  76. package/dist/hashline/types.js +13 -0
  77. package/dist/hashline/types.js.map +1 -0
  78. package/dist/local-vault/index.d.ts +1 -1
  79. package/dist/local-vault/index.js +1 -1
  80. package/dist/lsp/index.d.ts +1 -1
  81. package/dist/lsp/index.js +1 -1
  82. package/dist/lsp/types.d.ts +2 -2
  83. package/dist/mcp/host.d.ts +3 -3
  84. package/dist/mcp/load-mcp-host.d.ts +4 -4
  85. package/dist/mcp/load-mcp-host.js +4 -4
  86. package/dist/mcp/oauth/provider.d.ts.map +1 -1
  87. package/dist/mcp/oauth/provider.js +10 -1
  88. package/dist/mcp/oauth/provider.js.map +1 -1
  89. package/dist/plugin-api/index.d.ts +10 -2
  90. package/dist/plugin-api/index.d.ts.map +1 -1
  91. package/dist/plugin-api/index.js +15 -0
  92. package/dist/plugin-api/index.js.map +1 -1
  93. package/dist/tools/approval.d.ts +1 -1
  94. package/dist/tools/approval.js +1 -1
  95. package/dist/tools/builtins/_hashline-fs.d.ts +16 -0
  96. package/dist/tools/builtins/_hashline-fs.d.ts.map +1 -0
  97. package/dist/tools/builtins/_hashline-fs.js +62 -0
  98. package/dist/tools/builtins/_hashline-fs.js.map +1 -0
  99. package/dist/tools/builtins/_image.d.ts +26 -0
  100. package/dist/tools/builtins/_image.d.ts.map +1 -0
  101. package/dist/tools/builtins/_image.js +76 -0
  102. package/dist/tools/builtins/_image.js.map +1 -0
  103. package/dist/tools/builtins/_login-shell.d.ts +17 -0
  104. package/dist/tools/builtins/_login-shell.d.ts.map +1 -0
  105. package/dist/tools/builtins/_login-shell.js +66 -0
  106. package/dist/tools/builtins/_login-shell.js.map +1 -0
  107. package/dist/tools/builtins/_notebook.d.ts +15 -0
  108. package/dist/tools/builtins/_notebook.d.ts.map +1 -0
  109. package/dist/tools/builtins/_notebook.js +81 -0
  110. package/dist/tools/builtins/_notebook.js.map +1 -0
  111. package/dist/tools/builtins/_pdf.d.ts +19 -0
  112. package/dist/tools/builtins/_pdf.d.ts.map +1 -0
  113. package/dist/tools/builtins/_pdf.js +42 -0
  114. package/dist/tools/builtins/_pdf.js.map +1 -0
  115. package/dist/tools/builtins/_shared.d.ts +11 -0
  116. package/dist/tools/builtins/_shared.d.ts.map +1 -1
  117. package/dist/tools/builtins/_shared.js +40 -1
  118. package/dist/tools/builtins/_shared.js.map +1 -1
  119. package/dist/tools/builtins/ast-edit.d.ts +18 -0
  120. package/dist/tools/builtins/ast-edit.d.ts.map +1 -0
  121. package/dist/tools/builtins/ast-edit.js +109 -0
  122. package/dist/tools/builtins/ast-edit.js.map +1 -0
  123. package/dist/tools/builtins/ast-grep.d.ts +6 -0
  124. package/dist/tools/builtins/ast-grep.d.ts.map +1 -0
  125. package/dist/tools/builtins/ast-grep.js +67 -0
  126. package/dist/tools/builtins/ast-grep.js.map +1 -0
  127. package/dist/tools/builtins/bash.d.ts.map +1 -1
  128. package/dist/tools/builtins/bash.js +13 -2
  129. package/dist/tools/builtins/bash.js.map +1 -1
  130. package/dist/tools/builtins/edit.d.ts.map +1 -1
  131. package/dist/tools/builtins/edit.js +112 -31
  132. package/dist/tools/builtins/edit.js.map +1 -1
  133. package/dist/tools/builtins/lsp.js +1 -1
  134. package/dist/tools/builtins/pty.d.ts +1 -1
  135. package/dist/tools/builtins/read.d.ts.map +1 -1
  136. package/dist/tools/builtins/read.js +187 -11
  137. package/dist/tools/builtins/read.js.map +1 -1
  138. package/dist/tools/builtins.d.ts.map +1 -1
  139. package/dist/tools/builtins.js +4 -0
  140. package/dist/tools/builtins.js.map +1 -1
  141. package/dist/tools/file-lock.d.ts +8 -0
  142. package/dist/tools/file-lock.d.ts.map +1 -1
  143. package/dist/tools/file-lock.js +22 -0
  144. package/dist/tools/file-lock.js.map +1 -1
  145. package/dist/tools/index.d.ts +1 -1
  146. package/dist/tools/index.d.ts.map +1 -1
  147. package/dist/tools/index.js.map +1 -1
  148. package/dist/tools/types.d.ts +27 -1
  149. package/dist/tools/types.d.ts.map +1 -1
  150. package/dist/tools/types.js.map +1 -1
  151. package/package.json +8 -3
@@ -0,0 +1,332 @@
1
+ /**
2
+ * High-level patch orchestrator. Reads each section's target file via the
3
+ * configured {@link Filesystem}, strips BOM and normalizes line endings,
4
+ * validates the section snapshot tag (with {@link Recovery}), applies the
5
+ * result back through the same {@link Filesystem}. Ported from oh-my-pi
6
+ * `packages/hashline/src/patcher.ts`.
7
+ *
8
+ * **chances-cli change (7.9 R1-M3):** the live-match integrity check is exact
9
+ * snapshot-text equality (`byHash(path, tag)?.text === live`), NOT hash
10
+ * equality. The 4-hex tag is only a fast index; comparing the stored full text
11
+ * means a 16-bit collision can never silently apply stale line numbers to
12
+ * different live content — it degrades to recovery → forced re-read. The cost
13
+ * is that an edit to a file whose snapshot was evicted from the bounded store
14
+ * (or carried from a prior session) re-reads instead of fast-applying; that is
15
+ * the safe direction.
16
+ *
17
+ * Two layers:
18
+ *
19
+ * - {@link Patcher.apply} — high-level, all-or-nothing. Preflights every
20
+ * section in memory before any write hits disk, then commits in order.
21
+ * - {@link Patcher.prepare} / {@link Patcher.commit} — granular primitives
22
+ * for callers that need per-section control (e.g. the engine `edit` tool,
23
+ * which holds the per-file mutation lock across the whole prepare+commit
24
+ * span — 7.9 R1-M1).
25
+ *
26
+ * Because `prepare` already runs the full apply, a multi-section batch is
27
+ * naturally all-or-nothing: by the time any `commit` runs, every section
28
+ * has been validated.
29
+ *
30
+ * The patcher itself is stateless across calls; reuse one instance per
31
+ * filesystem configuration.
32
+ */
33
+ import { applyEdits } from "./apply.js";
34
+ import { hasBlockEdit, resolveBlockEdits } from "./block.js";
35
+ import { computeFileHash, formatHashlineHeader, hashTrimsTrailing } from "./format.js";
36
+ import { isNotFound } from "./fs.js";
37
+ import { HEADTAIL_DRIFT_WARNING, missingSnapshotTagMessage } from "./messages.js";
38
+ import { MismatchError } from "./mismatch.js";
39
+ import { detectLineEnding, normalizeToLF, restoreLineEndings, stripBom } from "./normalize.js";
40
+ import { Recovery } from "./recovery.js";
41
+ /**
42
+ * Opaque token returned by {@link Patcher.prepare}. Carries the section, the
43
+ * raw file content read off disk, and the in-memory apply result.
44
+ * {@link Patcher.commit} just writes the {@link PreparedSection.applyResult}.
45
+ */
46
+ export class PreparedSection {
47
+ section;
48
+ canonicalPath;
49
+ exists;
50
+ rawContent;
51
+ bom;
52
+ lineEnding;
53
+ normalized;
54
+ applyResult;
55
+ parseWarnings;
56
+ /** @internal */
57
+ constructor(section, canonicalPath, exists, rawContent, bom, lineEnding, normalized, applyResult, parseWarnings) {
58
+ this.section = section;
59
+ this.canonicalPath = canonicalPath;
60
+ this.exists = exists;
61
+ this.rawContent = rawContent;
62
+ this.bom = bom;
63
+ this.lineEnding = lineEnding;
64
+ this.normalized = normalized;
65
+ this.applyResult = applyResult;
66
+ this.parseWarnings = parseWarnings;
67
+ }
68
+ /** Convenience: returns true when the apply produced no change. */
69
+ get isNoop() {
70
+ return this.applyResult.text === this.normalized;
71
+ }
72
+ }
73
+ function hasAnchorScopedEdit(edits) {
74
+ return edits.some((edit) => {
75
+ if (edit.kind === "delete")
76
+ return true;
77
+ // A `replace block N:` edit anchors to concrete content on line N.
78
+ if (edit.kind === "block")
79
+ return true;
80
+ return edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor";
81
+ });
82
+ }
83
+ function assertSectionHashPresent(sectionPath, fileHash) {
84
+ if (fileHash !== undefined)
85
+ return;
86
+ throw new Error(missingSnapshotTagMessage(sectionPath));
87
+ }
88
+ function recoveryToApplyResult(result) {
89
+ return {
90
+ text: result.text,
91
+ firstChangedLine: result.firstChangedLine,
92
+ warnings: result.warnings,
93
+ };
94
+ }
95
+ function mergeWarnings(...sources) {
96
+ const out = [];
97
+ for (const source of sources) {
98
+ if (!source)
99
+ continue;
100
+ for (const warning of source)
101
+ out.push(warning);
102
+ }
103
+ return out;
104
+ }
105
+ function assertUniqueCanonicalPaths(prepared) {
106
+ const seen = new Map();
107
+ for (const entry of prepared) {
108
+ const previous = seen.get(entry.canonicalPath);
109
+ if (previous !== undefined) {
110
+ throw new Error(`Multiple hashline sections resolve to the same file (${previous} and ${entry.section.path}). Merge their ops under one header before applying.`);
111
+ }
112
+ seen.set(entry.canonicalPath, entry.section.path);
113
+ }
114
+ }
115
+ /**
116
+ * High-level patcher. Wires a {@link Filesystem} and a required
117
+ * {@link SnapshotStore} together with the parsing + applying core.
118
+ *
119
+ * Construct once per FS configuration; reuse across patches.
120
+ */
121
+ export class Patcher {
122
+ fs;
123
+ snapshots;
124
+ recovery;
125
+ blockResolver;
126
+ constructor(options) {
127
+ if (!options.snapshots) {
128
+ throw new Error("Hashline Patcher requires a SnapshotStore; section tags are opaque store pointers.");
129
+ }
130
+ this.fs = options.fs;
131
+ this.snapshots = options.snapshots;
132
+ this.recovery = new Recovery(options.snapshots);
133
+ this.blockResolver = options.blockResolver;
134
+ }
135
+ /**
136
+ * Apply every section in `patch`. `prepare` runs the full apply for each
137
+ * section in memory before any write hits the filesystem, so a
138
+ * multi-section batch is naturally all-or-nothing. Returns one
139
+ * {@link PatchSectionResult} per section in the original patch order.
140
+ */
141
+ async apply(patch) {
142
+ // Single-section fast path.
143
+ if (patch.sections.length === 1) {
144
+ const prepared = await this.prepare(patch.sections[0]);
145
+ return { sections: [await this.commit(prepared)] };
146
+ }
147
+ // Prepare every section first so any failure (stale hash, missing
148
+ // file, parse error, in-memory no-op) surfaces before any write.
149
+ const prepared = [];
150
+ for (const section of patch.sections)
151
+ prepared.push(await this.prepare(section));
152
+ assertUniqueCanonicalPaths(prepared);
153
+ for (const entry of prepared) {
154
+ if (entry.isNoop) {
155
+ throw new Error(`Edits to ${entry.section.path} resulted in no changes being made.`);
156
+ }
157
+ }
158
+ const results = [];
159
+ for (const entry of prepared)
160
+ results.push(await this.commit(entry));
161
+ return { sections: results };
162
+ }
163
+ /**
164
+ * Run the preflight pass only: read, parse, validate, apply-in-memory.
165
+ * No writes hit the filesystem. Use for CI checks and dry runs.
166
+ */
167
+ async preflight(patch) {
168
+ const prepared = [];
169
+ for (const section of patch.sections)
170
+ prepared.push(await this.prepare(section));
171
+ assertUniqueCanonicalPaths(prepared);
172
+ for (const entry of prepared) {
173
+ if (entry.isNoop) {
174
+ throw new Error(`Edits to ${entry.section.path} resulted in no changes being made.`);
175
+ }
176
+ }
177
+ }
178
+ /**
179
+ * Read a section's target file, parse the section, validate the snapshot
180
+ * tag (with recovery), and apply the edits in memory. Returns a
181
+ * {@link PreparedSection} which can be fed to {@link commit} to land
182
+ * the result on the filesystem.
183
+ *
184
+ * Throws on parse error, missing-file-for-anchored-edit, or unrecovered
185
+ * tag mismatch ({@link MismatchError}).
186
+ */
187
+ async prepare(section) {
188
+ const { edits, warnings: parseWarnings } = section.parse();
189
+ assertSectionHashPresent(section.path, section.fileHash);
190
+ const canonicalPath = this.fs.canonicalPath(section.path);
191
+ await this.fs.preflightWrite(section.path);
192
+ const { exists, rawContent } = await this.#tryRead(section.path);
193
+ if (!exists) {
194
+ throw new Error(`File not found: ${section.path}. Use the write tool to create new files.`);
195
+ }
196
+ const { bom, text } = stripBom(rawContent);
197
+ const lineEnding = detectLineEnding(text);
198
+ const normalized = normalizeToLF(text);
199
+ const applyResult = this.#applyWithRecovery({
200
+ section,
201
+ canonicalPath,
202
+ exists,
203
+ normalized,
204
+ edits,
205
+ });
206
+ return new PreparedSection(section, canonicalPath, exists, rawContent, bom, lineEnding, normalized, applyResult, parseWarnings);
207
+ }
208
+ /**
209
+ * Commit a previously {@link prepare}d section to the filesystem.
210
+ * Restores line endings and BOM, writes via the {@link Filesystem}, and
211
+ * records a fresh snapshot in the {@link SnapshotStore} keyed by the
212
+ * filesystem-canonical path.
213
+ */
214
+ async commit(prepared) {
215
+ const { section, normalized, bom, lineEnding, parseWarnings, exists, applyResult, canonicalPath } = prepared;
216
+ const after = applyResult.text;
217
+ const warnings = mergeWarnings(parseWarnings, applyResult.warnings);
218
+ if (after === normalized) {
219
+ const hash = this.#recordFullSnapshot(canonicalPath, normalized);
220
+ return {
221
+ path: section.path,
222
+ canonicalPath,
223
+ op: "noop",
224
+ before: normalized,
225
+ after: normalized,
226
+ persisted: prepared.rawContent,
227
+ written: prepared.rawContent,
228
+ fileHash: hash,
229
+ header: formatHashlineHeader(section.path, hash),
230
+ warnings,
231
+ };
232
+ }
233
+ const persisted = bom + restoreLineEndings(after, lineEnding);
234
+ const write = await this.fs.writeText(section.path, persisted);
235
+ const fileHash = this.#recordFullSnapshot(canonicalPath, after);
236
+ const op = exists ? "update" : "create";
237
+ return {
238
+ path: section.path,
239
+ canonicalPath,
240
+ op,
241
+ before: normalized,
242
+ after,
243
+ persisted,
244
+ written: write.text,
245
+ fileHash,
246
+ header: formatHashlineHeader(section.path, fileHash),
247
+ firstChangedLine: applyResult.firstChangedLine,
248
+ warnings,
249
+ };
250
+ }
251
+ async #tryRead(path) {
252
+ try {
253
+ const content = await this.fs.readText(path);
254
+ return { exists: true, rawContent: content };
255
+ }
256
+ catch (error) {
257
+ if (isNotFound(error))
258
+ return { exists: false, rawContent: "" };
259
+ throw error;
260
+ }
261
+ }
262
+ #recordFullSnapshot(canonicalPath, normalized) {
263
+ return this.snapshots.record(canonicalPath, normalized);
264
+ }
265
+ #mismatchError(section, canonicalPath, normalized, expected, hashRecognized) {
266
+ // Display the live content's tag (path-aware) without disturbing the
267
+ // version history's head/recency: compute directly rather than record.
268
+ const actualFileHash = computeFileHash(normalized, hashTrimsTrailing(section.path));
269
+ return new MismatchError({
270
+ path: section.path,
271
+ expectedFileHash: expected,
272
+ actualFileHash,
273
+ fileLines: normalized.split("\n"),
274
+ anchorLines: section.collectAnchorLines(),
275
+ hashRecognized,
276
+ });
277
+ }
278
+ #applyWithRecovery(args) {
279
+ const { section, canonicalPath, exists, normalized, edits } = args;
280
+ const expected = exists ? section.fileHash : undefined;
281
+ // R1-M3: the tag is a fast INDEX; the integrity check is exact stored-text
282
+ // equality. A 16-bit collision can therefore never silently apply stale
283
+ // line numbers — it falls through to recovery / re-read.
284
+ const liveMatches = expected !== undefined && this.snapshots.byHash(canonicalPath, expected)?.text === normalized;
285
+ // Resolve `replace block N:` edits to concrete ranges before recovery
286
+ // runs. Block anchors are expressed against the snapshot the section tag
287
+ // names, so resolve against that exact text:
288
+ // - live content matches the tag (or there is no tag) → resolve against
289
+ // the live, normalized content;
290
+ // - the file drifted → resolve against the tagged snapshot's text so the
291
+ // resulting ranges flow through the 3-way-merge recovery below.
292
+ // When a block edit needs the tagged snapshot but it is unavailable, the
293
+ // range cannot be placed safely — reject with a MismatchError (re-read).
294
+ let resolved = edits;
295
+ if (hasBlockEdit(edits)) {
296
+ const baseText = expected === undefined || liveMatches ? normalized : this.snapshots.byHash(canonicalPath, expected)?.text;
297
+ if (baseText === undefined) {
298
+ throw this.#mismatchError(section, canonicalPath, normalized, expected ?? "", false);
299
+ }
300
+ resolved = resolveBlockEdits(edits, baseText, section.path, this.blockResolver, { onUnresolved: "throw" });
301
+ }
302
+ if (expected === undefined)
303
+ return applyEdits(normalized, resolved);
304
+ // Whole-file unchanged → the tag still names the live content, so an
305
+ // edit anchored at ANY line (displayed or not) is safe to apply.
306
+ if (liveMatches)
307
+ return applyEdits(normalized, resolved);
308
+ // Head/tail-only inserts are position-stable: "start"/"end" cannot move
309
+ // with content drift, so a stale tag is non-fatal — BUT only when the tag
310
+ // was actually minted this session (R2-M1). An unrecognized tag (`byHash`
311
+ // null — fabricated or carried from a prior session) must still force a
312
+ // re-read; otherwise the model could append to a file it never read,
313
+ // breaking the "tag comes from read" contract (the M3 safety boundary).
314
+ if (!hasAnchorScopedEdit(resolved) && this.snapshots.byHash(canonicalPath, expected) !== null) {
315
+ const result = applyEdits(normalized, resolved);
316
+ return { ...result, warnings: [HEADTAIL_DRIFT_WARNING, ...(result.warnings ?? [])] };
317
+ }
318
+ // File drifted: try to replay the edit against the version the tag
319
+ // names and 3-way-merge it onto the live content.
320
+ const recovered = this.recovery.tryRecover({
321
+ path: canonicalPath,
322
+ currentText: normalized,
323
+ fileHash: expected,
324
+ edits: resolved,
325
+ });
326
+ if (recovered)
327
+ return recoveryToApplyResult(recovered);
328
+ const hashRecognized = this.snapshots.byHash(canonicalPath, expected) !== null;
329
+ throw this.#mismatchError(section, canonicalPath, normalized, expected, hashRecognized);
330
+ }
331
+ }
332
+ //# sourceMappingURL=patcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patcher.js","sourceRoot":"","sources":["../../src/hashline/patcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { SnapshotStore } from "./snapshots.js";
2
+ import type { Edit } from "./types.js";
3
+ export interface RecoveryArgs {
4
+ path: string;
5
+ currentText: string;
6
+ fileHash: string;
7
+ edits: readonly Edit[];
8
+ }
9
+ export interface RecoveryResult {
10
+ /** Post-recovery text. */
11
+ text: string;
12
+ /** First changed line (1-indexed) relative to the live `currentText`, or `undefined`. */
13
+ firstChangedLine: number | undefined;
14
+ /** Warnings collected during recovery, including the user-facing recovery banner. */
15
+ warnings: string[];
16
+ }
17
+ /**
18
+ * Stateless recovery driver over a {@link SnapshotStore}. Construct once and
19
+ * call {@link Recovery.tryRecover} per stale-tag incident. The default
20
+ * implementation tries two strategies in order:
21
+ *
22
+ * 1. Apply the edits on the full-file version the tag names, then 3-way-merge
23
+ * the resulting patch onto the live content (handles external writes).
24
+ * 2. (Session chain) If that version wasn't the head, replay the edits onto
25
+ * the live content directly when line counts match AND every edit's anchor
26
+ * line content is unchanged between version and current — a prior in-session
27
+ * edit advanced the tag and the model's anchors still name the same logical
28
+ * rows. Emits a dedicated {@link RECOVERY_SESSION_REPLAY_WARNING} because
29
+ * even with both guards a coincidental insert+delete pair on duplicate rows
30
+ * can still land the edit on the wrong row; see {@link replaySessionChainOnCurrent}.
31
+ */
32
+ export declare class Recovery {
33
+ readonly store: SnapshotStore;
34
+ constructor(store: SnapshotStore);
35
+ /**
36
+ * Attempt recovery. Returns `null` when no path forward is found — the
37
+ * caller should then surface a {@link MismatchError}.
38
+ */
39
+ tryRecover(args: RecoveryArgs): RecoveryResult | null;
40
+ }
41
+ //# sourceMappingURL=recovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/hashline/recovery.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAY,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAuB,IAAI,EAAE,MAAM,YAAY,CAAC;AAO5D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,qFAAqF;IACrF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAkHD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,QAAQ;IACP,QAAQ,CAAC,KAAK,EAAE,aAAa;gBAApB,KAAK,EAAE,aAAa;IACzC;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,GAAG,IAAI;CAetD"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Recover from a stale section snapshot tag by replaying the would-be edit
3
+ * against a cached pre-edit snapshot of the file and 3-way-merging the
4
+ * result onto the current on-disk content. Ported from oh-my-pi
5
+ * `packages/hashline/src/recovery.ts`.
6
+ *
7
+ * This is the synthesis the 7.9 decision [5.1] calls for: claude-code's
8
+ * normalization cascade survives here as the WITHIN-block content fallback —
9
+ * when the live file drifted, we replay the edit on the version the tag names
10
+ * and 3-way-merge onto live, instead of hard-rejecting.
11
+ *
12
+ * The patcher consults this when a section tag resolves to a snapshot that no
13
+ * longer matches the live file content. The recovery class is stateless apart
14
+ * from the {@link SnapshotStore} it queries; the snapshot store is the seam
15
+ * that lets you plug in your own caching strategy.
16
+ */
17
+ import * as Diff from "diff";
18
+ import { applyEdits } from "./apply.js";
19
+ import { RECOVERY_EXTERNAL_WARNING, RECOVERY_SESSION_CHAIN_WARNING, RECOVERY_SESSION_REPLAY_WARNING, } from "./messages.js";
20
+ // Section tags are line-precise; never let Diff.applyPatch slide a hunk
21
+ // onto a duplicate closer 100+ lines away. If snapshot replay does not
22
+ // align exactly, refuse and let the caller re-read.
23
+ const RECOVERY_FUZZ_FACTOR = 0;
24
+ function applyEditsToSnapshot(previousText, currentText, edits, recoveryWarning) {
25
+ let applied;
26
+ try {
27
+ applied = applyEdits(previousText, [...edits]);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ if (applied.text === previousText)
33
+ return null;
34
+ const patch = Diff.structuredPatch("file", "file", previousText, applied.text, "", "", { context: 3 });
35
+ const merged = Diff.applyPatch(currentText, patch, { fuzzFactor: RECOVERY_FUZZ_FACTOR });
36
+ if (typeof merged !== "string" || merged === currentText)
37
+ return null;
38
+ const firstChangedLine = findFirstChangedLine(currentText, merged) ?? applied.firstChangedLine;
39
+ const hasNetChange = firstChangedLine !== undefined;
40
+ const warnings = hasNetChange ? [recoveryWarning, ...(applied.warnings ?? [])] : [...(applied.warnings ?? [])];
41
+ return { text: merged, firstChangedLine, warnings };
42
+ }
43
+ function collectAnchorLines(edits) {
44
+ const lines = [];
45
+ for (const edit of edits) {
46
+ for (const anchor of getEditAnchors(edit))
47
+ lines.push(anchor.line);
48
+ }
49
+ return lines;
50
+ }
51
+ function getEditAnchors(edit) {
52
+ if (edit.kind === "delete")
53
+ return [edit.anchor];
54
+ // Recovery only ever receives already-resolved edits (no `block`); this arm
55
+ // exists for type-exhaustiveness over the full `Edit` union.
56
+ if (edit.kind === "block")
57
+ return [edit.anchor];
58
+ return edit.cursor.kind === "before_anchor" || edit.cursor.kind === "after_anchor" ? [edit.cursor.anchor] : [];
59
+ }
60
+ /**
61
+ * Returns true when every anchor line in `edits` has identical content in
62
+ * `previousText` and `currentText`. The session-chain replay fast-path
63
+ * requires this: if the prior in-session edit rewrote the line the model is
64
+ * now re-targeting with a stale hash, replaying onto current would silently
65
+ * overwrite the new content with whatever the model authored against the
66
+ * old content — a corruption window, not a recovery.
67
+ */
68
+ function verifyAnchorContent(previousText, currentText, edits) {
69
+ const lines = collectAnchorLines(edits);
70
+ if (lines.length === 0)
71
+ return true;
72
+ const prev = previousText.split("\n");
73
+ const curr = currentText.split("\n");
74
+ for (const line of lines) {
75
+ const idx = line - 1;
76
+ if (idx < 0 || idx >= prev.length || idx >= curr.length)
77
+ return false;
78
+ if (prev[idx] !== curr[idx])
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+ function replaySessionChainOnCurrent(previousText, currentText, edits) {
84
+ // Two guards narrow the corruption window. Neither alone is sufficient,
85
+ // and even together they don't fully prove correctness — replay is the
86
+ // less-certain recovery mode and emits RECOVERY_SESSION_REPLAY_WARNING
87
+ // so the caller can verify the diff.
88
+ // - Equal line counts: every line number in `edits` still resolves to
89
+ // SOME logical row (no net shift across the prior chain). A
90
+ // coincidental insert+delete pair can still leave indices pointing
91
+ // at different logical rows than the model anchored against.
92
+ // - Anchor-content alignment: the row at each anchor's line index has
93
+ // identical content in previous and current. Catches the common
94
+ // case of a prior edit rewriting the targeted line; can still be
95
+ // coincidentally satisfied by a duplicated row at the shifted
96
+ // index.
97
+ if (previousText.split("\n").length !== currentText.split("\n").length)
98
+ return null;
99
+ if (!verifyAnchorContent(previousText, currentText, edits))
100
+ return null;
101
+ let applied;
102
+ try {
103
+ applied = applyEdits(currentText, [...edits]);
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ if (applied.text === currentText)
109
+ return null;
110
+ return {
111
+ text: applied.text,
112
+ firstChangedLine: applied.firstChangedLine,
113
+ warnings: [RECOVERY_SESSION_REPLAY_WARNING, ...(applied.warnings ?? [])],
114
+ };
115
+ }
116
+ /** First 1-indexed line at which `a` and `b` diverge, or `undefined` if equal. */
117
+ function findFirstChangedLine(a, b) {
118
+ if (a === b)
119
+ return undefined;
120
+ const aLines = a.split("\n");
121
+ const bLines = b.split("\n");
122
+ const max = Math.max(aLines.length, bLines.length);
123
+ for (let i = 0; i < max; i++) {
124
+ if (aLines[i] !== bLines[i])
125
+ return i + 1;
126
+ }
127
+ return undefined;
128
+ }
129
+ function isHeadSnapshot(head, snapshot) {
130
+ return head === snapshot;
131
+ }
132
+ /**
133
+ * Stateless recovery driver over a {@link SnapshotStore}. Construct once and
134
+ * call {@link Recovery.tryRecover} per stale-tag incident. The default
135
+ * implementation tries two strategies in order:
136
+ *
137
+ * 1. Apply the edits on the full-file version the tag names, then 3-way-merge
138
+ * the resulting patch onto the live content (handles external writes).
139
+ * 2. (Session chain) If that version wasn't the head, replay the edits onto
140
+ * the live content directly when line counts match AND every edit's anchor
141
+ * line content is unchanged between version and current — a prior in-session
142
+ * edit advanced the tag and the model's anchors still name the same logical
143
+ * rows. Emits a dedicated {@link RECOVERY_SESSION_REPLAY_WARNING} because
144
+ * even with both guards a coincidental insert+delete pair on duplicate rows
145
+ * can still land the edit on the wrong row; see {@link replaySessionChainOnCurrent}.
146
+ */
147
+ export class Recovery {
148
+ store;
149
+ constructor(store) {
150
+ this.store = store;
151
+ }
152
+ /**
153
+ * Attempt recovery. Returns `null` when no path forward is found — the
154
+ * caller should then surface a {@link MismatchError}.
155
+ */
156
+ tryRecover(args) {
157
+ const { path, currentText, fileHash, edits } = args;
158
+ const snapshot = this.store.byHash(path, fileHash);
159
+ if (!snapshot)
160
+ return null;
161
+ const isHead = isHeadSnapshot(this.store.head(path), snapshot);
162
+ const recoveryWarning = isHead ? RECOVERY_EXTERNAL_WARNING : RECOVERY_SESSION_CHAIN_WARNING;
163
+ const merged = applyEditsToSnapshot(snapshot.text, currentText, edits, recoveryWarning);
164
+ if (merged !== null)
165
+ return merged;
166
+ // Session-chain fallback: the 3-way merge on the version refused.
167
+ // Replay onto current is gated by line-count equality AND
168
+ // anchor-content alignment — see `replaySessionChainOnCurrent`
169
+ // for why both guards together still don't fully prove correctness.
170
+ if (!isHead)
171
+ return replaySessionChainOnCurrent(snapshot.text, currentText, edits);
172
+ return null;
173
+ }
174
+ }
175
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/hashline/recovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * One full-file version observed at a point in time. The tag the model sees is
3
+ * {@link Snapshot.hash}; recovery replays edits against {@link Snapshot.text}.
4
+ */
5
+ export interface Snapshot {
6
+ /** Canonical path this version belongs to. */
7
+ readonly path: string;
8
+ /** Full normalized (LF, no BOM) file text as observed. */
9
+ readonly text: string;
10
+ /** Content-derived tag for {@link Snapshot.text} (see {@link computeFileHash}). */
11
+ readonly hash: string;
12
+ /** Timestamp (ms since epoch) the version was recorded. */
13
+ recordedAt: number;
14
+ }
15
+ /**
16
+ * Storage seam for full-file version snapshots. The patcher calls {@link head}
17
+ * for the latest version of a path and {@link byHash} when it needs the
18
+ * specific historical version a section's stale tag names.
19
+ */
20
+ export declare abstract class SnapshotStore {
21
+ /** Most-recently recorded version for `path`, or `null` if none. */
22
+ abstract head(path: string): Snapshot | null;
23
+ /** Recorded version for `path` whose tag equals `hash`, or `null`. */
24
+ abstract byHash(path: string, hash: string): Snapshot | null;
25
+ /** Record the full normalized text of `path` and return its content tag. */
26
+ abstract record(path: string, fullText: string): string;
27
+ /** Drop the version history for a single path. */
28
+ abstract invalidate(path: string): void;
29
+ /** Drop every version history. */
30
+ abstract clear(): void;
31
+ }
32
+ export interface InMemorySnapshotStoreOptions {
33
+ /** Maximum number of distinct paths tracked at once (default 30). LRU eviction. */
34
+ maxPaths?: number;
35
+ /** Maximum full-file versions retained per path (default 4). Oldest dropped first. */
36
+ maxVersionsPerPath?: number;
37
+ /**
38
+ * Clock used to stamp {@link Snapshot.recordedAt}. Defaults to `Date.now`;
39
+ * injectable so tests stay deterministic. (recordedAt is advisory only —
40
+ * recency ordering uses the version array order, not the timestamp.)
41
+ */
42
+ now?: () => number;
43
+ }
44
+ /**
45
+ * In-memory {@link SnapshotStore} backed by {@link LruMap}. Per-path history is
46
+ * a short ring of full-file versions (oldest dropped first); per-session path
47
+ * tracking is LRU-bounded so cold paths age out automatically.
48
+ *
49
+ * Recording byte-identical content again refreshes recency and reuses the
50
+ * existing tag (read fusion); recording new content unshifts a fresh version
51
+ * onto the front of the path history.
52
+ */
53
+ export declare class InMemorySnapshotStore extends SnapshotStore {
54
+ #private;
55
+ constructor(options?: InMemorySnapshotStoreOptions);
56
+ head(path: string): Snapshot | null;
57
+ byHash(path: string, hash: string): Snapshot | null;
58
+ record(path: string, fullText: string): string;
59
+ invalidate(path: string): void;
60
+ clear(): void;
61
+ }
62
+ //# sourceMappingURL=snapshots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshots.d.ts","sourceRoot":"","sources":["../../src/hashline/snapshots.ts"],"names":[],"mappings":"AA6BA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,8BAAsB,aAAa;IACjC,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAE5C,sEAAsE;IACtE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAE5D,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAEvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAEvC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,IAAI,IAAI;CACvB;AA8CD,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,aAAa;;gBAK1C,OAAO,GAAE,4BAAiC;IAOtD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAInC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAKnD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAoB9C,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI9B,KAAK,IAAI,IAAI;CAGd"}