typebulb 0.49.1 → 0.49.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.
@@ -1,2567 +0,0 @@
1
- // Generated by typebulb — do not edit; overwritten on each typebulb run.
2
-
3
- // cli/agents/pi/server/piPatcherExtension.ts
4
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
5
- import { resolve } from "node:path";
6
- import { Type } from "typebox";
7
-
8
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/utils/textUtils.js
9
- var TextUtils = class _TextUtils {
10
- static NormalizeToLf(text) {
11
- return (text ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
12
- }
13
- static DetectNewline(text) {
14
- if (!text || text.length === 0)
15
- return "\n";
16
- const idx = text.search(/[\r\n]/);
17
- if (idx < 0)
18
- return "\n";
19
- return text[idx] === "\r" ? idx + 1 < text.length && text[idx + 1] === "\n" ? "\r\n" : "\r" : "\n";
20
- }
21
- /// <summary>Normalize + split to LF-only lines.</summary>
22
- static ToLines(text) {
23
- return _TextUtils.NormalizeToLf(text).split("\n");
24
- }
25
- /// <summary>Count of leading characters drawn from `chars` (default: space or tab).</summary>
26
- static CountLeadingWhitespace(s, chars = " ") {
27
- let i = 0;
28
- while (i < s.length && chars.includes(s[i]))
29
- i++;
30
- return i;
31
- }
32
- /// <summary>Maps chat-layer typography (smart quotes, Unicode dashes, special
33
- /// spaces) to ASCII. Match-only: output text is never built from this.
34
- /// Numeric code points throughout: invisible/lookalike literals would make this
35
- /// file unmatchable by byte-exact edit tools, including this patcher's own MCP.</summary>
36
- static NormalizeHomoglyphs(line) {
37
- return [...line].some((c) => c.charCodeAt(0) >= 160) ? [...line].map((c) => _TextUtils.MapHomoglyph(c)).join("") : line;
38
- }
39
- static MapHomoglyph(c) {
40
- const cp = c.codePointAt(0);
41
- if (cp >= 8216 && cp <= 8219)
42
- return "'";
43
- if (cp >= 8220 && cp <= 8223)
44
- return '"';
45
- if (cp >= 8208 && cp <= 8213 || cp === 8722)
46
- return "-";
47
- if (cp === 160 || cp >= 8194 && cp <= 8202 || cp === 8239 || cp === 8287 || cp === 12288)
48
- return " ";
49
- return c;
50
- }
51
- /// <summary>Removes zero-width/control code points for match-tolerant
52
- /// comparison. Match-only, like NormalizeHomoglyphs: output text is never
53
- /// built from this.</summary>
54
- static StripInvisibles(line) {
55
- return [...line].some((c) => _TextUtils.IsInvisible(c)) ? [...line].filter((c) => !_TextUtils.IsInvisible(c)).join("") : line;
56
- }
57
- /// <summary>Folds NFC canonical equivalence and fullwidth ASCII forms
58
- /// (U+FF01..U+FF5E) for match-tolerant comparison. Match-only, like
59
- /// NormalizeHomoglyphs. The NFKC compat remainder (ligatures, superscripts)
60
- /// stays unfolded: visually distinct content must not cross-match.</summary>
61
- static FoldCanonicalAndFullwidth(line) {
62
- if (![...line].some((c) => c.charCodeAt(0) >= 768))
63
- return line;
64
- const folded = [...line].map((c) => {
65
- const cp = c.charCodeAt(0);
66
- return cp >= 65281 && cp <= 65374 ? String.fromCharCode(cp - 65248) : c;
67
- }).join("");
68
- return _TextUtils.HasUnpairedSurrogate(folded) ? folded : folded.normalize("NFC");
69
- }
70
- static HasUnpairedSurrogate(s) {
71
- for (let i = 0; i < s.length; i++) {
72
- const cp = s.charCodeAt(i);
73
- if (cp < 55296 || cp > 57343)
74
- continue;
75
- const next = i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
76
- if (cp > 56319 || next < 56320 || next > 57343)
77
- return true;
78
- i++;
79
- }
80
- return false;
81
- }
82
- // Zero-width/control code points that render as nothing (or reorder text):
83
- // the classes JSON-escape mangling degrades into. Tab excluded: real content.
84
- // Numeric code points, not escapes or literals: they survive tool boundaries
85
- // that decode backslash escapes or eat raw control chars.
86
- static IsInvisible(c) {
87
- const cp = c.codePointAt(0);
88
- if (cp === 9)
89
- return false;
90
- return cp < 32 || cp >= 127 && cp <= 159 || cp === 173 || cp === 1564 || cp === 6158 || cp >= 8203 && cp <= 8207 || cp >= 8234 && cp <= 8238 || cp >= 8288 && cp <= 8292 || cp >= 8294 && cp <= 8297 || cp === 65279;
91
- }
92
- // En/em dashes are exempt from the audit: this authoring stack emits them in all
93
- // comment prose, so warning on them fires on nearly every apply and trains readers
94
- // to skip the audit (warning-blindness). They stay in the homoglyph table, so match
95
- // tolerance is unchanged. Curly quotes still warn: the motivating mangle was a quote
96
- // lookalike inside code, and prose here uses straight quotes — a curly quote is more
97
- // likely damage than intent.
98
- static IsIntentionalTypography(c) {
99
- const cp = c.codePointAt(0);
100
- return cp === 8211 || cp === 8212;
101
- }
102
- /// <summary>Names the invisible/lookalike code points in the given lines, or null if
103
- /// there are none. Escape mangling at a tool-call boundary writes unintended bytes
104
- /// silently; intent is unrecoverable afterwards, so the audit states the facts (which
105
- /// code point, what it resembles, how many) and leaves the judgment to the caller.
106
- /// </summary>
107
- static DescribeSuspectCodePoints(lines) {
108
- const counts = /* @__PURE__ */ new Map();
109
- for (const line of lines)
110
- for (const c of line)
111
- if (_TextUtils.IsInvisible(c) || _TextUtils.MapHomoglyph(c) !== c && !_TextUtils.IsIntentionalTypography(c))
112
- counts.set(c, (counts.get(c) ?? 0) + 1);
113
- if (counts.size === 0)
114
- return null;
115
- return [...counts.entries()].sort((a, b) => a[0].codePointAt(0) - b[0].codePointAt(0)).map(([c, n]) => {
116
- const mapped = _TextUtils.MapHomoglyph(c);
117
- const what = _TextUtils.IsInvisible(c) ? "invisible" : mapped === " " ? "looks like a space" : `looks like ${mapped}`;
118
- const hex = c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
119
- return `U+${hex} (${what}) x${n}`;
120
- }).join(", ");
121
- }
122
- /// <summary>Round-trip variant that accepts updated LF lines.</summary>
123
- static RoundTripWhitespace(originalSnapshot, updatedLines) {
124
- const joined = updatedLines.join("\n");
125
- const origHadTrailing = originalSnapshot.endsWith("\n") || originalSnapshot.endsWith("\r\n") || originalSnapshot.length === 0;
126
- let result = joined;
127
- if (origHadTrailing && !joined.endsWith("\n") && joined.length > 0)
128
- result += "\n";
129
- else if (!origHadTrailing && joined.endsWith("\n"))
130
- result = result.slice(0, -1);
131
- const newline = _TextUtils.DetectNewline(originalSnapshot);
132
- return newline === "\n" ? result : result.replace(/\n/g, newline);
133
- }
134
- };
135
-
136
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/utils/arrayUtils.js
137
- var ArrayUtils = class {
138
- /// <summary>
139
- /// Groups items by key, preserving first-seen key order (like LINQ's GroupBy).
140
- /// </summary>
141
- static GroupBy(items, keySelector) {
142
- const map = /* @__PURE__ */ new Map();
143
- for (const item of items) {
144
- const key = keySelector(item);
145
- const arr = map.get(key) || [];
146
- arr.push(item);
147
- map.set(key, arr);
148
- }
149
- const result = [];
150
- for (const [key, values] of map.entries())
151
- result.push({ key, values });
152
- return result;
153
- }
154
- /// <summary>
155
- /// Returns a new array sorted ascending by key (stable, non-mutating, like LINQ's OrderBy).
156
- /// </summary>
157
- static OrderBy(items, keySelector) {
158
- return [...items].sort((a, b) => {
159
- const ka = keySelector(a), kb = keySelector(b);
160
- return ka < kb ? -1 : ka > kb ? 1 : 0;
161
- });
162
- }
163
- /// <summary>
164
- /// Returns a new array sorted descending by key (stable, non-mutating, like LINQ's OrderByDescending).
165
- /// </summary>
166
- static OrderByDescending(items, keySelector) {
167
- return [...items].sort((a, b) => {
168
- const ka = keySelector(a), kb = keySelector(b);
169
- return ka > kb ? -1 : ka < kb ? 1 : 0;
170
- });
171
- }
172
- };
173
-
174
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/models.js
175
- var PatchInputFile = class {
176
- Key;
177
- InputFullText;
178
- InputSelectedText;
179
- constructor(Key, InputFullText = "", InputSelectedText = "") {
180
- this.Key = Key;
181
- this.InputFullText = InputFullText;
182
- this.InputSelectedText = InputSelectedText;
183
- }
184
- };
185
- var PatchOptions = class {
186
- ContinueOnError;
187
- MaxErrorIterations;
188
- ContextWindowMax;
189
- MaxChunksPerHunk;
190
- SanitizeDiff;
191
- Truncation;
192
- ControlChars;
193
- constructor(ContinueOnError = true, MaxErrorIterations = 10, ContextWindowMax = 8, MaxChunksPerHunk = 64, SanitizeDiff = true, Truncation = "warn", ControlChars = "error") {
194
- this.ContinueOnError = ContinueOnError;
195
- this.MaxErrorIterations = MaxErrorIterations;
196
- this.ContextWindowMax = ContextWindowMax;
197
- this.MaxChunksPerHunk = MaxChunksPerHunk;
198
- this.SanitizeDiff = SanitizeDiff;
199
- this.Truncation = Truncation;
200
- this.ControlChars = ControlChars;
201
- }
202
- };
203
- var PatchOutputFile = class {
204
- Key;
205
- Fuzz;
206
- Edits;
207
- InputSelectedText;
208
- InputFullText;
209
- OutputFullText;
210
- Errors;
211
- AlreadyAppliedCount;
212
- Notices;
213
- constructor(Key, Fuzz, Edits, InputSelectedText, InputFullText, OutputFullText, Errors, AlreadyAppliedCount = 0, Notices = []) {
214
- this.Key = Key;
215
- this.Fuzz = Fuzz;
216
- this.Edits = Edits;
217
- this.InputSelectedText = InputSelectedText;
218
- this.InputFullText = InputFullText;
219
- this.OutputFullText = OutputFullText;
220
- this.Errors = Errors;
221
- this.AlreadyAppliedCount = AlreadyAppliedCount;
222
- this.Notices = Notices;
223
- }
224
- };
225
- var PatchOutput = class {
226
- Files;
227
- constructor(Files) {
228
- this.Files = Files;
229
- }
230
- };
231
- var LineType;
232
- (function(LineType2) {
233
- LineType2[LineType2["Context"] = 0] = "Context";
234
- LineType2[LineType2["Delete"] = 1] = "Delete";
235
- LineType2[LineType2["Insert"] = 2] = "Insert";
236
- })(LineType || (LineType = {}));
237
- var DiffLine = class {
238
- Type;
239
- Text;
240
- CollapsedFrom;
241
- constructor(Type2, Text, CollapsedFrom = null) {
242
- this.Type = Type2;
243
- this.Text = Text;
244
- this.CollapsedFrom = CollapsedFrom;
245
- }
246
- };
247
- var Hunk = class {
248
- Key;
249
- OldText;
250
- NewText;
251
- OldStart;
252
- constructor(Key, OldText, NewText, OldStart = -1) {
253
- this.Key = Key;
254
- this.OldText = OldText;
255
- this.NewText = NewText;
256
- this.OldStart = OldStart;
257
- }
258
- Lines = [];
259
- /// <summary>
260
- /// 1-based start position for the new side when available (from +c,d header), otherwise -1.
261
- /// Unused, but here for Unified Diff completeness.
262
- /// </summary>
263
- NewStart = -1;
264
- /// <summary>
265
- /// 0-based line number in the original diff text where this hunk's body (first diff body line) starts.
266
- /// -1 when unavailable (e.g., parser could not determine it).
267
- /// </summary>
268
- DiffBodyStartLine = -1;
269
- // Set by the parser under TruncationPolicy 'warn' on the final hunk when it
270
- // carried the tear signature.
271
- TruncationSuspected = false;
272
- // Set by the parser under ControlCharPolicy 'warn' when this hunk's insert
273
- // lines carried raw control characters.
274
- ControlCharsSuspected = false;
275
- // Error-reporting stand-in Chunk for a hunk that was never anchored: its lines
276
- // and diff location, no match.
277
- ToUnanchoredChunk() {
278
- return new Chunk([], this.Lines.filter((l) => l.Type == LineType.Delete).map((l) => l.Text), this.Lines.filter((l) => l.Type == LineType.Insert).map((l) => l.Text), [], UniqueMatch.NotFound, new DiffLocation(this));
279
- }
280
- };
281
- var Edit = class _Edit {
282
- LineIndex;
283
- DeleteLines;
284
- InsertLines;
285
- Fuzz;
286
- constructor(LineIndex, DeleteLines, InsertLines, Fuzz = 0) {
287
- this.LineIndex = LineIndex;
288
- this.DeleteLines = DeleteLines;
289
- this.InsertLines = InsertLines;
290
- this.Fuzz = Fuzz;
291
- }
292
- Shift(lineDelta) {
293
- return new _Edit(this.LineIndex + lineDelta, this.DeleteLines, this.InsertLines, this.Fuzz);
294
- }
295
- ApplyTo(lines) {
296
- if (this.LineIndex < 0 || this.LineIndex > lines.length)
297
- return;
298
- const deleteCount = Math.min(this.DeleteLines.length, lines.length - this.LineIndex);
299
- if (deleteCount > 0)
300
- lines.splice(this.LineIndex, deleteCount);
301
- if (this.InsertLines.length > 0)
302
- lines.splice(this.LineIndex, 0, ...this.InsertLines);
303
- }
304
- };
305
- var DiffLocation = class {
306
- StartLine;
307
- Length;
308
- constructor(startOrHunk, lengthOrStartLine, endLine) {
309
- if (typeof startOrHunk === "number") {
310
- this.StartLine = startOrHunk;
311
- this.Length = lengthOrStartLine ?? 0;
312
- } else {
313
- const hunk = startOrHunk;
314
- const startLine = lengthOrStartLine ?? 0;
315
- const end = endLine ?? hunk.Lines.length - 1;
316
- this.StartLine = hunk.DiffBodyStartLine + startLine + 1;
317
- this.Length = end - startLine + 1;
318
- }
319
- }
320
- };
321
- var FileHunkGroup = class {
322
- Key;
323
- Hunks;
324
- ContextOnlyHunkLines;
325
- constructor(Key, Hunks, ContextOnlyHunkLines = []) {
326
- this.Key = Key;
327
- this.Hunks = Hunks;
328
- this.ContextOnlyHunkLines = ContextOnlyHunkLines;
329
- }
330
- };
331
- var Chunk = class _Chunk {
332
- ContextBefore;
333
- DeleteLines;
334
- InsertLines;
335
- ContextAfter;
336
- Match;
337
- DiffLocation;
338
- constructor(ContextBefore, DeleteLines, InsertLines, ContextAfter, Match2, DiffLocation2) {
339
- this.ContextBefore = ContextBefore;
340
- this.DeleteLines = DeleteLines;
341
- this.InsertLines = InsertLines;
342
- this.ContextAfter = ContextAfter;
343
- this.Match = Match2;
344
- this.DiffLocation = DiffLocation2;
345
- }
346
- get IsPureInsert() {
347
- return this.DeleteLines.length == 0 && this.InsertLines.length > 0;
348
- }
349
- get IsPureDelete() {
350
- return this.DeleteLines.length > 0 && this.InsertLines.length == 0;
351
- }
352
- HasContextLines() {
353
- return this.ContextBefore.length > 0 || this.ContextAfter.length > 0;
354
- }
355
- DeleteLinesWithContext() {
356
- return [...this.ContextBefore, ...this.DeleteLines, ...this.ContextAfter];
357
- }
358
- InsertLinesWithContext() {
359
- return [...this.ContextBefore, ...this.InsertLines, ...this.ContextAfter];
360
- }
361
- // The FULL context runs the diff supplied adjacent to this chunk's block, bounded
362
- // by sibling blocks and hunk edges, in file order. A fact about the diff rather
363
- // than the match — ContextBefore/After hold only the window the progressive-growth
364
- // search consumed — so the dropped-context audit can see every supplied line, and
365
- // `with` rewrites (fallback anchor, tiebreak, splice) carry the stamps forward.
366
- SuppliedContextBefore = [];
367
- SuppliedContextAfter = [];
368
- with(values) {
369
- const chunk = new _Chunk(values.ContextBefore ?? this.ContextBefore, values.DeleteLines ?? this.DeleteLines, values.InsertLines ?? this.InsertLines, values.ContextAfter ?? this.ContextAfter, values.Match ?? this.Match, values.DiffLocation ?? this.DiffLocation);
370
- chunk.SuppliedContextBefore = values.SuppliedContextBefore ?? this.SuppliedContextBefore;
371
- chunk.SuppliedContextAfter = values.SuppliedContextAfter ?? this.SuppliedContextAfter;
372
- return chunk;
373
- }
374
- };
375
- var MatchState;
376
- (function(MatchState2) {
377
- MatchState2[MatchState2["Success"] = 0] = "Success";
378
- MatchState2[MatchState2["Ambiguous"] = 1] = "Ambiguous";
379
- MatchState2[MatchState2["NotFound"] = 2] = "NotFound";
380
- })(MatchState || (MatchState = {}));
381
- var UniqueMatch = class _UniqueMatch {
382
- State;
383
- LineIndex;
384
- Fuzz;
385
- constructor(State, LineIndex = -1, Fuzz = 0) {
386
- this.State = State;
387
- this.LineIndex = LineIndex;
388
- this.Fuzz = Fuzz;
389
- }
390
- get IsAmbiguous() {
391
- return this.State == MatchState.Ambiguous;
392
- }
393
- get IsNotFound() {
394
- return this.State == MatchState.NotFound;
395
- }
396
- get IsSuccess() {
397
- return this.State == MatchState.Success;
398
- }
399
- static get Ambiguous() {
400
- return new _UniqueMatch(MatchState.Ambiguous);
401
- }
402
- static get NotFound() {
403
- return new _UniqueMatch(MatchState.NotFound);
404
- }
405
- with(values) {
406
- return new _UniqueMatch(values.State ?? this.State, values.LineIndex ?? this.LineIndex, values.Fuzz ?? this.Fuzz);
407
- }
408
- };
409
-
410
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/utils/yaml.js
411
- var Yaml = class _Yaml {
412
- _sb = [];
413
- // -------- Public API --------
414
- AppendLine(text) {
415
- this._sb.push(text + "\n");
416
- }
417
- Section(keyWithIndent) {
418
- const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
419
- this._sb.push(`${indent}${key}:
420
- `);
421
- }
422
- Scalar(keyWithIndent, value) {
423
- const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
424
- this._sb.push(`${indent}${key}: ${value}
425
- `);
426
- }
427
- Folded(keyWithIndent, value, contentIndent = 2, chompStrip = true) {
428
- this.WriteBlockScalar(">", keyWithIndent, _Yaml.SplitLines(value), contentIndent, chompStrip);
429
- }
430
- Block(keyWithIndent, lines, contentIndent = 2, chompStrip = true) {
431
- this.WriteBlockScalar("|", keyWithIndent, lines ?? [], contentIndent, chompStrip);
432
- }
433
- ToString() {
434
- return this._sb.join("");
435
- }
436
- static SplitIndentAndKey(keyWithIndent) {
437
- const leadingSpaces = TextUtils.CountLeadingWhitespace(keyWithIndent, " ");
438
- const indentStr = " ".repeat(leadingSpaces);
439
- const key = keyWithIndent.replace(/^\s+/, "");
440
- return [indentStr, key];
441
- }
442
- static SplitLines(value) {
443
- if (value == null || value.length == 0)
444
- return [];
445
- const lf = value.indexOf("\r") >= 0 ? value.replace(/\r\n/g, "\n").replace(/\r/g, "\n") : value;
446
- return lf.split("\n");
447
- }
448
- WriteBlockScalar(style, keyWithIndent, lines, contentIndent, chompStrip) {
449
- const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
450
- const chomp = chompStrip ? "-" : "+";
451
- this._sb.push(`${indent}${key}: ${style}${contentIndent}${chomp}
452
- `);
453
- const pad = " ".repeat(contentIndent);
454
- let any = false;
455
- for (const line of lines) {
456
- any = true;
457
- this._sb.push(`${indent}${pad}${line}
458
- `);
459
- }
460
- if (!any) {
461
- this._sb.push(`${indent}${pad}
462
- `);
463
- }
464
- }
465
- };
466
-
467
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/exceptions.js
468
- var PatchException = class extends Error {
469
- Error;
470
- constructor(error) {
471
- super(error.SuggestedFixYaml);
472
- this.Error = error;
473
- }
474
- };
475
- var PatchParserException = class extends Error {
476
- constructor(message) {
477
- super(message ?? "Malformed diff patch");
478
- }
479
- };
480
- var PatchError = class {
481
- Type;
482
- FailedMatch;
483
- // The file key(s) the error concerns: the foreign key a FileMismatch named, or
484
- // the comma-joined candidate keys of an ambiguous headerless routing; null otherwise.
485
- FileKey;
486
- // Diagnosis computed against the file at error time (e.g. the quoted line
487
- // occurs inside one longer file line); null when no confident diagnosis.
488
- Hint;
489
- constructor(errorType, chunk, fileKey = null, hint = null) {
490
- this.Type = errorType;
491
- this.FailedMatch = chunk;
492
- this.FileKey = fileKey;
493
- this.Hint = hint;
494
- }
495
- toString() {
496
- return `Failed to patch text with the diff provided.
497
- ${this.SuggestedFixYaml}`;
498
- }
499
- get SuggestedFixYaml() {
500
- const SummaryFor = (type) => ({
501
- "MatchNotFound": "Matching lines not found; make sure the diff's context and deleted lines exactly match the original text.",
502
- "MatchAmbiguous": "Matched multiple locations; make sure there are enough context lines to uniquely identify an edit.",
503
- "ChunkDuplicated": "Duplicate chunks.",
504
- "ChunkOverlapping": "Overlapping chunks.",
505
- "FileMismatch": "The hunk's file header names a file that is not being patched, so it was not applied. Send hunks only for the file(s) being patched."
506
- })[type] ?? "Patch failed.";
507
- const diffLoc = this.FailedMatch.DiffLocation;
508
- const y = new Yaml();
509
- y.AppendLine("PatchError:");
510
- y.Scalar(" Type", this.Type.toString());
511
- if (this.FileKey != null)
512
- y.Scalar(" File", this.FileKey);
513
- y.Folded(" Summary", SummaryFor(this.Type));
514
- if (this.Hint != null)
515
- y.Folded(" Hint", this.Hint);
516
- y.Section(" Details");
517
- y.Section(" FailedMatch");
518
- y.Block(" ContextBefore", this.FailedMatch.ContextBefore);
519
- y.Block(" DeleteLines", this.FailedMatch.DeleteLines);
520
- y.Block(" InsertLines", this.FailedMatch.InsertLines);
521
- y.Block(" ContextAfter", this.FailedMatch.ContextAfter);
522
- y.Section(" DiffLocation");
523
- y.Scalar(" StartLine", diffLoc.StartLine.toString());
524
- y.Scalar(" Length", diffLoc.Length.toString());
525
- return y.ToString();
526
- }
527
- };
528
-
529
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/applier/guards.js
530
- var DuplicateGuard = class _DuplicateGuard {
531
- // Two chunks that resolve to the same anchor with the same deletes and inserts are
532
- // provably the same edit (an LLM re-emitting a hunk). Applying it once is
533
- // unambiguously correct, so keep the first occurrence and drop the rest — the lenient
534
- // move, with no false-positive risk: genuinely distinct edits differ in key
535
- // (anchor or content) and fall through to OverlapGuard.
536
- static Dedupe(args) {
537
- const seen = /* @__PURE__ */ new Set();
538
- const kept = [];
539
- for (const chunk of args.Chunks) {
540
- const key = _DuplicateGuard.BuildKey(chunk);
541
- if (!seen.has(key)) {
542
- seen.add(key);
543
- kept.push(chunk);
544
- }
545
- }
546
- return new InputChunks(args.InputFullText, kept, args.Options);
547
- }
548
- static BuildKey(c) {
549
- let sb = "";
550
- sb += `${c.Match?.LineIndex ?? -1}
551
- `;
552
- for (const l of c.DeleteLines)
553
- sb += `${l}
554
- `;
555
- sb += `\u2192
556
- `;
557
- for (const l of c.InsertLines)
558
- sb += `${l}
559
- `;
560
- return sb.toString();
561
- }
562
- };
563
- var OverlapGuard = class {
564
- static Guard(args) {
565
- const chunks = ArrayUtils.OrderBy(args.Chunks, (c) => c.Match?.LineIndex ?? -1);
566
- let currentEnd = 0;
567
- for (const chunk of chunks) {
568
- const idx = chunk.Match?.LineIndex ?? -1;
569
- if (idx < 0)
570
- continue;
571
- if (idx < currentEnd)
572
- throw new PatchException(new PatchError("ChunkOverlapping", chunk));
573
- if (chunk.DeleteLines.length > 0)
574
- currentEnd = idx + chunk.DeleteLines.length;
575
- }
576
- }
577
- };
578
-
579
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/applier/editMinimizer.js
580
- var EditMinimizer = class _EditMinimizer {
581
- static Same(a, b) {
582
- return a === b;
583
- }
584
- /// <summary>
585
- /// Convert an edit into a sequence of minimal edits, by excluding all unchanged lines.
586
- /// </summary>
587
- static *ToMinimalEdits(edit) {
588
- const deleted = edit.DeleteLines;
589
- const inserted = edit.InsertLines;
590
- const maxLength = Math.max(deleted.length, inserted.length);
591
- let diffStart = -1;
592
- for (let i = 0; i <= maxLength; i++) {
593
- const withinDeleted = i < deleted.length;
594
- const withinInserted = i < inserted.length;
595
- const atEnd = i == maxLength;
596
- const isMatch = withinDeleted && withinInserted && _EditMinimizer.Same(deleted[i], inserted[i]);
597
- const isDiff = !atEnd && (!withinDeleted || !withinInserted || !isMatch);
598
- const isBoundary = atEnd || isMatch;
599
- if (diffStart == -1 && isDiff) {
600
- diffStart = i;
601
- } else if (diffStart != -1 && isBoundary) {
602
- const delCount = Math.max(0, Math.min(i, deleted.length) - diffStart);
603
- const insCount = Math.max(0, Math.min(i, inserted.length) - diffStart);
604
- const delBlock = delCount <= 0 ? [] : deleted.slice(diffStart, diffStart + delCount);
605
- const insBlock = insCount <= 0 ? [] : inserted.slice(diffStart, diffStart + insCount);
606
- yield new Edit(edit.LineIndex + diffStart, delBlock, insBlock, edit.Fuzz);
607
- diffStart = -1;
608
- }
609
- }
610
- }
611
- };
612
-
613
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/applier/indenter.js
614
- var Anchor = class {
615
- BaseIndent;
616
- Shift;
617
- constructor(BaseIndent, Shift) {
618
- this.BaseIndent = BaseIndent;
619
- this.Shift = Shift;
620
- }
621
- };
622
- var Indenter = class _Indenter {
623
- static DefaultTabSize = 4;
624
- /// <summary>
625
- /// Aligns an inserted block (<see cref="Chunk.InsertLines"/>) to the file’s
626
- /// indent context at <see cref="Chunk.LineIndex"/>, leveraging
627
- /// ContextBefore / ContextAfter when available.
628
- /// </summary>
629
- static AlignInsert(lines, chunk) {
630
- const insert = chunk.InsertLines;
631
- if (insert.length == 0)
632
- return insert;
633
- const tabSize = _Indenter.InferTabSize(lines.concat(insert));
634
- const anchor = _Indenter.FindAnchor(lines, chunk.ContextBefore, chunk.Match.LineIndex - 1, true, tabSize) ?? _Indenter.FindAnchor(lines, chunk.ContextAfter, chunk.Match.LineIndex, false, tabSize) ?? new Anchor("", 0);
635
- const reindented = new Set(chunk.DeleteLines.map((l) => _Indenter.SplitIndent(l)[1]));
636
- const blankKeep = new Set(insert.filter((l) => !_Indenter.IsBlank(l)).map(_Indenter.LeadingWs).concat([anchor.BaseIndent]));
637
- const tabStyle = anchor.BaseIndent.length > 0 ? anchor.BaseIndent.indexOf(" ") >= 0 : lines.some((l) => l.startsWith(" "));
638
- return insert.map((l) => _Indenter.Adjust(l, anchor, tabSize, tabStyle, reindented, blankKeep));
639
- }
640
- static FindAnchor(lines, contextLines, startIndex, backward, tabSize) {
641
- if (contextLines.length == 0)
642
- return null;
643
- const [ctxWs, ctxTrim] = _Indenter.SplitIndent(contextLines[backward ? contextLines.length - 1 : 0]);
644
- const ctxW = _Indenter.VisualWidth(ctxWs, tabSize);
645
- const step = backward ? -1 : 1;
646
- const end = backward ? -1 : lines.length;
647
- for (let i = startIndex; i != end; i += step) {
648
- const [ws, trim] = _Indenter.SplitIndent(lines[i]);
649
- if (trim != ctxTrim)
650
- continue;
651
- return new Anchor(ws, _Indenter.VisualWidth(ws, tabSize) - ctxW);
652
- }
653
- return null;
654
- }
655
- static Adjust(line, anchor, tabSize, tabStyle, reindented, blankKeep) {
656
- if (_Indenter.IsBlank(line))
657
- return anchor.Shift == 0 && blankKeep.has(line) ? line : "";
658
- const [ws, content] = _Indenter.SplitIndent(line);
659
- const wsWidth = _Indenter.VisualWidth(ws, tabSize);
660
- const target = Math.max(0, wsWidth + anchor.Shift);
661
- if (wsWidth == target && (ws.indexOf(" ") >= 0 == tabStyle || reindented.has(content)))
662
- return ws + content;
663
- return _Indenter.BuildIndent(anchor.BaseIndent, target, tabSize) + content;
664
- }
665
- static LeadingWs(line) {
666
- return _Indenter.SplitIndent(line)[0];
667
- }
668
- static IsBlank(s) {
669
- return s == null || s.length == 0 || Array.from(s).every((c) => c === " " || c === " ");
670
- }
671
- static SplitIndent(line) {
672
- if (line == null || line.length == 0)
673
- return ["", ""];
674
- const i = TextUtils.CountLeadingWhitespace(line);
675
- return [line.substring(0, i), line.substring(i)];
676
- }
677
- static InferTabSize(lines) {
678
- for (const ws of Array.from(lines).map(_Indenter.LeadingWs)) {
679
- const lastTab = ws.lastIndexOf(" ");
680
- if (lastTab >= 0) {
681
- const spaces = ws.length - lastTab - 1;
682
- if (spaces > 0)
683
- return spaces;
684
- }
685
- }
686
- return _Indenter.DefaultTabSize;
687
- }
688
- static VisualWidth(indent, tabSize) {
689
- return Array.from(indent).map((c) => c == " " ? tabSize : 1).reduce((a, b) => a + b, 0);
690
- }
691
- static BuildIndent(baseIndent, targetW, tabSize) {
692
- if (targetW <= 0)
693
- return "";
694
- const baseW = _Indenter.VisualWidth(baseIndent, tabSize);
695
- const delta = targetW - baseW;
696
- if (delta == 0)
697
- return baseIndent;
698
- if (delta > 0)
699
- return baseIndent + " ".repeat(delta);
700
- return baseIndent.indexOf(" ") >= 0 ? " ".repeat(Math.floor(targetW / tabSize)) + " ".repeat(targetW % tabSize) : " ".repeat(targetW);
701
- }
702
- };
703
-
704
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/applier/matchHint.js
705
- var MatchHint = class _MatchHint {
706
- // A quoted line inside exactly one longer file line is the prose line-model
707
- // mistake: the author's "line" (a sentence of an unwrapped paragraph, or a
708
- // re-flowed hard wrap) is a slice of the file's physical line. Matching stays
709
- // refused — a head fragment is byte-identical to an elided whole-line delete,
710
- // so either auto-reading risks silent corruption — but naming the line is
711
- // correct under both readings: the retry re-quotes it whole.
712
- static SubLine(chunk, lines) {
713
- for (const q of chunk.DeleteLinesWithContext()) {
714
- if (q.trim().length < _MatchHint.MinHintLineContent)
715
- continue;
716
- if (lines.includes(q))
717
- continue;
718
- const containing = [];
719
- for (let i = 0; i < lines.length && containing.length < 2; i++)
720
- if (lines[i].length > q.length && lines[i].includes(q))
721
- containing.push(i);
722
- if (containing.length != 1)
723
- continue;
724
- return `The diff line "${_MatchHint.Preview(q)}" matches only PART of line ${containing[0] + 1}, which is ${lines[containing[0]].length} characters long \u2014 in prose, a whole paragraph is often one physical line. Re-send the diff quoting entire lines exactly.`;
725
- }
726
- return null;
727
- }
728
- static Preview(s) {
729
- const t = s.trim();
730
- return t.length <= 60 ? t : t.slice(0, 57) + "...";
731
- }
732
- // Trimmed length a quoted line must reach before containment counts as
733
- // evidence: short strings occur inside longer lines by coincidence.
734
- static MinHintLineContent = 12;
735
- };
736
-
737
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/applier/chunkApplier.js
738
- var InputChunks = class {
739
- InputFullText;
740
- Chunks;
741
- Options;
742
- constructor(InputFullText, Chunks, Options) {
743
- this.InputFullText = InputFullText;
744
- this.Chunks = Chunks;
745
- this.Options = Options;
746
- }
747
- };
748
- var OutputEdits = class {
749
- OutputText;
750
- Edits;
751
- Errors;
752
- constructor(OutputText, Edits, Errors) {
753
- this.OutputText = OutputText;
754
- this.Edits = Edits;
755
- this.Errors = Errors;
756
- }
757
- };
758
- var ChunkApplier = class _ChunkApplier {
759
- // NOTE: on partial failure, Apply is all-or-nothing — it returns the ORIGINAL
760
- // text untouched alongside the errors, never a best-effort partial application.
761
- static Apply(targetText, chunks, options) {
762
- const inputChunks = new InputChunks(targetText, chunks, options);
763
- if (!options.ContinueOnError)
764
- return _ChunkApplier.ApplyChunks(inputChunks);
765
- const errors = [];
766
- for (let attempt = 0; attempt < options.MaxErrorIterations; attempt++) {
767
- try {
768
- const r = _ChunkApplier.ApplyChunks(inputChunks);
769
- if (errors.length)
770
- return new OutputEdits(targetText, [], errors);
771
- return r;
772
- } catch (ex) {
773
- if (ex instanceof PatchException) {
774
- errors.push(ex.Error);
775
- const idx = chunks.indexOf(ex.Error.FailedMatch);
776
- if (idx >= 0)
777
- chunks.splice(idx, 1);
778
- } else
779
- throw ex;
780
- }
781
- }
782
- return new OutputEdits(targetText, [], errors);
783
- }
784
- static ApplyChunks(args) {
785
- args = DuplicateGuard.Dedupe(args);
786
- OverlapGuard.Guard(args);
787
- const lines = TextUtils.ToLines(args.InputFullText);
788
- const edits = args.Chunks.map((chunk) => _ChunkApplier.CreateEdit(chunk, lines));
789
- const workingLines = Array.from(lines);
790
- for (const edit of ArrayUtils.OrderByDescending(edits, (e) => e.LineIndex))
791
- edit.ApplyTo(workingLines);
792
- const patched = TextUtils.RoundTripWhitespace(args.InputFullText, workingLines);
793
- const minimalEdits = edits.flatMap((e) => Array.from(EditMinimizer.ToMinimalEdits(e)));
794
- return new OutputEdits(patched, minimalEdits, []);
795
- }
796
- static CreateEdit(chunk, lines) {
797
- const match = chunk.Match;
798
- if (match.IsAmbiguous)
799
- throw new PatchException(new PatchError("MatchAmbiguous", chunk));
800
- if (match.IsNotFound)
801
- throw new PatchException(new PatchError("MatchNotFound", chunk, null, MatchHint.SubLine(chunk, lines)));
802
- return new Edit(match.LineIndex, Array.from(chunk.DeleteLines), Indenter.AlignInsert(lines, chunk), match.Fuzz);
803
- }
804
- };
805
-
806
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/heuristics.js
807
- var DiffContentHeuristics = class _DiffContentHeuristics {
808
- /**
809
- * Checks if a line starts with a doubled marker (++ or --)
810
- */
811
- static IsDoubleMarker(line) {
812
- return line.length > 1 && line[0] == line[1] && (line[0] == "-" || line[0] == "+");
813
- }
814
- /**
815
- * Determines if a doubled marker represents content rather than diff syntax.
816
- *
817
- * Examples:
818
- * - `--bg-color` → true (CSS variable, content)
819
- * - `--` → true (exactly two hyphens, content)
820
- * - `-- text` → false (deletion of markdown list `- text`, diff syntax)
821
- * - `---comment` → false (deletion of `--comment`, diff syntax)
822
- * - `----` → false (deletion of `--`, diff syntax)
823
- * - `++var` → true (C++ code, content)
824
- * - `++ text` → false (insertion of `+ text`, diff syntax)
825
- * - `+++` → false (insertion of `+`, diff syntax)
826
- */
827
- static IsDoubleMarkerContent(line) {
828
- if (!_DiffContentHeuristics.IsDoubleMarker(line))
829
- return false;
830
- if (line.length == 2)
831
- return true;
832
- return line[2] != line[0] && line[2] != " ";
833
- }
834
- /**
835
- * A '++X' line that is a candidate for the sloppy doubled-insert-marker
836
- * collapse ('++X' → insert 'X'). Tripled markers are proper syntax
837
- * ('+++X' = insert '++X') and never candidates.
838
- */
839
- static IsSloppyDoublePlus(line) {
840
- return line.length > 1 && line[0] == "+" && line[1] == "+" && (line.length == 2 || line[2] != "+");
841
- }
842
- /**
843
- * True when the hunk body proves the edit region is itself diff-shaped:
844
- * a context or deletion line whose CONTENT starts with '+' or '-' exists
845
- * verbatim in the file. In such a region, doubled markers are diff syntax
846
- * over diff-shaped content, not sloppy doubling.
847
- */
848
- static IsDiffShapedRegion(rawLines, fileLines) {
849
- if (!fileLines || fileLines.length == 0)
850
- return false;
851
- for (const l of rawLines) {
852
- if (l.length < 2 || l[0] != " " && l[0] != "-")
853
- continue;
854
- if (l[1] != "+" && l[1] != "-")
855
- continue;
856
- const content = l.substring(1);
857
- if (fileLines.some((f) => f == content))
858
- return true;
859
- }
860
- return false;
861
- }
862
- /**
863
- * Validates whether a line (after stripping indentation) represents valid diff syntax.
864
- * Used by StripIndent to determine if indentation should be removed.
865
- *
866
- * Returns false for content that looks like diff markers:
867
- * - `--text` (CSS variables)
868
- * - `-abc-*` (hyphenated identifiers like -webkit-*)
869
- */
870
- static IsValidDiffLineStart(line) {
871
- if (line.length == 0)
872
- return false;
873
- const first = line[0];
874
- if (first == " " || first == "+")
875
- return true;
876
- if (first == "-") {
877
- if (line.length == 1)
878
- return true;
879
- if (line[1] == "-")
880
- return line.length > 2 && line[2] == "-";
881
- if (_DiffContentHeuristics.IsHyphenatedIdentifier(line))
882
- return false;
883
- return true;
884
- }
885
- return false;
886
- }
887
- /**
888
- * Pattern: -[a-z]+- (e.g., -webkit-*, -moz-*)
889
- * If hyphen is followed by letters then another hyphen, it's structural content.
890
- */
891
- static IsHyphenatedIdentifier(line) {
892
- if (line.length < 3 || line[0] != "-")
893
- return false;
894
- let i = 1;
895
- while (i < line.length && line[i] >= "a" && line[i] <= "z")
896
- i++;
897
- return i > 1 && i < line.length && line[i] == "-";
898
- }
899
- /**
900
- * Applies heuristics to classify raw lines (lines without explicit diff markers).
901
- *
902
- * Heuristic: Raw lines are treated as context until we've seen explicit context markers.
903
- * Once explicit context has been seen, a raw line followed by an insertion before a
904
- * context/deletion boundary is treated as a deletion.
905
- *
906
- * This helps handle sloppy diffs where deletion markers are omitted but can be inferred.
907
- */
908
- static ApplyRawLineHeuristic(raw, rawLines, index, seenExplicitContext, fileLines) {
909
- const insAhead = _DiffContentHeuristics.HasInsertionAheadBeforeContextOrDeletion(rawLines, index);
910
- if (insAhead && seenExplicitContext) {
911
- return new DiffLine(LineType.Delete, _DiffContentHeuristics.StripInlineComment(raw, fileLines));
912
- }
913
- if (insAhead) {
914
- const verified = _DiffContentHeuristics.TryVerifiedCommentStrip(raw, fileLines);
915
- if (verified !== null)
916
- return new DiffLine(LineType.Delete, verified);
917
- }
918
- return new DiffLine(LineType.Context, raw);
919
- }
920
- static InlineCommentMarkers = [" //", " --", " #"];
921
- /**
922
- * Returns the comment-stripped form of a line when file content verifies it:
923
- * the full line must NOT exist in the file and the stripped variant MUST — so
924
- * real content containing " --" (SQL) or " #" (Python) is never mangled.
925
- * Returns null when no verified strip exists.
926
- */
927
- static TryVerifiedCommentStrip(input, fileLines) {
928
- if (!fileLines || fileLines.length == 0)
929
- return null;
930
- if (fileLines.some((l) => l === input))
931
- return null;
932
- for (const marker of _DiffContentHeuristics.InlineCommentMarkers) {
933
- for (let idx = input.indexOf(marker); idx >= 0; idx = input.indexOf(marker, idx + 1)) {
934
- const candidate = input.substring(0, idx).trimEnd();
935
- if (fileLines.some((l) => l === candidate))
936
- return candidate;
937
- }
938
- }
939
- return null;
940
- }
941
- /**
942
- * Strips an inline comment the LLM appended to an inferred deletion line
943
- * (e.g., "Line2 // Missing - marker" → "Line2").
944
- * Prefers a content-verified strip; without file content (or when
945
- * verification is inconclusive), falls back to stripping C-style " //" only.
946
- */
947
- static StripInlineComment(input, fileLines) {
948
- if (fileLines?.some((l) => l === input))
949
- return input;
950
- const verified = _DiffContentHeuristics.TryVerifiedCommentStrip(input, fileLines);
951
- if (verified !== null)
952
- return verified;
953
- const idx = input.indexOf(" //");
954
- return idx >= 0 ? input.substring(0, idx) : input;
955
- }
956
- /**
957
- * Checks if there's an insertion (line starting with '+') ahead of the current position,
958
- * before encountering a context line, deletion line, or blank line (boundaries).
959
- *
960
- * Used to infer whether a raw line should be treated as a deletion.
961
- */
962
- static HasInsertionAheadBeforeContextOrDeletion(rawLines, currentIndex) {
963
- for (let i = currentIndex + 1; i < rawLines.length; i++) {
964
- const l = rawLines[i];
965
- if (l.length == 0)
966
- break;
967
- const c = l[0];
968
- if (c == " ")
969
- break;
970
- if (c == "-" && !_DiffContentHeuristics.IsDoubleMarkerContent(l))
971
- break;
972
- if (c == "+")
973
- return true;
974
- }
975
- return false;
976
- }
977
- /**
978
- * Checks if a line starting with "-" or "+" is likely content with a missing context prefix.
979
- * Uses file content to disambiguate: if file has the line but NOT the would-be-modified content.
980
- *
981
- * Examples:
982
- * - "- list item" might be markdown bullet, not deletion of " list item"
983
- * - "+ Add feature" might be changelog entry, not insertion of " Add feature"
984
- *
985
- * @returns true if line should be treated as context, false if it should be treated as diff operation
986
- */
987
- static IsMissingContextPrefix(line, fileLines) {
988
- if (line.length < 2)
989
- return false;
990
- const marker = line[0];
991
- if (marker !== "-" && marker !== "+")
992
- return false;
993
- if (marker === "+" && line[1] !== " ")
994
- return false;
995
- const hasContentMatch = fileLines.some((l) => l === line);
996
- const hasOperationMatch = fileLines.some((l) => l === line.substring(1));
997
- return hasContentMatch && !hasOperationMatch;
998
- }
999
- /**
1000
- * Resolves ambiguity when stripping indentation reveals "- " (hyphen + space).
1001
- *
1002
- * This pattern is ambiguous:
1003
- * - Could be an indented DELETE line (deleting content starting with a space)
1004
- * - Could be a properly formatted CONTEXT line containing a markdown bullet
1005
- *
1006
- * Example: " - Label them..." could mean:
1007
- * - DELETE: Remove line " Label them..." (space + content) from file
1008
- * - CONTEXT: Keep line " - Label them..." (3 spaces + bullet) in file
1009
- *
1010
- * Resolution: Check which interpretation matches the actual file content.
1011
- * If only one matches, use that. If both or neither, prefer CONTEXT (conservative).
1012
- *
1013
- * @param original - The original line before stripping (e.g., " - Label them...")
1014
- * @param stripped - The line after stripping indentation (e.g., "- Label them...")
1015
- * @param fileLines - Lines from the actual file being patched
1016
- * @returns The line to use (original if CONTEXT, stripped if DELETE)
1017
- */
1018
- static ResolveHyphenSpaceAmbiguity(original, stripped, fileLines) {
1019
- const contextContent = original.length > 0 && original[0] === " " ? original.substring(1) : original;
1020
- const deleteContent = stripped.substring(1);
1021
- const hasContextMatch = fileLines.some((line) => line === contextContent);
1022
- const hasDeleteMatch = fileLines.some((line) => line === deleteContent);
1023
- if (hasContextMatch && !hasDeleteMatch)
1024
- return original;
1025
- if (hasDeleteMatch && !hasContextMatch)
1026
- return stripped;
1027
- return original;
1028
- }
1029
- };
1030
-
1031
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/hunkBuilder.js
1032
- var Header = class _Header {
1033
- Path;
1034
- OldStart;
1035
- OldCount;
1036
- NewStart;
1037
- NewCount;
1038
- DiffBodyStartLine;
1039
- constructor(Path = null, OldStart = -1, OldCount = 0, NewStart = -1, NewCount = 0, DiffBodyStartLine = -1) {
1040
- this.Path = Path;
1041
- this.OldStart = OldStart;
1042
- this.OldCount = OldCount;
1043
- this.NewStart = NewStart;
1044
- this.NewCount = NewCount;
1045
- this.DiffBodyStartLine = DiffBodyStartLine;
1046
- }
1047
- static WithParsed(h, parsedHdr, diffBodyStartLine) {
1048
- return new _Header(h.Path, parsedHdr.OldStart, parsedHdr.OldCount, parsedHdr.NewStart, parsedHdr.NewCount, diffBodyStartLine);
1049
- }
1050
- static ParseSide(header, part, prefix, isOld) {
1051
- if (!part.startsWith(prefix))
1052
- return header;
1053
- const split = part.substring(1).split(",");
1054
- const start = split.length > 0 && !isNaN(parseInt(split[0])) ? parseInt(split[0]) : -1;
1055
- const count = split.length > 1 && !isNaN(parseInt(split[1])) ? parseInt(split[1]) : 1;
1056
- return isOld ? new _Header(header.Path, start, count, header.NewStart, header.NewCount, header.DiffBodyStartLine) : new _Header(header.Path, header.OldStart, header.OldCount, start, count, header.DiffBodyStartLine);
1057
- }
1058
- };
1059
- var HunkBuilder = class _HunkBuilder {
1060
- hunks = [];
1061
- bodySeen = /* @__PURE__ */ new Map();
1062
- get Hunks() {
1063
- return this.hunks;
1064
- }
1065
- // Only the diff's FINAL hunk can be torn by a cutoff, so the parser reads this
1066
- // once, after the last commit; mid-diff the same signature is just sloppy counting.
1067
- LastHunkTruncated = false;
1068
- // A closing ``` fence is completion evidence a token cutoff cannot emit — the
1069
- // tear signature on a fence-closed hunk is miscount slop, not truncation.
1070
- // (Trailing prose already clears the flag via the pure-context commit path.)
1071
- ClearTruncation() {
1072
- this.LastHunkTruncated = false;
1073
- }
1074
- // Diff line of the first @@-headed body with real content but no change lines.
1075
- // Such a hunk can never be intentional (its '+'/'-' markers were lost, and the
1076
- // loss is ambiguous — adds or deletes?), so the parser rejects the whole diff
1077
- // instead of silently dropping it. Bodies outside any hunk (prose before or
1078
- // after a diff) and blank-only bodies keep flowing through silently.
1079
- // Diff line of each @@-headed body that had real content but no change lines. Such a
1080
- // hunk states no change, so it is skipped and its siblings apply — but the lines are
1081
- // surfaced so the caller can name the skip rather than report a clean success. Bodies
1082
- // outside any hunk (prose before or after a diff) and blank-only bodies are not hunks
1083
- // at all and are never recorded.
1084
- ContextOnlyHunkLines = [];
1085
- CommitIfAny(hdr, body, fileLines) {
1086
- if (body.length == 0)
1087
- return;
1088
- const blankContextDeletion = _HunkBuilder.IsBlankContextDeletion(hdr.OldCount, hdr.NewCount, body);
1089
- if (!blankContextDeletion && !body.some((l) => l.length > 0 && (l[0] == "-" || l[0] == "+"))) {
1090
- this.LastHunkTruncated = false;
1091
- if (hdr.DiffBodyStartLine >= 0 && body.some((l) => l.trim().length > 0))
1092
- this.ContextOnlyHunkLines.push(hdr.DiffBodyStartLine);
1093
- body.length = 0;
1094
- return;
1095
- }
1096
- const diffLines = _HunkBuilder.BuildLines(body, blankContextDeletion, fileLines);
1097
- this.LastHunkTruncated = _HunkBuilder.IsTruncated(hdr, diffLines);
1098
- const oldSb = [];
1099
- const newSb = [];
1100
- let oldLines = 0, newLines = 0;
1101
- for (const dl of diffLines) {
1102
- switch (dl.Type) {
1103
- case LineType.Insert:
1104
- newSb.push(dl.Text, "\n");
1105
- newLines++;
1106
- break;
1107
- case LineType.Delete:
1108
- oldSb.push(dl.Text, "\n");
1109
- oldLines++;
1110
- break;
1111
- case LineType.Context:
1112
- oldSb.push(dl.Text, "\n");
1113
- oldLines++;
1114
- newSb.push(dl.Text, "\n");
1115
- newLines++;
1116
- break;
1117
- }
1118
- }
1119
- const oldTxt = oldLines > 0 ? oldSb.join("") : "";
1120
- const newTxt = newLines > 0 ? newSb.join("") : "";
1121
- if (oldTxt == newTxt) {
1122
- body.length = 0;
1123
- return;
1124
- }
1125
- const hunk = new Hunk(hdr.Path ?? "", oldTxt, newTxt, hdr.OldStart);
1126
- hunk.NewStart = hdr.NewStart;
1127
- hunk.Lines = diffLines;
1128
- hunk.DiffBodyStartLine = hdr.DiffBodyStartLine;
1129
- this.AddHunk(hunk);
1130
- body.length = 0;
1131
- }
1132
- // Token-limit cutoff: body ends mid-change-run and the header declares more lines
1133
- // than delivered, asymmetrically. Symmetric deficits are stripped context (the
1134
- // anchorer recovers) and never count. An insert-ending body is torn only when the
1135
- // old side is ALSO short (promised trailing context never arrived): an old side
1136
- // fully delivered is complete by its own header, and a new-side deficit alone is
1137
- // overcount slop that must still apply — a complete append with a miscounted
1138
- // header is indistinguishable from a mid-run cut, and counts are tie-breakers,
1139
- // never sole grounds for rejection. A delete-ending body is the mirror: torn only
1140
- // when the NEW side is also short (newDeficit > 0) AND the old side is shorter
1141
- // still (the delete run was cut), so an over-counted-but-complete delete hunk
1142
- // (newDeficit == 0) applies as overcount slop. Cost (both branches): a cut with no
1143
- // trailing context owed on the completed side goes undetected.
1144
- static IsTruncated(hdr, lines) {
1145
- if (hdr.OldStart < 0 || hdr.NewStart < 0 || lines.length == 0)
1146
- return false;
1147
- const last = lines[lines.length - 1].Type;
1148
- if (last == LineType.Context)
1149
- return false;
1150
- const actualOld = lines.filter((l) => l.Type != LineType.Insert).length;
1151
- const actualNew = lines.filter((l) => l.Type != LineType.Delete).length;
1152
- const oldDeficit = hdr.OldCount - actualOld, newDeficit = hdr.NewCount - actualNew;
1153
- return last == LineType.Insert ? oldDeficit > 0 && newDeficit > oldDeficit : newDeficit > 0 && oldDeficit > newDeficit;
1154
- }
1155
- // Identical bodies collapse when the repeat adds no placement info (bare @@ or a
1156
- // start line already seen) — LLM-slop duplication. But git legitimately emits
1157
- // byte-identical hunks at distinct line numbers when a file repeats a pattern;
1158
- // those are separate edits, and the anchorer's line-number tiebreak places each
1159
- // at its own site. Distinct bodies always survive.
1160
- AddHunk(h) {
1161
- const bodyKey = `${h.OldText}
1162
- \u241E
1163
- ${h.NewText}`;
1164
- const starts = this.bodySeen.get(bodyKey);
1165
- if (starts) {
1166
- if (h.OldStart < 0 || starts.has(-1) || starts.has(h.OldStart))
1167
- return;
1168
- starts.add(h.OldStart);
1169
- } else {
1170
- this.bodySeen.set(bodyKey, /* @__PURE__ */ new Set([h.OldStart]));
1171
- }
1172
- this.hunks.push(h);
1173
- }
1174
- static BuildLines(rawLines, blankContextDeletion, fileLines) {
1175
- const result = [];
1176
- let seenExplicitContext = false;
1177
- const diffShaped = DiffContentHeuristics.IsDiffShapedRegion(rawLines, fileLines);
1178
- for (let i = 0; i < rawLines.length; i++) {
1179
- const raw = rawLines[i];
1180
- if (raw.length == 0) {
1181
- if (i == rawLines.length - 1)
1182
- continue;
1183
- result.push(new DiffLine(blankContextDeletion ? LineType.Delete : LineType.Context, ""));
1184
- continue;
1185
- }
1186
- if (blankContextDeletion) {
1187
- const c = raw[0];
1188
- result.push(new DiffLine(LineType.Delete, c == " " || c == "-" ? _HunkBuilder.RemoveDiffPrefix(raw) : raw));
1189
- continue;
1190
- }
1191
- switch (raw[0]) {
1192
- case "+":
1193
- case "-":
1194
- if (fileLines && DiffContentHeuristics.IsMissingContextPrefix(raw, fileLines)) {
1195
- result.push(new DiffLine(LineType.Context, raw));
1196
- seenExplicitContext = true;
1197
- break;
1198
- }
1199
- if (raw[0] === "+") {
1200
- if (DiffContentHeuristics.IsSloppyDoublePlus(raw)) {
1201
- result.push(diffShaped ? new DiffLine(LineType.Insert, raw.substring(1)) : new DiffLine(LineType.Insert, _HunkBuilder.RemoveDiffPrefix(raw), raw));
1202
- break;
1203
- }
1204
- result.push(new DiffLine(LineType.Insert, _HunkBuilder.RemoveDiffPrefix(raw)));
1205
- break;
1206
- }
1207
- if (DiffContentHeuristics.IsDoubleMarkerContent(raw)) {
1208
- if (fileLines && fileLines.some((l) => l == raw.substring(1)) && !fileLines.some((l) => l == raw)) {
1209
- result.push(new DiffLine(LineType.Delete, raw.substring(1)));
1210
- break;
1211
- }
1212
- if (seenExplicitContext && raw.length > 2 && raw[2] === " ") {
1213
- result.push(new DiffLine(LineType.Delete, raw.substring(1)));
1214
- } else {
1215
- result.push(DiffContentHeuristics.ApplyRawLineHeuristic(raw, rawLines, i, seenExplicitContext, fileLines));
1216
- }
1217
- } else {
1218
- result.push(new DiffLine(LineType.Delete, _HunkBuilder.RemoveDiffPrefix(raw)));
1219
- }
1220
- break;
1221
- case " ":
1222
- result.push(new DiffLine(LineType.Context, _HunkBuilder.RemoveDiffPrefix(raw)));
1223
- seenExplicitContext = true;
1224
- break;
1225
- default:
1226
- result.push(DiffContentHeuristics.ApplyRawLineHeuristic(raw, rawLines, i, seenExplicitContext, fileLines));
1227
- break;
1228
- }
1229
- }
1230
- return result;
1231
- }
1232
- static IsBlankContextDeletion(oldCount, newCount, rawLines) {
1233
- return oldCount > 0 && newCount == 0 && rawLines.every((l) => l.length == 0 || l[0] == " ");
1234
- }
1235
- // Strip diff prefix: ` foo` → `foo`, `-bar` → `bar`, `+baz` → `baz`
1236
- // Sloppy doubled markers: `++code` → `code`, `++ code` → `code`
1237
- // But NOT tripled: `---comment` → `--comment` (proper syntax for deleting `--comment`)
1238
- // And NOT `-- text` which is deletion of `- text` (markdown list), not sloppy
1239
- static RemoveDiffPrefix(s) {
1240
- if (!s || s.length <= 1)
1241
- return "";
1242
- let result = s.substring(1);
1243
- if (DiffContentHeuristics.IsSloppyDoublePlus(s)) {
1244
- result = result.substring(1);
1245
- if (result[0] === " " || result[0] === " ")
1246
- result = result.substring(1);
1247
- }
1248
- return result;
1249
- }
1250
- };
1251
-
1252
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/unifiedDiffParser.js
1253
- var UnifiedDiffParser = class _UnifiedDiffParser {
1254
- /**
1255
- * Parse a unified diff into structured hunks.
1256
- * @param diff - The diff text to parse
1257
- * @param files - Map of filename to content (content used for disambiguation of ambiguous lines)
1258
- * @param truncation - Verdict for a surviving tear signature on the final hunk
1259
- * @param controlChars - Verdict for raw control characters in insert lines
1260
- */
1261
- static Parse(diff, files, truncation = "warn", controlChars = "error") {
1262
- const parsed = diff == null || diff.trim().length == 0 ? { Hunks: [], ContextOnlyLines: [] } : _UnifiedDiffParser.ParseHunks(diff, files, truncation, controlChars);
1263
- const hunks = parsed.Hunks;
1264
- const ctxOnly = parsed.ContextOnlyLines;
1265
- const fileKeySet = new Set(files.keys());
1266
- const headerless = hunks.filter((h) => h.Key == null || h.Key == "");
1267
- const explicitKnown = hunks.filter((h) => h.Key != null && h.Key != "" && fileKeySet.has(h.Key));
1268
- const explicitFallback = hunks.filter((h) => h.Key != null && h.Key != "" && !fileKeySet.has(h.Key));
1269
- const groups = [];
1270
- if (headerless.length > 0)
1271
- groups.push(new FileHunkGroup("", headerless, ctxOnly));
1272
- if (explicitKnown.length > 0)
1273
- ArrayUtils.GroupBy(explicitKnown, (h) => h.Key).map((g) => new FileHunkGroup(g.key, g.values, ctxOnly)).forEach((x) => groups.push(x));
1274
- const treatAsSingleFile = fileKeySet.size == 1 && fileKeySet.has("") && explicitKnown.length == 0 && headerless.length == 0 && Array.from(new Set(explicitFallback.map((h) => h.Key))).length == 1;
1275
- if (treatAsSingleFile && explicitFallback.length > 0)
1276
- groups.push(new FileHunkGroup("", explicitFallback, ctxOnly));
1277
- else if (explicitFallback.length > 0)
1278
- ArrayUtils.GroupBy(explicitFallback, (h) => h.Key).map((g) => new FileHunkGroup(g.key, g.values, ctxOnly)).forEach((x) => groups.push(x));
1279
- if (groups.length == 0 && ctxOnly.length > 0)
1280
- groups.push(new FileHunkGroup("", [], ctxOnly));
1281
- return groups;
1282
- }
1283
- static ParseHunks(diff, fileContents, truncation = "warn", controlChars = "error") {
1284
- const hunkBuilder = new HunkBuilder();
1285
- const body = [];
1286
- let header = new Header();
1287
- let inFence = false;
1288
- let inHunkBody = false;
1289
- let lineNo = -1;
1290
- const defaultFileLines = fileContents?.size == 1 && fileContents.has("") ? _UnifiedDiffParser.ToFileLines(fileContents.get("")) : void 0;
1291
- let currentFileLines = defaultFileLines;
1292
- let fileLineSet = _UnifiedDiffParser.ToLineSet(currentFileLines);
1293
- for (const raw of _UnifiedDiffParser.StripCommonIndent(diff.replace(/\r\n/g, "\n")).split("\n")) {
1294
- lineNo++;
1295
- if (raw.length > 1 && raw[0] == " " && fileLineSet != null && fileLineSet.has(raw.slice(1).replace(/\s+$/, ""))) {
1296
- body.push(raw);
1297
- continue;
1298
- }
1299
- const line = _UnifiedDiffParser.StripIndent(raw, currentFileLines);
1300
- if (line.startsWith("```diff")) {
1301
- inFence = true;
1302
- continue;
1303
- }
1304
- if (!inFence && line.startsWith("```")) {
1305
- continue;
1306
- }
1307
- if (inFence && line.startsWith("```")) {
1308
- hunkBuilder.CommitIfAny(header, body, currentFileLines);
1309
- hunkBuilder.ClearTruncation();
1310
- header.DiffBodyStartLine = -1;
1311
- inFence = false;
1312
- inHunkBody = false;
1313
- continue;
1314
- }
1315
- if (_UnifiedDiffParser.IsMeta(line))
1316
- continue;
1317
- const path = _UnifiedDiffParser.TryParseFileHeader(line);
1318
- if (path && _UnifiedDiffParser.IsFileHeader(line, path, inHunkBody, fileContents, fileLineSet, body, currentFileLines)) {
1319
- hunkBuilder.CommitIfAny(header, body, currentFileLines);
1320
- header = new Header(path);
1321
- currentFileLines = _UnifiedDiffParser.ToFileLines(fileContents?.get(path)) ?? defaultFileLines;
1322
- fileLineSet = _UnifiedDiffParser.ToLineSet(currentFileLines);
1323
- inHunkBody = false;
1324
- continue;
1325
- }
1326
- const parsedHdr = _UnifiedDiffParser.TryParseHunkHeader(line);
1327
- if (parsedHdr) {
1328
- hunkBuilder.CommitIfAny(header, body, currentFileLines);
1329
- header = Header.WithParsed(header, parsedHdr, lineNo + 1);
1330
- inHunkBody = true;
1331
- continue;
1332
- }
1333
- body.push(line);
1334
- }
1335
- hunkBuilder.CommitIfAny(header, body, currentFileLines);
1336
- const hunks = hunkBuilder.Hunks;
1337
- if (hunkBuilder.LastHunkTruncated && truncation != "ignore") {
1338
- if (truncation == "error")
1339
- throw new PatchParserException("The diff appears truncated mid-hunk: the final hunk's header declares more lines than its body delivers. Nothing was applied; re-send the complete diff.");
1340
- if (hunks.length > 0)
1341
- hunks[hunks.length - 1].TruncationSuspected = true;
1342
- }
1343
- if (controlChars != "ignore") {
1344
- for (const hunk of hunks) {
1345
- const suspect = _UnifiedDiffParser.DescribeControlChars(hunk.Lines.filter((l) => l.Type == LineType.Insert).map((l) => l.Text));
1346
- if (suspect == null)
1347
- continue;
1348
- if (controlChars == "error")
1349
- throw new PatchParserException(`Insert lines contain raw control characters (${suspect}) \u2014 almost certainly transport damage, not intended content. Nothing was applied; re-send the diff with the intended text (or opt into the 'warn'/'ignore' policy).`);
1350
- hunk.ControlCharsSuspected = true;
1351
- }
1352
- }
1353
- return { Hunks: hunks, ContextOnlyLines: hunkBuilder.ContextOnlyHunkLines };
1354
- }
1355
- static IsSuspectControlChar(c) {
1356
- const cp = c.charCodeAt(0);
1357
- return cp < 32 && c != " " && c != "\n" && c != "\r" && c != "\f" || cp == 127;
1358
- }
1359
- // "U+0000 x2, U+001B x1" — names the invisible damage so the caller can see it.
1360
- static DescribeControlChars(lines) {
1361
- const counts = /* @__PURE__ */ new Map();
1362
- for (const line of lines)
1363
- for (const c of line)
1364
- if (_UnifiedDiffParser.IsSuspectControlChar(c))
1365
- counts.set(c, (counts.get(c) ?? 0) + 1);
1366
- if (counts.size == 0)
1367
- return null;
1368
- return [...counts.entries()].sort((a, b) => a[0].charCodeAt(0) - b[0].charCodeAt(0)).map(([c, n]) => `U+${c.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")} x${n}`).join(", ");
1369
- }
1370
- static MetaPrefixes = [
1371
- "index ",
1372
- "new file mode ",
1373
- "deleted file mode ",
1374
- "similarity index ",
1375
- "rename from ",
1376
- "rename to ",
1377
- "Binary files ",
1378
- "GIT binary patch",
1379
- "mode change "
1380
- ];
1381
- static IsMeta(line) {
1382
- return _UnifiedDiffParser.MetaPrefixes.some((p) => line.startsWith(p));
1383
- }
1384
- static ToFileLines(content) {
1385
- return content ? content.replace(/\r\n/g, "\n").split("\n") : void 0;
1386
- }
1387
- static ToLineSet(lines) {
1388
- return lines ? new Set(lines.map((l) => l.replace(/\s+$/, ""))) : void 0;
1389
- }
1390
- // Strips the longest common whitespace prefix from all non-empty diff body lines.
1391
- // This normalizes diffs that are uniformly indented (e.g. pasted inside a markdown block
1392
- // or another indented structure), while preserving the single-space context markers on
1393
- // lines whose content happens to start with '+' or '-'.
1394
- //
1395
- // @@ hunk headers are excluded from the prefix calculation because an LLM may indent
1396
- // body lines but not the header (or vice versa). The strip is applied only to lines
1397
- // that actually start with the computed prefix, so unindented headers are left intact.
1398
- //
1399
- // Runs before the per-line StripIndent pass: this handles uniform indentation
1400
- // (including a single-space indent that per-line stripping must not touch), while
1401
- // StripIndent handles ragged 2+ whitespace indentation line by line.
1402
- static StripCommonIndent(diff) {
1403
- const lines = diff.split("\n");
1404
- let common = null;
1405
- for (const line of lines) {
1406
- if (line.length == 0)
1407
- continue;
1408
- const i = TextUtils.CountLeadingWhitespace(line);
1409
- if (line.startsWith("@@", i))
1410
- continue;
1411
- const prefix = line.substring(0, i);
1412
- if (common == null) {
1413
- common = prefix;
1414
- continue;
1415
- }
1416
- const minLen = Math.min(common.length, prefix.length);
1417
- let j = 0;
1418
- while (j < minLen && common[j] == prefix[j])
1419
- j++;
1420
- common = common.substring(0, j);
1421
- if (common.length == 0)
1422
- return diff;
1423
- }
1424
- if (common == null || common.length == 0)
1425
- return diff;
1426
- const c = common;
1427
- if (!lines.some((l) => l.startsWith(c) && l.length > c.length && (l[c.length] == "+" || l[c.length] == "-")))
1428
- return diff;
1429
- return lines.map((l) => l.startsWith(c) ? l.substring(c.length) : l).join("\n");
1430
- }
1431
- static StripIndent(s, fileLines) {
1432
- if (s == null || s.length == 0)
1433
- return s;
1434
- const stripped = s.replace(/^[ \t]{2,}/, "");
1435
- if (fileLines && stripped.length > 1 && stripped[0] === "-" && stripped[1] === " ") {
1436
- return DiffContentHeuristics.ResolveHyphenSpaceAmbiguity(s, stripped, fileLines);
1437
- }
1438
- if (fileLines && stripped.length > 0 && stripped[0] === "+" && fileLines.some((f) => f.trim() == stripped.trimEnd()))
1439
- return s;
1440
- return DiffContentHeuristics.IsValidDiffLineStart(stripped) ? stripped : s;
1441
- }
1442
- static TryParseFileHeader(line) {
1443
- if (!(line.startsWith("diff --git ") || line.startsWith("--- ") || line.startsWith("+++ "))) {
1444
- return null;
1445
- }
1446
- const path = line.startsWith("diff --git ") ? _UnifiedDiffParser.Canon(line.split(" ").filter((s) => s.length > 0)[2]) : _UnifiedDiffParser.Canon(line.length >= 4 ? line.substring(4) : "");
1447
- return path;
1448
- }
1449
- // Inside a hunk body, `--- foo` could be deletion of `-- foo` (SQL comment,
1450
- // prose) and `+++ foo` insertion of `++ foo` — or the next file's header.
1451
- // Evidence ladder: a transition to a file we HOLD
1452
- // is a real header; otherwise content decides — the delete reading wins when
1453
- // the current file contains `-- foo` verbatim, the insert reading when the
1454
- // body so far is a verified diff-shaped region (real header pairs never hit
1455
- // this: their `+++` follows a header-read `---`, which ends the body). Only
1456
- // in an evidence vacuum does the structure heuristic decide: real paths have
1457
- // `.` (extension) or `/` (directory); prose doesn't.
1458
- static IsFileHeader(line, path, inHunkBody, files, fileLineSet, body, fileLines) {
1459
- if (!inHunkBody)
1460
- return true;
1461
- if (line.startsWith("diff --git "))
1462
- return true;
1463
- if (path != "" && files?.has(path))
1464
- return true;
1465
- if (line.startsWith("--- ") && fileLineSet != null && fileLineSet.has(line.slice(1).replace(/\s+$/, "")))
1466
- return false;
1467
- if (line.startsWith("+++ ") && body != null && DiffContentHeuristics.IsDiffShapedRegion(body, fileLines))
1468
- return false;
1469
- return _UnifiedDiffParser.LooksLikePath(path);
1470
- }
1471
- static LooksLikePath(path) {
1472
- return path.includes(".") || path.includes("/") || path.includes("\\") || path === "/dev/null";
1473
- }
1474
- static Canon(p) {
1475
- if (p == null || p.length == 0)
1476
- return p;
1477
- p = p.trim();
1478
- if (p.startsWith("a/") || p.startsWith("b/"))
1479
- p = p.substring(2);
1480
- return p;
1481
- }
1482
- static TryParseHunkHeader(line) {
1483
- const t = line.trim();
1484
- if (!t.startsWith("@@"))
1485
- return null;
1486
- let header = new Header();
1487
- const parts = t.replace(/^[@ ]+|[@ ]+$/g, "").split(" ").filter((s) => s.length > 0);
1488
- if (parts.length > 0)
1489
- header = Header.ParseSide(header, parts[0], "-", true);
1490
- if (parts.length > 1)
1491
- header = Header.ParseSide(header, parts[1], "+", false);
1492
- return header;
1493
- }
1494
- };
1495
-
1496
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/anchorer/hunkSlicer.js
1497
- var Block = class {
1498
- StartIndex;
1499
- EndIndex;
1500
- DeleteLines;
1501
- InsertLines;
1502
- constructor(StartIndex, EndIndex, DeleteLines, InsertLines) {
1503
- this.StartIndex = StartIndex;
1504
- this.EndIndex = EndIndex;
1505
- this.DeleteLines = DeleteLines;
1506
- this.InsertLines = InsertLines;
1507
- }
1508
- };
1509
- var SlicedHunk = class {
1510
- Hunk;
1511
- Blocks;
1512
- constructor(Hunk2, Blocks) {
1513
- this.Hunk = Hunk2;
1514
- this.Blocks = Blocks;
1515
- }
1516
- };
1517
- var HunkSlicer = class _HunkSlicer {
1518
- static Slice(hunks) {
1519
- return Array.from(hunks).map((h) => new SlicedHunk(h, Array.from(_HunkSlicer.SliceHunk(h))));
1520
- }
1521
- static *SliceHunk(hunk) {
1522
- const lines = hunk.Lines;
1523
- if (lines == null || lines.length == 0) {
1524
- return;
1525
- }
1526
- let i = 0;
1527
- while (i < lines.length) {
1528
- if (lines[i].Type == LineType.Context) {
1529
- i++;
1530
- continue;
1531
- }
1532
- const start = i;
1533
- const del = [];
1534
- const ins = [];
1535
- while (i < lines.length && lines[i].Type != LineType.Context) {
1536
- const l = lines[i++];
1537
- if (l.Type == LineType.Delete)
1538
- del.push(l.Text);
1539
- else if (l.Type == LineType.Insert)
1540
- ins.push(l.Text);
1541
- }
1542
- const end = i - 1;
1543
- if (del.length > 0 || ins.length > 0)
1544
- yield new Block(start, end, del, ins);
1545
- }
1546
- }
1547
- };
1548
-
1549
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/anchorer/search.js
1550
- var Match = class {
1551
- LineIndex;
1552
- Fuzz;
1553
- constructor(LineIndex, Fuzz) {
1554
- this.LineIndex = LineIndex;
1555
- this.Fuzz = Fuzz;
1556
- }
1557
- };
1558
- var Search = class _Search {
1559
- // Whitespace matching modes with their per-line fuzz
1560
- static StrictnessPasses = [
1561
- { Transform: (s) => s, Fuzz: 0 },
1562
- // exact
1563
- { Transform: (s) => s.trimEnd(), Fuzz: 1 },
1564
- // rstrip
1565
- { Transform: (s) => s.trim(), Fuzz: 100 },
1566
- // strip
1567
- { Transform: (s) => TextUtils.NormalizeHomoglyphs(s).trim(), Fuzz: 200 },
1568
- // homoglyphs + strip
1569
- { Transform: (s) => TextUtils.NormalizeHomoglyphs(TextUtils.StripInvisibles(s)).trim(), Fuzz: 300 },
1570
- // invisibles stripped + homoglyphs + strip
1571
- { Transform: (s) => TextUtils.NormalizeHomoglyphs(TextUtils.FoldCanonicalAndFullwidth(TextUtils.StripInvisibles(s))).trim(), Fuzz: 400 }
1572
- // + NFC and fullwidth folds
1573
- ];
1574
- /// <summary>
1575
- /// Attempts to locate <paramref name="needle"/> inside <paramref name="haystack"/>,
1576
- /// applying the strictness passes in order, strictest first.
1577
- /// Returns a FindResult with Index == -1 if no match.
1578
- /// Passes whose per-line fuzz exceeds <paramref name="maxPassFuzz"/> are skipped:
1579
- /// callers that treat a match as evidence the file already contains specific text
1580
- /// (rather than as an anchor to edit) pass 100 to exclude the homoglyph and
1581
- /// invisible-stripping passes.
1582
- /// </summary>
1583
- static Find(haystack, needle, startIndex = 0, maxPassFuzz = Number.MAX_SAFE_INTEGER) {
1584
- if (needle.length == 0)
1585
- return new Match(Math.min(Math.max(startIndex, 0), haystack.length), 0);
1586
- if (haystack.length < needle.length)
1587
- return new Match(-1, 0);
1588
- for (const { Transform: transform, Fuzz: fuzz } of _Search.StrictnessPasses.filter((p) => p.Fuzz <= maxPassFuzz)) {
1589
- const transformedNeedle = Array.from(needle).map(transform);
1590
- for (let i = startIndex; i <= haystack.length - needle.length; i++) {
1591
- let match = true;
1592
- for (let j = 0; j < needle.length; j++) {
1593
- if (transform(haystack[i + j]) != transformedNeedle[j]) {
1594
- match = false;
1595
- break;
1596
- }
1597
- }
1598
- if (!match)
1599
- continue;
1600
- let affected = 0;
1601
- for (let k = 0; k < needle.length; k++) {
1602
- if (transform(haystack[i + k]) != haystack[i + k] || transformedNeedle[k] != needle[k])
1603
- affected++;
1604
- }
1605
- return new Match(i, fuzz * affected);
1606
- }
1607
- }
1608
- return new Match(-1, 0);
1609
- }
1610
- /// <summary>
1611
- /// True when two lines are equal under any of the strictness passes the anchor
1612
- /// search itself uses. Used to revalidate context at a line-number-tiebroken slot:
1613
- /// the revalidation must not be stricter than the search that produced the
1614
- /// candidates, or slots the search considered equivalent (indent slop, homoglyphs)
1615
- /// get rejected loudly instead of applied.
1616
- /// </summary>
1617
- static LinesEquivalent(a, b) {
1618
- return _Search.StrictnessPasses.some((p) => p.Transform(a) == p.Transform(b));
1619
- }
1620
- /// <summary>
1621
- /// Attempts to find a unique match and reports ambiguity without requiring a separate call.
1622
- /// Success=true when exactly one match; Ambiguous=true when more than one match exists; both false when none.
1623
- /// </summary>
1624
- static FindUnique(haystack, needle) {
1625
- const first = _Search.Find(haystack, needle);
1626
- if (first.LineIndex < 0)
1627
- return UniqueMatch.NotFound;
1628
- const second = _Search.Find(haystack, needle, first.LineIndex + 1);
1629
- if (second.LineIndex >= 0)
1630
- return UniqueMatch.Ambiguous;
1631
- return new UniqueMatch(MatchState.Success, first.LineIndex, first.Fuzz);
1632
- }
1633
- /// <summary>
1634
- /// Finds the best insertion anchor for a pure-insert hunk using provided context lines.
1635
- /// Attempts, in order: full pattern (pre+post), pre-only (insert after pre), post-only (insert before post).
1636
- /// If none uniquely match, returns a non-success result with Ambiguous flag when any side matches somewhere.
1637
- /// </summary>
1638
- static FindContextAnchor(haystack, contextBefore, contextAfter) {
1639
- const preCount = contextBefore.length;
1640
- let sawAmbiguity = false;
1641
- const searchPatterns = [
1642
- { pattern: contextBefore.concat(contextAfter), insertIndex: preCount, condition: preCount + contextAfter.length > 0 },
1643
- { pattern: contextBefore, insertIndex: preCount, condition: preCount > 0 },
1644
- { pattern: contextAfter, insertIndex: 0, condition: contextAfter.length > 0 }
1645
- ];
1646
- for (const { pattern, insertIndex, condition } of searchPatterns) {
1647
- if (!condition)
1648
- continue;
1649
- const match = _Search.FindUnique(haystack, pattern);
1650
- if (match.IsSuccess)
1651
- return new UniqueMatch(MatchState.Success, match.LineIndex + insertIndex, match.Fuzz);
1652
- if (match.IsAmbiguous)
1653
- sawAmbiguity = true;
1654
- }
1655
- return new UniqueMatch(sawAmbiguity ? MatchState.Ambiguous : MatchState.NotFound);
1656
- }
1657
- /// <summary>
1658
- /// Returns every position in <paramref name="haystack"/> where <paramref name="needle"/>
1659
- /// matches exactly (no whitespace fuzz). Used for line-number tiebreaking among
1660
- /// ambiguous candidates — we deliberately exclude fuzzy matches so the candidate set
1661
- /// reflects only positions the LLM could plausibly have meant.
1662
- /// </summary>
1663
- static FindAllExact(haystack, needle) {
1664
- const results = [];
1665
- if (needle.length == 0)
1666
- return results;
1667
- for (let i = 0; i + needle.length <= haystack.length; i++)
1668
- if (needle.every((v, j) => haystack[i + j] === v))
1669
- results.push(i);
1670
- return results;
1671
- }
1672
- static TiebreakByLineNumber(candidates, expectedLine) {
1673
- if (candidates.length == 0)
1674
- return null;
1675
- if (candidates.length == 1)
1676
- return candidates[0];
1677
- const sorted = ArrayUtils.OrderBy(candidates.map((c) => ({ line: c, dist: Math.abs(c - expectedLine) })), (x) => x.dist);
1678
- if (sorted[0].dist == 0)
1679
- return sorted[0].line;
1680
- const gapToNext = sorted[1].dist - sorted[0].dist;
1681
- if (gapToNext > sorted[0].dist * 10)
1682
- return sorted[0].line;
1683
- return null;
1684
- }
1685
- };
1686
-
1687
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/anchorer/blockAnchorer.js
1688
- var BlockAnchorer = class _BlockAnchorer {
1689
- static AnchorBlocks(hunk, blocks, lines, options) {
1690
- return blocks.map((focus, index) => {
1691
- let aboveEdge = focus.StartIndex;
1692
- let belowEdge = focus.EndIndex;
1693
- let aboveBlockIdx = index - 1;
1694
- let belowBlockIdx = index + 1;
1695
- let chunk = null;
1696
- const suppliedAbove = _BlockAnchorer.ContextLines(hunk, focus.StartIndex - 1, index > 0 ? blocks[index - 1].EndIndex + 1 : 0, Number.MAX_SAFE_INTEGER, true);
1697
- const suppliedBelow = _BlockAnchorer.ContextLines(hunk, focus.EndIndex + 1, index < blocks.length - 1 ? blocks[index + 1].StartIndex - 1 : hunk.Lines.length - 1, Number.MAX_SAFE_INTEGER, false);
1698
- while (true) {
1699
- const aboveBound = aboveBlockIdx >= 0 ? blocks[aboveBlockIdx].EndIndex + 1 : 0;
1700
- const belowBound = belowBlockIdx < blocks.length ? blocks[belowBlockIdx].StartIndex - 1 : hunk.Lines.length - 1;
1701
- for (let k = 1; k <= options.ContextWindowMax; k++) {
1702
- const ctxAbove = _BlockAnchorer.ContextLines(hunk, aboveEdge - 1, aboveBound, k, true);
1703
- const ctxBelow = _BlockAnchorer.ContextLines(hunk, belowEdge + 1, belowBound, k, false);
1704
- const needle = _BlockAnchorer.BuildNeedle(hunk, aboveEdge, belowEdge);
1705
- const match = _BlockAnchorer.Anchor(lines, ctxAbove, needle, ctxBelow);
1706
- chunk = new Chunk(aboveEdge == focus.StartIndex ? ctxAbove : _BlockAnchorer.ContextLines(hunk, focus.StartIndex - 1, 0, k, true), focus.DeleteLines, focus.InsertLines, belowEdge == focus.EndIndex ? ctxBelow : _BlockAnchorer.ContextLines(hunk, focus.EndIndex + 1, hunk.Lines.length - 1, k, false), _BlockAnchorer.AlignMatchToFocus(match, hunk, aboveEdge, focus.StartIndex), new DiffLocation(hunk, focus.StartIndex, focus.EndIndex));
1707
- chunk.SuppliedContextBefore = suppliedAbove;
1708
- chunk.SuppliedContextAfter = suppliedBelow;
1709
- if (!chunk.Match.IsAmbiguous)
1710
- return chunk;
1711
- if (ctxAbove.length < k && ctxBelow.length < k)
1712
- break;
1713
- }
1714
- if (aboveBlockIdx < 0 && belowBlockIdx >= blocks.length)
1715
- break;
1716
- const ctxAboveCount = aboveBlockIdx >= 0 ? aboveEdge - (blocks[aboveBlockIdx].EndIndex + 1) : Number.MAX_SAFE_INTEGER;
1717
- const ctxBelowCount = belowBlockIdx < blocks.length ? blocks[belowBlockIdx].StartIndex - (belowEdge + 1) : Number.MAX_SAFE_INTEGER;
1718
- if (ctxBelowCount <= ctxAboveCount && belowBlockIdx < blocks.length)
1719
- belowEdge = blocks[belowBlockIdx++].EndIndex;
1720
- else
1721
- aboveEdge = blocks[aboveBlockIdx--].StartIndex;
1722
- }
1723
- if (chunk.Match.IsAmbiguous && hunk.OldStart >= 0) {
1724
- const tiebroken = _BlockAnchorer.TryLineNumberTiebreak(hunk, focus, lines);
1725
- if (tiebroken != null)
1726
- chunk = chunk.with({ Match: tiebroken });
1727
- }
1728
- return chunk;
1729
- });
1730
- }
1731
- static TryLineNumberTiebreak(hunk, focus, lines) {
1732
- let expectedLine = hunk.OldStart - 1;
1733
- for (let i = 0; i < focus.StartIndex; i++)
1734
- if (hunk.Lines[i].Type != LineType.Insert)
1735
- expectedLine++;
1736
- if (focus.DeleteLines.length == 0) {
1737
- if (expectedLine < 0 || expectedLine > lines.length)
1738
- return null;
1739
- if (!_BlockAnchorer.CtxMatchesAtSlot(hunk, focus, lines, expectedLine))
1740
- return null;
1741
- return new UniqueMatch(MatchState.Success, expectedLine);
1742
- }
1743
- const candidates = Search.FindAllExact(lines, focus.DeleteLines);
1744
- const resolved = Search.TiebreakByLineNumber(candidates, expectedLine);
1745
- return resolved != null && _BlockAnchorer.CtxMatchesAtSlot(hunk, focus, lines, resolved) ? new UniqueMatch(MatchState.Success, resolved) : null;
1746
- }
1747
- // Validates the hunk's immediate context lines against a resolved slot's
1748
- // neighbours so a stale or shifted header errs loudly instead of silently
1749
- // mis-placing the edit. Content-bearing context compares under the search's own
1750
- // strictness ladder — the revalidation must not be stricter than the search that
1751
- // produced the candidates, or indent-slopped slots the search matched get
1752
- // rejected loudly. But a line that trims to near-nothing (brace/paren-only)
1753
- // carries its signal IN the indentation, which every ladder pass beyond rstrip
1754
- // erases — such context stays whitespace-right-trimmed strict, or a shifted
1755
- // header walks the edit to any same-shaped brace. Both tolerances corpus-measured
1756
- // 2026-07-06 (ladder-everywhere: 3 silent mis-applies; this gate: 0, +14 recall).
1757
- // Serves both callers: a pure insert (DeleteLines empty, after-context sits at
1758
- // `slot`) and a delete-block tiebreak (deletes occupy [slot, slot + DeleteLines.length)).
1759
- static CtxMatchesAtSlot(hunk, focus, lines, slot) {
1760
- const MinLadderContent = 3;
1761
- const matches = (hunkIdx, lineIdx) => {
1762
- if (hunkIdx < 0 || hunkIdx >= hunk.Lines.length || hunk.Lines[hunkIdx].Type != LineType.Context)
1763
- return true;
1764
- if (lineIdx < 0 || lineIdx >= lines.length)
1765
- return false;
1766
- const text = hunk.Lines[hunkIdx].Text;
1767
- return text.trim().length >= MinLadderContent ? Search.LinesEquivalent(lines[lineIdx], text) : lines[lineIdx].trimEnd() == text.trimEnd();
1768
- };
1769
- return matches(focus.StartIndex - 1, slot - 1) && matches(focus.EndIndex + 1, slot + focus.DeleteLines.length);
1770
- }
1771
- static AlignMatchToFocus(match, hunk, needleStartIndex, focusStartIndex) {
1772
- if (!match.IsSuccess)
1773
- return match;
1774
- return new UniqueMatch(match.State, match.LineIndex + hunk.Lines.slice(needleStartIndex, focusStartIndex).filter((l) => l.Type != LineType.Insert).length, match.Fuzz);
1775
- }
1776
- static ContextLines(hunk, startLine, bound, maxTake, isAbove) {
1777
- if (maxTake <= 0)
1778
- return [];
1779
- const lines = [];
1780
- for (let i = 0; i < maxTake; i++) {
1781
- const index = startLine + (isAbove ? -i : i);
1782
- if (!(index >= 0 && index < hunk.Lines.length))
1783
- break;
1784
- if (!(isAbove ? index >= bound : index <= bound))
1785
- break;
1786
- const line = hunk.Lines[index];
1787
- if (line.Type != LineType.Context)
1788
- break;
1789
- lines.push(line.Text);
1790
- }
1791
- return isAbove ? [...lines].reverse() : lines;
1792
- }
1793
- static BuildNeedle(h, startIndex, endIndex) {
1794
- return h.Lines.slice(startIndex, endIndex + 1).filter((line) => line.Type != LineType.Insert).map((line) => line.Text);
1795
- }
1796
- static Anchor(lines, contextAbove, deleteLines, contextBelow) {
1797
- if (deleteLines.length == 0)
1798
- return Search.FindContextAnchor(lines, contextAbove, contextBelow);
1799
- const pattern = contextAbove.concat(deleteLines).concat(contextBelow);
1800
- const match = Search.FindUnique(lines, pattern);
1801
- if (match.IsAmbiguous)
1802
- return UniqueMatch.Ambiguous;
1803
- if (match.IsSuccess)
1804
- return new UniqueMatch(match.State, match.LineIndex + contextAbove.length, match.Fuzz);
1805
- return Search.FindUnique(lines, deleteLines);
1806
- }
1807
- };
1808
-
1809
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/anchorer/hunkAnchorer.js
1810
- var AnchorOutcome = class {
1811
- Chunks;
1812
- AlreadyAppliedCount;
1813
- constructor(Chunks, AlreadyAppliedCount) {
1814
- this.Chunks = Chunks;
1815
- this.AlreadyAppliedCount = AlreadyAppliedCount;
1816
- }
1817
- };
1818
- var HunkAnchorer = class _HunkAnchorer {
1819
- /// <param name="hunkWasSkipped">A hunk of this diff stated no change and was dropped at
1820
- /// parse time — so an unpositioned insert here may be the orphaned half of a split hunk
1821
- /// whose anchor went with it. See UseFallbackAnchorIfNecessary.</param>
1822
- static Anchor(originalText, hunks, options, hunkWasSkipped = false) {
1823
- const lines = TextUtils.ToLines(originalText);
1824
- const slicedHunks = HunkSlicer.Slice(hunks);
1825
- const chunks = [];
1826
- for (const sliced of slicedHunks)
1827
- for (const chunk of BlockAnchorer.AnchorBlocks(sliced.Hunk, sliced.Blocks, lines, options))
1828
- chunks.push(_HunkAnchorer.UseFallbackAnchorIfNecessary(sliced.Hunk, chunk, lines, hunkWasSkipped));
1829
- const deleteCounts = /* @__PURE__ */ new Map();
1830
- for (const l of chunks.flatMap((c) => c.DeleteLines))
1831
- if (l.trim().length > 0)
1832
- deleteCounts.set(l, (deleteCounts.get(l) ?? 0) + 1);
1833
- const movedFromElsewhere = (c, line) => (deleteCounts.get(line) ?? 0) > c.DeleteLines.filter((d) => d === line).length;
1834
- const kept = chunks.filter((c) => !_HunkAnchorer.AppliedAtAnchor(lines, c) && (c.InsertLines.some((l) => movedFromElsewhere(c, l)) || !_HunkAnchorer.AlreadyApplied(lines, c))).map((c) => _HunkAnchorer.TrySubLineSplice(c, lines));
1835
- return new AnchorOutcome(kept, chunks.length - kept.length);
1836
- }
1837
- static UseFallbackAnchorIfNecessary(hunk, chunk, lines, hunkWasSkipped) {
1838
- const lineCount = lines.length;
1839
- if (chunk.Match.IsNotFound && hunk.Lines.length == chunk.InsertLines.length) {
1840
- const targetIsEmpty = lineCount == 0 || lineCount == 1 && lines[0].length == 0;
1841
- if (hunk.OldStart < 0 && hunkWasSkipped && !targetIsEmpty)
1842
- return chunk;
1843
- const anchorLine = hunk.OldStart <= 1 ? 0 : hunk.OldStart;
1844
- if (anchorLine > lineCount)
1845
- return chunk;
1846
- return chunk.with({ Match: new UniqueMatch(MatchState.Success, anchorLine) });
1847
- }
1848
- return chunk;
1849
- }
1850
- // Sub-line splice fallback: a delete line that matches no file line but occurs
1851
- // as a substring of exactly one line is an author-quoted fragment — an author
1852
- // cannot intend to delete text they never mentioned, so the only coherent
1853
- // reading replaces the fragment within the line and preserves the rest. Every
1854
- // guard failure means fragment-vs-mangled-whole-line is undecidable: the chunk
1855
- // stays NotFound and errors loudly. The insert-side guards catch damaged
1856
- // whole-line edits: an insert that echoes text from outside the fragment, or
1857
- // dwarfs it, was authored by someone who saw the whole line.
1858
- // MUST run only on chunks that survived the already-applied filter: an applied
1859
- // edit that wrapped the old line (comment-out, prefix insertion) leaves the
1860
- // delete line as a substring of the new line, and splicing it again would
1861
- // stack the wrapper (corpus wave-1 case 177: "#x" became "##x").
1862
- static TrySubLineSplice(chunk, lines) {
1863
- if (!chunk.Match.IsNotFound || chunk.DeleteLines.length != 1 || chunk.InsertLines.length > 1)
1864
- return chunk;
1865
- const frag = chunk.DeleteLines[0];
1866
- if (frag.trim().length < _HunkAnchorer.MinSpliceFragmentContent)
1867
- return chunk;
1868
- let lineIndex = -1;
1869
- for (let i = 0; i < lines.length; i++) {
1870
- const first = lines[i].indexOf(frag);
1871
- if (first < 0)
1872
- continue;
1873
- const again = lines[i].indexOf(frag, first + 1) >= 0;
1874
- if (lineIndex >= 0 || again)
1875
- return chunk;
1876
- lineIndex = i;
1877
- }
1878
- if (lineIndex < 0)
1879
- return chunk;
1880
- const target = lines[lineIndex];
1881
- const at = target.indexOf(frag);
1882
- const prefix = target.slice(0, at), suffix = target.slice(at + frag.length);
1883
- const insert = chunk.InsertLines.length == 1 ? chunk.InsertLines[0] : "";
1884
- if (prefix.length == 0 && suffix.length > 0)
1885
- return chunk;
1886
- if (insert.length == 0 && prefix.length > 0 && suffix.length > 0 && /\s/.test(prefix[prefix.length - 1]) && /\s/.test(suffix[0]))
1887
- return chunk;
1888
- const remnantOk = (r) => r.length == 0 || r.trim().length >= _HunkAnchorer.MinSpliceRemnantContent;
1889
- const echoes = (r) => r.length > 0 && insert.includes(r.trim());
1890
- if (!remnantOk(prefix) || !remnantOk(suffix) || echoes(prefix) || echoes(suffix) || insert.length > frag.length * 2 + 8 && _HunkAnchorer.SharedStem(frag, insert) < _HunkAnchorer.MinSpliceStemContent)
1891
- return chunk;
1892
- return chunk.with({
1893
- ContextBefore: [],
1894
- DeleteLines: [target],
1895
- InsertLines: [prefix + insert + suffix],
1896
- ContextAfter: [],
1897
- Match: new UniqueMatch(MatchState.Success, lineIndex, _HunkAnchorer.SubLineSpliceFuzz)
1898
- });
1899
- }
1900
- // Trimmed length a fragment must reach before substring evidence counts.
1901
- static MinSpliceFragmentContent = 8;
1902
- // Trimmed length a non-empty out-of-fragment remnant must carry.
1903
- static MinSpliceRemnantContent = 4;
1904
- // Trimmed length the fragment/insert shared stem must reach to waive the dwarf cap.
1905
- static MinSpliceStemContent = 8;
1906
- // The longest run the insert shares with the fragment's start or end, in trimmed
1907
- // chars — the fragment-edit evidence that waives the dwarf cap.
1908
- static SharedStem(frag, insert) {
1909
- let p = 0;
1910
- while (p < frag.length && p < insert.length && frag[p] == insert[p])
1911
- p++;
1912
- let s = 0;
1913
- while (s < frag.length && s < insert.length && frag[frag.length - 1 - s] == insert[insert.length - 1 - s])
1914
- s++;
1915
- return Math.max(frag.slice(0, p).trim().length, s == 0 ? 0 : frag.slice(-s).trim().length);
1916
- }
1917
- // Reported fuzz for a spliced edit: looser than every line-match pass.
1918
- static SubLineSpliceFuzz = 500;
1919
- // A sanity check to avoid errors for a chunk that is already applied.
1920
- // Asymmetric strictness: insert-side evidence is capped at whitespace-level
1921
- // passes (a homoglyph variant of the post-image is not proof the edit landed,
1922
- // and would turn a loud MatchNotFound into a silent no-op), while the delete
1923
- // side stays uncapped (finding the pre-image even loosely proves the edit is
1924
- // still pending, blocking a false already-applied verdict).
1925
- static AlreadyApplied(lines, c) {
1926
- if (!c.HasContextLines() && (c.IsPureDelete || c.IsPureInsert))
1927
- return false;
1928
- const insertImage = c.HasContextLines() ? c.InsertLinesWithContext() : c.InsertLines;
1929
- const deleteImage = c.HasContextLines() ? c.DeleteLinesWithContext() : c.DeleteLines;
1930
- const insertAt = Search.Find(lines, insertImage, 0, _HunkAnchorer.MaxAppliedEvidenceFuzz);
1931
- return insertAt.LineIndex != -1 && Search.Find(lines, deleteImage).LineIndex == -1 && !_HunkAnchorer.OldImagePresentElsewhere(lines, c.DeleteLines, c.IsPureDelete ? null : insertImage, insertAt.LineIndex);
1932
- }
1933
- // Veto on the already-applied verdict: "old image absent" cannot
1934
- // distinguish an applied edit from a delete block damaged in transit — one bad
1935
- // char defeats every search pass — so a window nearly matching the delete block
1936
- // proves the old image is still in the file and the edit pending: keep the chunk
1937
- // and let it error loudly. Refinements, each forced by a corpus counter-example:
1938
- // - Windows overlapping an insert-image occurrence never veto: after a genuine
1939
- // apply the region shares most lines with the old image (rewrites re-state
1940
- // unchanged lines as -/+), and vetoing there would break idempotent re-apply.
1941
- // - Pure deletes skip that exclusion (exclusionImage = null): their post-image
1942
- // is thin context that occurs everywhere — even inside the pending block
1943
- // itself — so its occurrences prove nothing.
1944
- // - The window is the delete block WITHOUT context: context sits in both images
1945
- // and would glue the pending old image to a context match, masking it.
1946
- // - Only content-bearing lines vote, with an absolute floor: blank/brace lines
1947
- // near-match everywhere and tiny blocks carry too little signal either way.
1948
- static OldImagePresentElsewhere(lines, dels, exclusionImage, insertIndex) {
1949
- const contentIdx = [...dels.keys()].filter((i) => dels[i].trim().length > 0);
1950
- if (contentIdx.length < _HunkAnchorer.MinNearImageMatches)
1951
- return false;
1952
- const insertStarts = exclusionImage == null ? [] : [...Search.FindAllExact(lines, exclusionImage), insertIndex];
1953
- const overlapsInsert = (start) => insertStarts.some((s) => start < s + exclusionImage.length && s < start + dels.length);
1954
- for (let start = 0; start + dels.length <= lines.length; start++) {
1955
- if (overlapsInsert(start))
1956
- continue;
1957
- const matched = contentIdx.filter((j) => lines[start + j] === dels[j]).length;
1958
- if (matched >= _HunkAnchorer.MinNearImageMatches && matched * 2 > contentIdx.length)
1959
- return true;
1960
- }
1961
- return false;
1962
- }
1963
- // Exact-matching content lines required before a near-old-image window counts.
1964
- static MinNearImageMatches = 3;
1965
- // Highest per-line fuzz (strip pass) that still counts as already-applied evidence.
1966
- static MaxAppliedEvidenceFuzz = 100;
1967
- // A successfully anchored pure-insert chunk whose full post-image (context +
1968
- // inserted lines + context) already sits at the resolved slot — or immediately
1969
- // before it — would duplicate itself if applied: the file already reflects this
1970
- // chunk. Including the context lines in the comparison distinguishes "already
1971
- // applied" from a diff that intentionally duplicates adjacent lines (there the
1972
- // old-side context sits where the copy would be, so the post-image cannot match).
1973
- static AppliedAtAnchor(lines, c) {
1974
- if (!c.IsPureInsert || !c.Match.IsSuccess)
1975
- return false;
1976
- const post = c.InsertLinesWithContext();
1977
- const slot = c.Match.LineIndex;
1978
- return _HunkAnchorer.RegionEquals(lines, slot - c.ContextBefore.length, post) || _HunkAnchorer.RegionEquals(lines, slot - c.ContextBefore.length - c.InsertLines.length, post);
1979
- }
1980
- static RegionEquals(lines, start, region) {
1981
- if (start < 0 || start + region.length > lines.length)
1982
- return false;
1983
- for (let i = 0; i < region.length; i++)
1984
- if (lines[start + i] !== region[i])
1985
- return false;
1986
- return true;
1987
- }
1988
- };
1989
-
1990
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/anchorer/contextAuditor.js
1991
- var ContextDivergence = class _ContextDivergence {
1992
- ContextText;
1993
- FileText;
1994
- DiffLine;
1995
- FileLine;
1996
- constructor(ContextText, FileText, DiffLine2, FileLine) {
1997
- this.ContextText = ContextText;
1998
- this.FileText = FileText;
1999
- this.DiffLine = DiffLine2;
2000
- this.FileLine = FileLine;
2001
- }
2002
- // Mirrors Edit.Shift: selection-scoped callers see true file coordinates.
2003
- Shift(lineDelta) {
2004
- return new _ContextDivergence(this.ContextText, this.FileText, this.DiffLine, this.FileLine + lineDelta);
2005
- }
2006
- };
2007
- var ContextAuditor = class _ContextAuditor {
2008
- static Audit(appliedChunks, lines) {
2009
- const divergences = appliedChunks.filter((c) => c.Match.IsSuccess).flatMap((c) => [
2010
- ..._ContextAuditor.AuditRun(c.SuppliedContextBefore, c.Match.LineIndex - c.SuppliedContextBefore.length, lines, c),
2011
- ..._ContextAuditor.AuditRun(c.SuppliedContextAfter, c.Match.LineIndex + c.DeleteLines.length, lines, c)
2012
- ]);
2013
- const seen = /* @__PURE__ */ new Set();
2014
- return divergences.filter((d) => seen.has(d.ContextText) ? false : (seen.add(d.ContextText), true));
2015
- }
2016
- // `run` is in file order; `startIndex` is the claimed position of its first line.
2017
- static AuditRun(run, startIndex, lines, chunk) {
2018
- return run.map((ctx, i) => _ContextAuditor.AuditLine(ctx, startIndex + i, lines, chunk)).filter((d) => d != null);
2019
- }
2020
- static AuditLine(ctx, at, lines, chunk) {
2021
- if (ctx.trim().length == 0)
2022
- return null;
2023
- const inRange = at >= 0 && at < lines.length;
2024
- if (inRange && Search.LinesEquivalent(lines[at], ctx))
2025
- return null;
2026
- if (lines.some((l) => Search.LinesEquivalent(l, ctx)))
2027
- return null;
2028
- return new ContextDivergence(ctx, inRange ? lines[at] : null, chunk.DiffLocation?.StartLine ?? -1, inRange ? at + 1 : chunk.Match.LineIndex + 1);
2029
- }
2030
- };
2031
-
2032
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/selectionTarget.js
2033
- var SelectionTarget = class {
2034
- FullText;
2035
- SelectedText;
2036
- UseSelection = false;
2037
- LineOffset = 0;
2038
- get TargetText() {
2039
- return this.UseSelection ? this.SelectedText : this.FullText;
2040
- }
2041
- constructor(fullText, selection = "") {
2042
- this.FullText = fullText;
2043
- this.SelectedText = selection;
2044
- if (!(this.SelectedText == null || this.SelectedText.length == 0)) {
2045
- const hay = TextUtils.ToLines(this.FullText);
2046
- const needle = TextUtils.ToLines(this.SelectedText);
2047
- const result = Search.Find(hay, needle);
2048
- this.UseSelection = result.LineIndex >= 0;
2049
- this.LineOffset = this.UseSelection ? Math.max(0, result.LineIndex) : 0;
2050
- }
2051
- }
2052
- Replace(replace) {
2053
- if (!this.UseSelection)
2054
- return replace;
2055
- const fullLines = TextUtils.ToLines(this.FullText);
2056
- const selectionLines = TextUtils.ToLines(this.SelectedText);
2057
- const replaceLines = TextUtils.ToLines(replace);
2058
- const edit = new Edit(this.LineOffset, selectionLines, replaceLines);
2059
- edit.ApplyTo(fullLines);
2060
- return TextUtils.RoundTripWhitespace(this.FullText, fullLines);
2061
- }
2062
- };
2063
-
2064
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/shared.js
2065
- function isDiffMarker(line) {
2066
- return line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("@@") || line.startsWith("diff --git");
2067
- }
2068
- function isHunkBoundary(line, trimmed) {
2069
- return isDiffMarker(line) || trimmed.startsWith("```");
2070
- }
2071
- function isValidPrefix(char) {
2072
- return char === "+" || char === "-" || char === " ";
2073
- }
2074
- function transformOutsideHunkBodies(text, transform) {
2075
- let remainingOld = 0, remainingNew = 0;
2076
- let inBareBody = false;
2077
- const kept = [];
2078
- for (const line of text.split("\n")) {
2079
- const trimmed = line.trim();
2080
- const m = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(trimmed);
2081
- if (m) {
2082
- remainingOld = m[1] !== void 0 ? parseInt(m[1]) : 1;
2083
- remainingNew = m[2] !== void 0 ? parseInt(m[2]) : 1;
2084
- inBareBody = false;
2085
- kept.push(line);
2086
- continue;
2087
- }
2088
- if (remainingOld > 0 || remainingNew > 0) {
2089
- const c = line.length > 0 ? line[0] : " ";
2090
- if (c === "-")
2091
- remainingOld--;
2092
- else if (c === "+")
2093
- remainingNew--;
2094
- else {
2095
- remainingOld--;
2096
- remainingNew--;
2097
- }
2098
- kept.push(line);
2099
- continue;
2100
- }
2101
- if (trimmed.startsWith("@@")) {
2102
- inBareBody = true;
2103
- kept.push(line);
2104
- continue;
2105
- }
2106
- if (inBareBody && line.length > 0 && isValidPrefix(line[0]) && !isLikelyFileHeader(trimmed)) {
2107
- kept.push(line);
2108
- continue;
2109
- }
2110
- inBareBody = false;
2111
- const t = transform(line);
2112
- if (t !== null)
2113
- kept.push(t);
2114
- }
2115
- return kept.join("\n");
2116
- }
2117
- function isLikelyFileHeader(trimmed) {
2118
- return trimmed.startsWith("diff --git") || (trimmed.startsWith("--- ") || trimmed.startsWith("+++ ")) && (trimmed.includes(".") || trimmed.includes("/") || trimmed.includes("\\"));
2119
- }
2120
-
2121
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/customFormatSanitizer.js
2122
- var CustomFormatSanitizer = class _CustomFormatSanitizer {
2123
- static OPERATIONS = [
2124
- { header: /^\s*\*\*\* Update File: (.+)$/i, fromPath: (p) => `a/${p}`, toPath: (p) => `b/${p}` },
2125
- { header: /^\s*\*\*\* Add File: (.+)$/i, fromPath: (_p) => "/dev/null", toPath: (p) => `b/${p}` },
2126
- { header: /^\s*\*\*\* Delete File: (.+)$/i, fromPath: (p) => `a/${p}`, toPath: (_p) => "/dev/null" }
2127
- ];
2128
- static process(text) {
2129
- return transformOutsideHunkBodies(text, (line) => {
2130
- const hadCr = line.endsWith("\r");
2131
- const core = hadCr ? line.slice(0, -1) : line;
2132
- for (const op of _CustomFormatSanitizer.OPERATIONS) {
2133
- const m = core.match(op.header);
2134
- if (!m)
2135
- continue;
2136
- const path = m[1].trim();
2137
- const tail = hadCr ? "\r" : "";
2138
- return `--- ${op.fromPath(path)}${tail}
2139
- +++ ${op.toPath(path)}${tail}`;
2140
- }
2141
- return line;
2142
- });
2143
- }
2144
- };
2145
-
2146
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/decoratedHeaderSanitizer.js
2147
- var DecoratedHeaderSanitizer = class _DecoratedHeaderSanitizer {
2148
- static DECORATED_HEADER_REGEX = /^(---|\+\+\+)\s+(.+?)\s+(---|\+\+\+)+\s*$/;
2149
- static process(text) {
2150
- return transformOutsideHunkBodies(text, (line) => {
2151
- const hadCr = line.endsWith("\r");
2152
- const core = hadCr ? line.slice(0, -1) : line;
2153
- const replaced = core.replace(_DecoratedHeaderSanitizer.DECORATED_HEADER_REGEX, "$1 $2");
2154
- return replaced === core ? line : hadCr ? replaced + "\r" : replaced;
2155
- });
2156
- }
2157
- };
2158
-
2159
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/decorativeMarkerSanitizer.js
2160
- var DecorativeMarkerSanitizer = class _DecorativeMarkerSanitizer {
2161
- static process(text) {
2162
- return transformOutsideHunkBodies(text, (line) => _DecorativeMarkerSanitizer.isDecorativeMarker(line.trim()) ? null : line);
2163
- }
2164
- static isDecorativeMarker(line) {
2165
- if (!line)
2166
- return false;
2167
- if (/^diff --git\s+/.test(line))
2168
- return false;
2169
- if (/^[*#_]{3,}$/.test(line))
2170
- return true;
2171
- if (/^[=\-]{5,}$/.test(line))
2172
- return true;
2173
- const wholeLineKeyword = /^[*=\-#_\s]*(BEGIN|END|START|STOP)\s+(PATCH|DIFF|HUNK|SECTION|BLOCK)[*=\-#_\s]*$/i.test(line);
2174
- if (wholeLineKeyword && /[*=\-#_]{2,}/.test(line))
2175
- return true;
2176
- return false;
2177
- }
2178
- };
2179
-
2180
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/backtickSanitizer.js
2181
- var BacktickSanitizer = class {
2182
- static process(text) {
2183
- return text.replace(/^```+\s*diff\b/gmi, "```diff").replace(/^```+$/gm, "```");
2184
- }
2185
- };
2186
-
2187
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/diffBlockReunifier.js
2188
- var DiffBlockReunifier = class {
2189
- static process(text) {
2190
- const lines = text.split("\n");
2191
- const result = [];
2192
- let inDiff = false;
2193
- for (let i = 0; i < lines.length; i++) {
2194
- const line = lines[i];
2195
- const trimmed = line.trim();
2196
- if (trimmed.startsWith("```diff")) {
2197
- inDiff = true;
2198
- result.push(line);
2199
- } else if (trimmed === "```" && line.startsWith("```")) {
2200
- if (inDiff && this.hasDiffContentBeforeNextFence(lines, i)) {
2201
- continue;
2202
- }
2203
- inDiff = false;
2204
- result.push(line);
2205
- } else {
2206
- result.push(line);
2207
- }
2208
- }
2209
- return result.join("\n");
2210
- }
2211
- static hasDiffContentBeforeNextFence(lines, currentIndex) {
2212
- for (let j = currentIndex + 1; j < lines.length; j++) {
2213
- const ahead = lines[j].trim();
2214
- if (lines[j].startsWith("```"))
2215
- return false;
2216
- if (isDiffMarker(ahead))
2217
- return true;
2218
- }
2219
- return false;
2220
- }
2221
- };
2222
-
2223
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/devNullSanitizer.js
2224
- var DevNullSanitizer = class {
2225
- static process(text, fileKeys) {
2226
- if (!fileKeys?.length)
2227
- return text;
2228
- const existingFiles = new Set(fileKeys);
2229
- const lines = text.split("\n");
2230
- const result = [];
2231
- for (let i = 0; i < lines.length; i++) {
2232
- const line = lines[i];
2233
- const trimmed = line.trim();
2234
- if (this.isNewFileModeForExistingFile(trimmed, lines, i, existingFiles)) {
2235
- continue;
2236
- }
2237
- if (trimmed.startsWith("--- /dev/null")) {
2238
- const targetFile = this.getTargetFileFromNextLine(lines[i + 1]);
2239
- if (targetFile && existingFiles.has(targetFile)) {
2240
- result.push(line.replace("/dev/null", `a/${targetFile}`));
2241
- continue;
2242
- }
2243
- }
2244
- result.push(line);
2245
- }
2246
- return result.join("\n");
2247
- }
2248
- static isNewFileModeForExistingFile(line, allLines, currentIndex, existingFiles) {
2249
- if (!line.startsWith("new file mode"))
2250
- return false;
2251
- const prevLine = allLines[currentIndex - 1]?.trim() || "";
2252
- if (!prevLine.startsWith("diff --git"))
2253
- return false;
2254
- const match = prevLine.match(/diff --git\s+[ab]\/(\S+)\s+[ab]\/\S+/);
2255
- const filePath = match?.[1];
2256
- return filePath ? existingFiles.has(filePath) : false;
2257
- }
2258
- static getTargetFileFromNextLine(nextLine) {
2259
- if (!nextLine?.trim().startsWith("+++ "))
2260
- return null;
2261
- const match = nextLine.match(/^\+\+\+\s+(?:[ab]\/)?(\S+)/);
2262
- return match?.[1] ?? null;
2263
- }
2264
- };
2265
-
2266
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/sanitizers/prefixInjector.js
2267
- var PrefixInjector = class {
2268
- static process(text) {
2269
- const lines = text.split("\n");
2270
- let inHunk = false;
2271
- let deletes = { remaining: 0, consumed: 0 };
2272
- let adds = { remaining: 0, consumed: 0 };
2273
- for (let i = 0; i < lines.length; i++) {
2274
- const line = lines[i];
2275
- if (line.length === 0 && i === lines.length - 1)
2276
- continue;
2277
- const trimmed = line.trim();
2278
- if (trimmed.startsWith("@@")) {
2279
- const { deleteCount, addCount } = this.parseHunkHeader(trimmed);
2280
- inHunk = true;
2281
- deletes = { remaining: deleteCount, consumed: 0 };
2282
- adds = { remaining: addCount, consumed: 0 };
2283
- continue;
2284
- }
2285
- if (isHunkBoundary(line, trimmed)) {
2286
- inHunk = false;
2287
- continue;
2288
- }
2289
- if (inHunk) {
2290
- const first = line[0] ?? "";
2291
- const hasValidPrefix = isValidPrefix(first);
2292
- const prefix = hasValidPrefix ? first : this.inferPrefix(deletes.remaining - deletes.consumed, adds.remaining - adds.consumed);
2293
- if (!hasValidPrefix && prefix !== null) {
2294
- lines[i] = prefix + line;
2295
- }
2296
- if (prefix === "+")
2297
- adds.consumed++;
2298
- else if (prefix === "-")
2299
- deletes.consumed++;
2300
- else {
2301
- adds.consumed++;
2302
- deletes.consumed++;
2303
- }
2304
- }
2305
- }
2306
- return lines.join("\n");
2307
- }
2308
- static parseHunkHeader(header) {
2309
- const match = header.match(/@@\s*-(\d+)(?:,(\d+))?\s*\+(\d+)(?:,(\d+))?\s*@@/);
2310
- return match ? { deleteCount: parseInt(match[2] ?? "1"), addCount: parseInt(match[4] ?? "1") } : { deleteCount: 0, addCount: 0 };
2311
- }
2312
- // Only inject when the header arithmetic is unambiguous. When both deletes and
2313
- // adds remain (or the header had no usable counts), leave the line untouched —
2314
- // the parser's raw-line heuristics use content evidence and insertion-lookahead
2315
- // to classify it better than count-based guessing can.
2316
- static inferPrefix(remainingDeletes, remainingAdds) {
2317
- if (remainingDeletes > 0 && remainingAdds <= 0)
2318
- return "-";
2319
- if (remainingAdds > 0 && remainingDeletes <= 0)
2320
- return "+";
2321
- return null;
2322
- }
2323
- };
2324
-
2325
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/parser/diffSanitizer.js
2326
- var DiffSanitizer = class {
2327
- static Process(diff, fileKeys) {
2328
- if (!diff?.trim())
2329
- return diff;
2330
- let result = CustomFormatSanitizer.process(diff);
2331
- result = DecoratedHeaderSanitizer.process(result);
2332
- result = DecorativeMarkerSanitizer.process(result);
2333
- result = BacktickSanitizer.process(result);
2334
- result = DiffBlockReunifier.process(result);
2335
- result = DevNullSanitizer.process(result, fileKeys);
2336
- result = PrefixInjector.process(result);
2337
- return result;
2338
- }
2339
- };
2340
-
2341
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/headerlessRouter.js
2342
- var HeaderlessRouter = class {
2343
- static Route(fileHunks, files, options) {
2344
- const group = fileHunks.find((g) => g.Key == "");
2345
- if (group == null || files.some((f) => f.Key == ""))
2346
- return [];
2347
- const probeOptions = Object.assign(new PatchOptions(), options, { ContinueOnError: true });
2348
- const candidates = files.filter((f) => {
2349
- const target = new SelectionTarget(f.InputFullText, f.InputSelectedText).TargetText;
2350
- const anchored = HunkAnchorer.Anchor(target, group.Hunks, probeOptions);
2351
- return ChunkApplier.Apply(target, anchored.Chunks, probeOptions).Errors.length == 0;
2352
- });
2353
- if (candidates.length == 1) {
2354
- fileHunks.splice(fileHunks.indexOf(group), 1);
2355
- const existing = fileHunks.find((g) => g.Key == candidates[0].Key);
2356
- if (existing)
2357
- existing.Hunks.push(...group.Hunks);
2358
- else
2359
- fileHunks.push(new FileHunkGroup(candidates[0].Key, group.Hunks));
2360
- return [];
2361
- }
2362
- return [candidates.length == 0 ? new PatchError("MatchNotFound", group.Hunks[0].ToUnanchoredChunk(), null, "The diff has no file headers and its content did not match any file being patched. Make sure context and deleted lines match the target file, and add '--- <file>' / '+++ <file>' headers naming it.") : new PatchError("MatchAmbiguous", group.Hunks[0].ToUnanchoredChunk(), candidates.map((f) => f.Key).join(", "), "The diff has no file headers and its content matched more than one file being patched. Add '--- <file>' / '+++ <file>' headers naming the intended file.")];
2363
- }
2364
- };
2365
-
2366
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/patchNotices.js
2367
- var PatchNotices = class _PatchNotices {
2368
- static For(hunks, file, contextOnlyHunkLines, contextDivergences) {
2369
- const notices = [];
2370
- if (file.Edits.length === 0)
2371
- notices.push(file.AlreadyAppliedCount > 0 ? `Note: no edits were applied because the file already contains this change (${file.AlreadyAppliedCount} chunk(s) detected as already applied).` : "Note: no edits were applied \u2014 the diff parsed but expressed no change for this file.");
2372
- if (file.Fuzz > 0)
2373
- notices.push(`Note: applied with fuzz factor ${file.Fuzz} \u2014 the diff did not match exactly, so confirm the edits landed where you intended.`);
2374
- const suspects = TextUtils.DescribeSuspectCodePoints(file.Edits.flatMap((e) => e.InsertLines));
2375
- if (suspects)
2376
- notices.push(`Warning: written lines contain invisible or lookalike characters: ${suspects}. If these were authored as \\uXXXX escapes or plain ASCII, the tool-call layer may have mangled them; verify the file content.`);
2377
- const collapsed = hunks.flatMap((h) => h.Lines).filter((l) => l.CollapsedFrom != null).map((l) => l.CollapsedFrom);
2378
- if (collapsed.length > 0)
2379
- notices.push(`Note: ${collapsed.length} body line(s) starting '++' were read as sloppy doubled insert markers and written without the leading '+' (first: "${collapsed[0]}"). If the inserted content was meant to keep a '+' prefix (diff-shaped payload), verify the file.`);
2380
- if (hunks.some((h) => h.TruncationSuspected))
2381
- notices.push("Note: the diff's final hunk looks truncated (its header declares more lines than the body delivered). The delivered lines were applied; verify the tail of that change landed.");
2382
- if (hunks.some((h) => h.ControlCharsSuspected))
2383
- notices.push("Note: insert lines contained raw control characters and were written verbatim. That is usually transport damage rather than intended content; verify the written bytes.");
2384
- if (contextOnlyHunkLines.length > 0)
2385
- notices.push(`Note: the hunk${contextOnlyHunkLines.length === 1 ? "" : "s"} at diff line ${contextOnlyHunkLines.join(", ")} stated no change (context only), so nothing was applied there while the other edits landed. If an edit was intended, its '+'/'-' lines were lost \u2014 re-send that hunk and verify.`);
2386
- if (file.Edits.length > 0)
2387
- for (const d of contextDivergences)
2388
- notices.push((d.DiffLine > 0 ? `Note: the edit at diff line ${d.DiffLine} supplied a context line that appears nowhere in the file \u2014 ` : "Note: a supplied context line appears nowhere in the file \u2014 ") + (d.FileText != null ? `the diff said "${_PatchNotices.DivergenceQuote(d.ContextText, d.FileText)}", and at the spot the edit landed (file line ${d.FileLine}) the file reads "${_PatchNotices.DivergenceQuote(d.FileText, d.ContextText)}". ` : `the diff said "${_PatchNotices.DivergenceQuote(d.ContextText, null)}" beside an edit landing at the file's boundary (file line ${d.FileLine}), where the file has no adjacent line. `) + "The edit applied via its '-'/'+' lines alone; context lines only locate an edit. If that difference was an intended change, it was NOT applied \u2014 express it as a '-'/'+' pair.");
2389
- return notices;
2390
- }
2391
- // A quote window around the first character where the two lines diverge, so long
2392
- // prose lines that share a long head still display their difference.
2393
- static DivergenceQuote(line, other) {
2394
- const window = 72;
2395
- if (line.length <= window)
2396
- return line;
2397
- let p = 0;
2398
- if (other != null)
2399
- while (p < line.length && p < other.length && line[p] == other[p])
2400
- p++;
2401
- const start = Math.min(Math.max(p - Math.floor(window / 3), 0), line.length - window);
2402
- return (start > 0 ? "\u2026" : "") + line.slice(start, start + window) + (start + window < line.length ? "\u2026" : "");
2403
- }
2404
- };
2405
-
2406
- // ../node_modules/.pnpm/matchu-patchu@0.3.5/node_modules/matchu-patchu/dist/patcher.js
2407
- var Patcher = class {
2408
- static Apply(diff, files, options) {
2409
- options = options ?? new PatchOptions();
2410
- const fileKeys = files.map((f) => f.Key);
2411
- const fileContents = new Map(files.map((f) => [f.Key, f.InputFullText]));
2412
- diff = options.SanitizeDiff ? DiffSanitizer.Process(diff, fileKeys) : diff;
2413
- const fileHunks = UnifiedDiffParser.Parse(diff, fileContents, options.Truncation, options.ControlChars);
2414
- const routingErrors = HeaderlessRouter.Route(fileHunks, files, options);
2415
- const namedKeys = fileKeys.filter((k) => k != "");
2416
- const rosterHint = namedKeys.length > 0 ? `Files being patched: ${namedKeys.join(", ")}.` : null;
2417
- const reportEntry = (key, errors) => new PatchOutputFile(key, 0, [], "", "", "", errors);
2418
- const reportFiles = fileHunks.filter((g) => g.Key != "" && !fileContents.has(g.Key)).map((g) => reportEntry(g.Key, [new PatchError("FileMismatch", g.Hunks[0].ToUnanchoredChunk(), g.Key, rosterHint)]));
2419
- if (routingErrors.length > 0)
2420
- reportFiles.unshift(reportEntry("", routingErrors));
2421
- const reportErrors = reportFiles.flatMap((f) => f.Errors);
2422
- if (reportErrors.length > 0 && !options.ContinueOnError)
2423
- throw new PatchException(reportErrors[0]);
2424
- const patchOutputFiles = files.map((f) => {
2425
- const targetSelection = new SelectionTarget(f.InputFullText, f.InputSelectedText);
2426
- const targetText = targetSelection.TargetText;
2427
- const group = fileHunks.find((h) => h.Key == f.Key);
2428
- const hunks = group?.Hunks ?? [];
2429
- const contextOnly = group?.ContextOnlyHunkLines ?? [];
2430
- const anchored = HunkAnchorer.Anchor(targetText, hunks, options, contextOnly.length > 0);
2431
- const outputEdits = ChunkApplier.Apply(targetText, anchored.Chunks, options);
2432
- const contextDivergences = ContextAuditor.Audit(anchored.Chunks, TextUtils.ToLines(targetText)).map((d) => d.Shift(targetSelection.LineOffset));
2433
- const file = new PatchOutputFile(f.Key, outputEdits.Edits.map((h) => h.Fuzz).reduce((a, b) => a + b, 0), outputEdits.Edits.map((e) => e.Shift(targetSelection.LineOffset)), f.InputSelectedText, f.InputFullText, targetSelection.Replace(outputEdits.OutputText), [...outputEdits.Errors], anchored.AlreadyAppliedCount);
2434
- file.Notices = PatchNotices.For(hunks, file, contextOnly, contextDivergences);
2435
- return file;
2436
- });
2437
- return new PatchOutput([...patchOutputFiles, ...reportFiles]);
2438
- }
2439
- };
2440
-
2441
- // cli/agents/pi/server/piPatcherExtension.ts
2442
- var DESCRIPTION = true ? "Apply unified diffs to files \u2014 tolerant of form, strict about intent. Repairs sloppy AI-generated diffs (mangled headers, whitespace drift, missing prefixes; line numbers can be wrong or absent \u2014 hunks anchor by fuzzy-matched context lines) and applies all hunks atomically when the intent is unambiguous; fails with a precise, typed error when it isn't. Use for multi-hunk edits in one step, or when exact string-replacement editing fails on whitespace or invisible characters." : "Apply unified diffs to files.";
2443
- var BUILD_TAG = true ? "matchu-patchu-pi 0.3.5, built 2026-08-03 10:40:13" : "matchu-patchu-pi dev";
2444
- var errorBlocks = (errors) => {
2445
- const parts = [`Patch failed with ${errors.length} error(s):`];
2446
- for (const e of errors) parts.push("", e.toString());
2447
- return parts.join("\n");
2448
- };
2449
- var outcomeMessage = (target, out) => `Applied ${out.Edits.length} edit(s) to ${target}.` + (out.Edits.length > 0 ? "" : " File left unmodified.") + out.Notices.map((n) => " " + n).join("");
2450
- function discoverDiffKeys(diff) {
2451
- const keys = [];
2452
- for (const f of Patcher.Apply(diff, []).Files) {
2453
- if (f.Key && !keys.includes(f.Key)) keys.push(f.Key);
2454
- }
2455
- return keys;
2456
- }
2457
- function applySingle(cwd, filePath, diff, dryRun) {
2458
- const abs = resolve(cwd, filePath);
2459
- if (!existsSync(abs)) return { ok: false, message: `Error: file not found: ${filePath}` };
2460
- const out = Patcher.Apply(diff, [new PatchInputFile("", readFileSync(abs, "utf-8"))]).Files[0];
2461
- if (out.Errors.length > 0) return { ok: false, message: errorBlocks(out.Errors) };
2462
- if (out.Edits.length > 0 && !dryRun) writeFileSync(abs, out.OutputFullText);
2463
- return {
2464
- ok: true,
2465
- message: outcomeMessage(filePath, out) + (dryRun ? `
2466
-
2467
- --- patched output ---
2468
- ${out.OutputFullText}` : "")
2469
- };
2470
- }
2471
- function applyMulti(cwd, diff, dryRun) {
2472
- const keys = discoverDiffKeys(diff);
2473
- if (keys.length === 0)
2474
- return { ok: false, message: "Error: no file headers (---/+++) found in the diff \u2014 pass filePath to patch a headerless diff into one file" };
2475
- const missing = keys.filter((k) => !existsSync(resolve(cwd, k)));
2476
- if (missing.length > 0)
2477
- return { ok: false, message: missing.map((k) => `Error: file not found: ${k}`).join("\n") };
2478
- const inputs = keys.map((k) => new PatchInputFile(k, readFileSync(resolve(cwd, k), "utf-8")));
2479
- const result = Patcher.Apply(diff, inputs);
2480
- const errors = result.Files.flatMap((f) => f.Errors);
2481
- if (errors.length > 0) return { ok: false, message: errorBlocks(errors) };
2482
- const lines = [];
2483
- const dryOutputs = [];
2484
- for (const out of result.Files) {
2485
- if (out.Edits.length > 0) {
2486
- if (!dryRun) writeFileSync(resolve(cwd, out.Key), out.OutputFullText);
2487
- else dryOutputs.push(`--- patched output: ${out.Key} ---
2488
- ${out.OutputFullText}`);
2489
- }
2490
- lines.push(outcomeMessage(out.Key, out));
2491
- }
2492
- return { ok: true, message: [lines.join("\n"), ...dryOutputs].join("\n\n") };
2493
- }
2494
- function applyPatch(cwd, args) {
2495
- if (!args.diff || !args.diff.trim()) return { ok: false, message: "Error: patch is empty" };
2496
- try {
2497
- const out = args.filePath ? applySingle(cwd, args.filePath, args.diff, args.dryRun === true) : applyMulti(cwd, args.diff, args.dryRun === true);
2498
- return out.ok ? { ok: true, message: `${out.message} [${BUILD_TAG}]` } : out;
2499
- } catch (ex) {
2500
- if (ex instanceof PatchParserException)
2501
- return { ok: false, message: `Error: failed to parse patch \u2014 ${ex.message}` };
2502
- return { ok: false, message: `Error: ${ex instanceof Error ? ex.message : String(ex)}` };
2503
- }
2504
- }
2505
- function logErr(...a) {
2506
- try {
2507
- console.error("[matchu-patchu]", ...a);
2508
- } catch {
2509
- }
2510
- }
2511
- function patchOwnership(tools, registeredByUs) {
2512
- const patch = tools.find((t) => t.name === "patch");
2513
- if (!patch) return "absent";
2514
- return registeredByUs || /matchu-patchu/i.test(patch.sourceInfo?.path ?? "") ? "ours" : "foreign";
2515
- }
2516
- var PATCH_TOOL = {
2517
- name: "patch",
2518
- label: "Patch",
2519
- description: DESCRIPTION,
2520
- promptSnippet: "Modify one or more existing files by applying a unified diff",
2521
- promptGuidelines: [
2522
- "Use patch for every edit to an existing file; write is only for creating new files or full rewrites.",
2523
- "patch accepts lenient unified diffs: line numbers are optional and hunks anchor by context lines. One diff may target several files via ---/+++ headers; pass filePath instead for a headerless single-file diff."
2524
- ],
2525
- parameters: Type.Object({
2526
- diff: Type.String({ description: "Unified diff content to apply" }),
2527
- filePath: Type.Optional(Type.String({
2528
- description: "Path of the single file to patch (absolute or relative to the project root); the diff's headers are then ignored. Omit to target the file(s) named by the diff's ---/+++ headers."
2529
- })),
2530
- dryRun: Type.Optional(Type.Boolean({ description: "If true, return the patched content without writing to disk" }))
2531
- }),
2532
- async execute(_id, params, _signal, _onUpdate, ctx) {
2533
- const { ok, message } = applyPatch(ctx.cwd, params);
2534
- if (!ok) throw new Error(message);
2535
- return { content: [{ type: "text", text: message }], details: {} };
2536
- }
2537
- };
2538
- function piPatcherExtension_default(pi) {
2539
- let registeredPatch = false;
2540
- try {
2541
- pi.on("session_start", () => {
2542
- try {
2543
- const owner = patchOwnership(pi.getAllTools(), registeredPatch);
2544
- if (owner === "foreign") {
2545
- logErr('another extension already provides "patch" \u2014 typebulb patcher standing down (built-in edit kept)');
2546
- return;
2547
- }
2548
- if (owner === "absent") {
2549
- pi.registerTool(PATCH_TOOL);
2550
- registeredPatch = true;
2551
- }
2552
- const active = pi.getActiveTools();
2553
- if (active.includes("edit")) pi.setActiveTools(active.filter((n) => n !== "edit"));
2554
- } catch (e) {
2555
- logErr("session_start", e);
2556
- }
2557
- });
2558
- } catch (e) {
2559
- logErr("load", e);
2560
- }
2561
- }
2562
- export {
2563
- applyPatch,
2564
- piPatcherExtension_default as default,
2565
- discoverDiffKeys,
2566
- patchOwnership
2567
- };