// Generated by typebulb — do not edit; overwritten on each typebulb run.
// cli/agents/pi/server/piPatcherExtension.ts
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";
import { Type } from "typebox";
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/utils/textUtils.js
var TextUtils = class _TextUtils {
static NormalizeToLf(text) {
return (text ?? "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
}
static DetectNewline(text) {
if (!text || text.length === 0)
return "\n";
const idx = text.search(/[\r\n]/);
if (idx < 0)
return "\n";
return text[idx] === "\r" ? idx + 1 < text.length && text[idx + 1] === "\n" ? "\r\n" : "\r" : "\n";
}
/// Normalize + split to LF-only lines.
static ToLines(text) {
return _TextUtils.NormalizeToLf(text).split("\n");
}
/// Count of leading characters drawn from `chars` (default: space or tab).
static CountLeadingWhitespace(s, chars = " ") {
let i = 0;
while (i < s.length && chars.includes(s[i]))
i++;
return i;
}
/// Maps chat-layer typography (smart quotes, Unicode dashes, special
/// spaces) to ASCII. Match-only: output text is never built from this.
/// Numeric code points throughout: invisible/lookalike literals would make this
/// file unmatchable by byte-exact edit tools, including this patcher's own MCP.
static NormalizeHomoglyphs(line) {
return [...line].some((c) => c.charCodeAt(0) >= 160) ? [...line].map((c) => _TextUtils.MapHomoglyph(c)).join("") : line;
}
static MapHomoglyph(c) {
const cp = c.codePointAt(0);
if (cp >= 8216 && cp <= 8219)
return "'";
if (cp >= 8220 && cp <= 8223)
return '"';
if (cp >= 8208 && cp <= 8213 || cp === 8722)
return "-";
if (cp === 160 || cp >= 8194 && cp <= 8202 || cp === 8239 || cp === 8287 || cp === 12288)
return " ";
return c;
}
/// Removes zero-width/control code points for match-tolerant
/// comparison. Match-only, like NormalizeHomoglyphs: output text is never
/// built from this.
static StripInvisibles(line) {
return [...line].some((c) => _TextUtils.IsInvisible(c)) ? [...line].filter((c) => !_TextUtils.IsInvisible(c)).join("") : line;
}
/// Folds NFC canonical equivalence and fullwidth ASCII forms
/// (U+FF01..U+FF5E) for match-tolerant comparison. Match-only, like
/// NormalizeHomoglyphs. The NFKC compat remainder (ligatures, superscripts)
/// stays unfolded: visually distinct content must not cross-match.
static FoldCanonicalAndFullwidth(line) {
if (![...line].some((c) => c.charCodeAt(0) >= 768))
return line;
const folded = [...line].map((c) => {
const cp = c.charCodeAt(0);
return cp >= 65281 && cp <= 65374 ? String.fromCharCode(cp - 65248) : c;
}).join("");
return _TextUtils.HasUnpairedSurrogate(folded) ? folded : folded.normalize("NFC");
}
static HasUnpairedSurrogate(s) {
for (let i = 0; i < s.length; i++) {
const cp = s.charCodeAt(i);
if (cp < 55296 || cp > 57343)
continue;
const next = i + 1 < s.length ? s.charCodeAt(i + 1) : 0;
if (cp > 56319 || next < 56320 || next > 57343)
return true;
i++;
}
return false;
}
// Zero-width/control code points that render as nothing (or reorder text):
// the classes JSON-escape mangling degrades into. Tab excluded: real content.
// Numeric code points, not escapes or literals: they survive tool boundaries
// that decode backslash escapes or eat raw control chars.
static IsInvisible(c) {
const cp = c.codePointAt(0);
if (cp === 9)
return false;
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;
}
// En/em dashes are exempt from the audit: this authoring stack emits them in all
// comment prose, so warning on them fires on nearly every apply and trains readers
// to skip the audit (warning-blindness). They stay in the homoglyph table, so match
// tolerance is unchanged. Curly quotes still warn: the motivating mangle was a quote
// lookalike inside code, and prose here uses straight quotes — a curly quote is more
// likely damage than intent.
static IsIntentionalTypography(c) {
const cp = c.codePointAt(0);
return cp === 8211 || cp === 8212;
}
/// Names the invisible/lookalike code points in the given lines, or null if
/// there are none. Escape mangling at a tool-call boundary writes unintended bytes
/// silently; intent is unrecoverable afterwards, so the audit states the facts (which
/// code point, what it resembles, how many) and leaves the judgment to the caller.
///
static DescribeSuspectCodePoints(lines) {
const counts = /* @__PURE__ */ new Map();
for (const line of lines)
for (const c of line)
if (_TextUtils.IsInvisible(c) || _TextUtils.MapHomoglyph(c) !== c && !_TextUtils.IsIntentionalTypography(c))
counts.set(c, (counts.get(c) ?? 0) + 1);
if (counts.size === 0)
return null;
return [...counts.entries()].sort((a, b) => a[0].codePointAt(0) - b[0].codePointAt(0)).map(([c, n]) => {
const mapped = _TextUtils.MapHomoglyph(c);
const what = _TextUtils.IsInvisible(c) ? "invisible" : mapped === " " ? "looks like a space" : `looks like ${mapped}`;
const hex = c.codePointAt(0).toString(16).toUpperCase().padStart(4, "0");
return `U+${hex} (${what}) x${n}`;
}).join(", ");
}
/// Round-trip variant that accepts updated LF lines.
static RoundTripWhitespace(originalSnapshot, updatedLines) {
const joined = updatedLines.join("\n");
const origHadTrailing = originalSnapshot.endsWith("\n") || originalSnapshot.endsWith("\r\n") || originalSnapshot.length === 0;
let result = joined;
if (origHadTrailing && !joined.endsWith("\n") && joined.length > 0)
result += "\n";
else if (!origHadTrailing && joined.endsWith("\n"))
result = result.slice(0, -1);
const newline = _TextUtils.DetectNewline(originalSnapshot);
return newline === "\n" ? result : result.replace(/\n/g, newline);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/utils/arrayUtils.js
var ArrayUtils = class {
///
/// Groups items by key, preserving first-seen key order (like LINQ's GroupBy).
///
static GroupBy(items, keySelector) {
const map = /* @__PURE__ */ new Map();
for (const item of items) {
const key = keySelector(item);
const arr = map.get(key) || [];
arr.push(item);
map.set(key, arr);
}
const result = [];
for (const [key, values] of map.entries())
result.push({ key, values });
return result;
}
///
/// Returns a new array sorted ascending by key (stable, non-mutating, like LINQ's OrderBy).
///
static OrderBy(items, keySelector) {
return [...items].sort((a, b) => {
const ka = keySelector(a), kb = keySelector(b);
return ka < kb ? -1 : ka > kb ? 1 : 0;
});
}
///
/// Returns a new array sorted descending by key (stable, non-mutating, like LINQ's OrderByDescending).
///
static OrderByDescending(items, keySelector) {
return [...items].sort((a, b) => {
const ka = keySelector(a), kb = keySelector(b);
return ka > kb ? -1 : ka < kb ? 1 : 0;
});
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/models.js
var PatchInputFile = class {
Key;
InputFullText;
InputSelectedText;
constructor(Key, InputFullText = "", InputSelectedText = "") {
this.Key = Key;
this.InputFullText = InputFullText;
this.InputSelectedText = InputSelectedText;
}
};
var PatchOptions = class {
ContinueOnError;
MaxErrorIterations;
ContextWindowMax;
MaxChunksPerHunk;
SanitizeDiff;
Truncation;
ControlChars;
constructor(ContinueOnError = true, MaxErrorIterations = 10, ContextWindowMax = 8, MaxChunksPerHunk = 64, SanitizeDiff = true, Truncation = "warn", ControlChars = "error") {
this.ContinueOnError = ContinueOnError;
this.MaxErrorIterations = MaxErrorIterations;
this.ContextWindowMax = ContextWindowMax;
this.MaxChunksPerHunk = MaxChunksPerHunk;
this.SanitizeDiff = SanitizeDiff;
this.Truncation = Truncation;
this.ControlChars = ControlChars;
}
};
var PatchOutputFile = class {
Key;
Fuzz;
Edits;
InputSelectedText;
InputFullText;
OutputFullText;
Errors;
AlreadyAppliedCount;
Notices;
constructor(Key, Fuzz, Edits, InputSelectedText, InputFullText, OutputFullText, Errors, AlreadyAppliedCount = 0, Notices = []) {
this.Key = Key;
this.Fuzz = Fuzz;
this.Edits = Edits;
this.InputSelectedText = InputSelectedText;
this.InputFullText = InputFullText;
this.OutputFullText = OutputFullText;
this.Errors = Errors;
this.AlreadyAppliedCount = AlreadyAppliedCount;
this.Notices = Notices;
}
};
var PatchOutput = class {
Files;
constructor(Files) {
this.Files = Files;
}
};
var LineType;
(function(LineType2) {
LineType2[LineType2["Context"] = 0] = "Context";
LineType2[LineType2["Delete"] = 1] = "Delete";
LineType2[LineType2["Insert"] = 2] = "Insert";
})(LineType || (LineType = {}));
var DiffLine = class {
Type;
Text;
CollapsedFrom;
constructor(Type2, Text, CollapsedFrom = null) {
this.Type = Type2;
this.Text = Text;
this.CollapsedFrom = CollapsedFrom;
}
};
var Hunk = class {
Key;
OldText;
NewText;
OldStart;
constructor(Key, OldText, NewText, OldStart = -1) {
this.Key = Key;
this.OldText = OldText;
this.NewText = NewText;
this.OldStart = OldStart;
}
Lines = [];
///
/// 1-based start position for the new side when available (from +c,d header), otherwise -1.
/// Unused, but here for Unified Diff completeness.
///
NewStart = -1;
///
/// 0-based line number in the original diff text where this hunk's body (first diff body line) starts.
/// -1 when unavailable (e.g., parser could not determine it).
///
DiffBodyStartLine = -1;
// Set by the parser under TruncationPolicy 'warn' on the final hunk when it
// carried the tear signature.
TruncationSuspected = false;
// Set by the parser under ControlCharPolicy 'warn' when this hunk's insert
// lines carried raw control characters.
ControlCharsSuspected = false;
// Error-reporting stand-in Chunk for a hunk that was never anchored: its lines
// and diff location, no match.
ToUnanchoredChunk() {
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));
}
};
var Edit = class _Edit {
LineIndex;
DeleteLines;
InsertLines;
Fuzz;
constructor(LineIndex, DeleteLines, InsertLines, Fuzz = 0) {
this.LineIndex = LineIndex;
this.DeleteLines = DeleteLines;
this.InsertLines = InsertLines;
this.Fuzz = Fuzz;
}
Shift(lineDelta) {
return new _Edit(this.LineIndex + lineDelta, this.DeleteLines, this.InsertLines, this.Fuzz);
}
ApplyTo(lines) {
if (this.LineIndex < 0 || this.LineIndex > lines.length)
return;
const deleteCount = Math.min(this.DeleteLines.length, lines.length - this.LineIndex);
if (deleteCount > 0)
lines.splice(this.LineIndex, deleteCount);
if (this.InsertLines.length > 0)
lines.splice(this.LineIndex, 0, ...this.InsertLines);
}
};
var DiffLocation = class {
StartLine;
Length;
constructor(startOrHunk, lengthOrStartLine, endLine) {
if (typeof startOrHunk === "number") {
this.StartLine = startOrHunk;
this.Length = lengthOrStartLine ?? 0;
} else {
const hunk = startOrHunk;
const startLine = lengthOrStartLine ?? 0;
const end = endLine ?? hunk.Lines.length - 1;
this.StartLine = hunk.DiffBodyStartLine + startLine + 1;
this.Length = end - startLine + 1;
}
}
};
var FileHunkGroup = class {
Key;
Hunks;
ContextOnlyHunkLines;
constructor(Key, Hunks, ContextOnlyHunkLines = []) {
this.Key = Key;
this.Hunks = Hunks;
this.ContextOnlyHunkLines = ContextOnlyHunkLines;
}
};
var Chunk = class _Chunk {
ContextBefore;
DeleteLines;
InsertLines;
ContextAfter;
Match;
DiffLocation;
constructor(ContextBefore, DeleteLines, InsertLines, ContextAfter, Match2, DiffLocation2) {
this.ContextBefore = ContextBefore;
this.DeleteLines = DeleteLines;
this.InsertLines = InsertLines;
this.ContextAfter = ContextAfter;
this.Match = Match2;
this.DiffLocation = DiffLocation2;
}
get IsPureInsert() {
return this.DeleteLines.length == 0 && this.InsertLines.length > 0;
}
get IsPureDelete() {
return this.DeleteLines.length > 0 && this.InsertLines.length == 0;
}
HasContextLines() {
return this.ContextBefore.length > 0 || this.ContextAfter.length > 0;
}
DeleteLinesWithContext() {
return [...this.ContextBefore, ...this.DeleteLines, ...this.ContextAfter];
}
InsertLinesWithContext() {
return [...this.ContextBefore, ...this.InsertLines, ...this.ContextAfter];
}
with(values) {
return 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);
}
};
var MatchState;
(function(MatchState2) {
MatchState2[MatchState2["Success"] = 0] = "Success";
MatchState2[MatchState2["Ambiguous"] = 1] = "Ambiguous";
MatchState2[MatchState2["NotFound"] = 2] = "NotFound";
})(MatchState || (MatchState = {}));
var UniqueMatch = class _UniqueMatch {
State;
LineIndex;
Fuzz;
constructor(State, LineIndex = -1, Fuzz = 0) {
this.State = State;
this.LineIndex = LineIndex;
this.Fuzz = Fuzz;
}
get IsAmbiguous() {
return this.State == MatchState.Ambiguous;
}
get IsNotFound() {
return this.State == MatchState.NotFound;
}
get IsSuccess() {
return this.State == MatchState.Success;
}
static get Ambiguous() {
return new _UniqueMatch(MatchState.Ambiguous);
}
static get NotFound() {
return new _UniqueMatch(MatchState.NotFound);
}
with(values) {
return new _UniqueMatch(values.State ?? this.State, values.LineIndex ?? this.LineIndex, values.Fuzz ?? this.Fuzz);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/utils/yaml.js
var Yaml = class _Yaml {
_sb = [];
// -------- Public API --------
AppendLine(text) {
this._sb.push(text + "\n");
}
Section(keyWithIndent) {
const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
this._sb.push(`${indent}${key}:
`);
}
Scalar(keyWithIndent, value) {
const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
this._sb.push(`${indent}${key}: ${value}
`);
}
Folded(keyWithIndent, value, contentIndent = 2, chompStrip = true) {
this.WriteBlockScalar(">", keyWithIndent, _Yaml.SplitLines(value), contentIndent, chompStrip);
}
Block(keyWithIndent, lines, contentIndent = 2, chompStrip = true) {
this.WriteBlockScalar("|", keyWithIndent, lines ?? [], contentIndent, chompStrip);
}
ToString() {
return this._sb.join("");
}
static SplitIndentAndKey(keyWithIndent) {
const leadingSpaces = TextUtils.CountLeadingWhitespace(keyWithIndent, " ");
const indentStr = " ".repeat(leadingSpaces);
const key = keyWithIndent.replace(/^\s+/, "");
return [indentStr, key];
}
static SplitLines(value) {
if (value == null || value.length == 0)
return [];
const lf = value.indexOf("\r") >= 0 ? value.replace(/\r\n/g, "\n").replace(/\r/g, "\n") : value;
return lf.split("\n");
}
WriteBlockScalar(style, keyWithIndent, lines, contentIndent, chompStrip) {
const [indent, key] = _Yaml.SplitIndentAndKey(keyWithIndent);
const chomp = chompStrip ? "-" : "+";
this._sb.push(`${indent}${key}: ${style}${contentIndent}${chomp}
`);
const pad = " ".repeat(contentIndent);
let any = false;
for (const line of lines) {
any = true;
this._sb.push(`${indent}${pad}${line}
`);
}
if (!any) {
this._sb.push(`${indent}${pad}
`);
}
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/exceptions.js
var PatchException = class extends Error {
Error;
constructor(error) {
super(error.SuggestedFixYaml);
this.Error = error;
}
};
var PatchParserException = class extends Error {
constructor(message) {
super(message ?? "Malformed diff patch");
}
};
var PatchError = class {
Type;
FailedMatch;
// The file key(s) the error concerns: the foreign key a FileMismatch named, or
// the comma-joined candidate keys of an ambiguous headerless routing; null otherwise.
FileKey;
// Diagnosis computed against the file at error time (e.g. the quoted line
// occurs inside one longer file line); null when no confident diagnosis.
Hint;
constructor(errorType, chunk, fileKey = null, hint = null) {
this.Type = errorType;
this.FailedMatch = chunk;
this.FileKey = fileKey;
this.Hint = hint;
}
toString() {
return `Failed to patch text with the diff provided.
${this.SuggestedFixYaml}`;
}
get SuggestedFixYaml() {
const SummaryFor = (type) => ({
"MatchNotFound": "Matching lines not found; make sure the diff's context and deleted lines exactly match the original text.",
"MatchAmbiguous": "Matched multiple locations; make sure there are enough context lines to uniquely identify an edit.",
"ChunkDuplicated": "Duplicate chunks.",
"ChunkOverlapping": "Overlapping chunks.",
"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."
})[type] ?? "Patch failed.";
const diffLoc = this.FailedMatch.DiffLocation;
const y = new Yaml();
y.AppendLine("PatchError:");
y.Scalar(" Type", this.Type.toString());
if (this.FileKey != null)
y.Scalar(" File", this.FileKey);
y.Folded(" Summary", SummaryFor(this.Type));
if (this.Hint != null)
y.Folded(" Hint", this.Hint);
y.Section(" Details");
y.Section(" FailedMatch");
y.Block(" ContextBefore", this.FailedMatch.ContextBefore);
y.Block(" DeleteLines", this.FailedMatch.DeleteLines);
y.Block(" InsertLines", this.FailedMatch.InsertLines);
y.Block(" ContextAfter", this.FailedMatch.ContextAfter);
y.Section(" DiffLocation");
y.Scalar(" StartLine", diffLoc.StartLine.toString());
y.Scalar(" Length", diffLoc.Length.toString());
return y.ToString();
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/applier/guards.js
var DuplicateGuard = class _DuplicateGuard {
// Two chunks that resolve to the same anchor with the same deletes and inserts are
// provably the same edit (an LLM re-emitting a hunk). Applying it once is
// unambiguously correct, so keep the first occurrence and drop the rest — the lenient
// move, with no false-positive risk: genuinely distinct edits differ in key
// (anchor or content) and fall through to OverlapGuard.
static Dedupe(args) {
const seen = /* @__PURE__ */ new Set();
const kept = [];
for (const chunk of args.Chunks) {
const key = _DuplicateGuard.BuildKey(chunk);
if (!seen.has(key)) {
seen.add(key);
kept.push(chunk);
}
}
return new InputChunks(args.InputFullText, kept, args.Options);
}
static BuildKey(c) {
let sb = "";
sb += `${c.Match?.LineIndex ?? -1}
`;
for (const l of c.DeleteLines)
sb += `${l}
`;
sb += `\u2192
`;
for (const l of c.InsertLines)
sb += `${l}
`;
return sb.toString();
}
};
var OverlapGuard = class {
static Guard(args) {
const chunks = ArrayUtils.OrderBy(args.Chunks, (c) => c.Match?.LineIndex ?? -1);
let currentEnd = 0;
for (const chunk of chunks) {
const idx = chunk.Match?.LineIndex ?? -1;
if (idx < 0)
continue;
if (idx < currentEnd)
throw new PatchException(new PatchError("ChunkOverlapping", chunk));
if (chunk.DeleteLines.length > 0)
currentEnd = idx + chunk.DeleteLines.length;
}
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/applier/editMinimizer.js
var EditMinimizer = class _EditMinimizer {
static Same(a, b) {
return a === b;
}
///
/// Convert an edit into a sequence of minimal edits, by excluding all unchanged lines.
///
static *ToMinimalEdits(edit) {
const deleted = edit.DeleteLines;
const inserted = edit.InsertLines;
const maxLength = Math.max(deleted.length, inserted.length);
let diffStart = -1;
for (let i = 0; i <= maxLength; i++) {
const withinDeleted = i < deleted.length;
const withinInserted = i < inserted.length;
const atEnd = i == maxLength;
const isMatch = withinDeleted && withinInserted && _EditMinimizer.Same(deleted[i], inserted[i]);
const isDiff = !atEnd && (!withinDeleted || !withinInserted || !isMatch);
const isBoundary = atEnd || isMatch;
if (diffStart == -1 && isDiff) {
diffStart = i;
} else if (diffStart != -1 && isBoundary) {
const delCount = Math.max(0, Math.min(i, deleted.length) - diffStart);
const insCount = Math.max(0, Math.min(i, inserted.length) - diffStart);
const delBlock = delCount <= 0 ? [] : deleted.slice(diffStart, diffStart + delCount);
const insBlock = insCount <= 0 ? [] : inserted.slice(diffStart, diffStart + insCount);
yield new Edit(edit.LineIndex + diffStart, delBlock, insBlock, edit.Fuzz);
diffStart = -1;
}
}
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/applier/indenter.js
var Anchor = class {
BaseIndent;
Shift;
constructor(BaseIndent, Shift) {
this.BaseIndent = BaseIndent;
this.Shift = Shift;
}
};
var Indenter = class _Indenter {
static DefaultTabSize = 4;
///
/// Aligns an inserted block () to the file’s
/// indent context at , leveraging
/// ContextBefore / ContextAfter when available.
///
static AlignInsert(lines, chunk) {
const insert = chunk.InsertLines;
if (insert.length == 0)
return insert;
const tabSize = _Indenter.InferTabSize(lines.concat(insert));
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);
const reindented = new Set(chunk.DeleteLines.map((l) => _Indenter.SplitIndent(l)[1]));
const blankKeep = new Set(insert.filter((l) => !_Indenter.IsBlank(l)).map(_Indenter.LeadingWs).concat([anchor.BaseIndent]));
const tabStyle = anchor.BaseIndent.length > 0 ? anchor.BaseIndent.indexOf(" ") >= 0 : lines.some((l) => l.startsWith(" "));
return insert.map((l) => _Indenter.Adjust(l, anchor, tabSize, tabStyle, reindented, blankKeep));
}
static FindAnchor(lines, contextLines, startIndex, backward, tabSize) {
if (contextLines.length == 0)
return null;
const [ctxWs, ctxTrim] = _Indenter.SplitIndent(contextLines[backward ? contextLines.length - 1 : 0]);
const ctxW = _Indenter.VisualWidth(ctxWs, tabSize);
const step = backward ? -1 : 1;
const end = backward ? -1 : lines.length;
for (let i = startIndex; i != end; i += step) {
const [ws, trim] = _Indenter.SplitIndent(lines[i]);
if (trim != ctxTrim)
continue;
return new Anchor(ws, _Indenter.VisualWidth(ws, tabSize) - ctxW);
}
return null;
}
static Adjust(line, anchor, tabSize, tabStyle, reindented, blankKeep) {
if (_Indenter.IsBlank(line))
return anchor.Shift == 0 && blankKeep.has(line) ? line : "";
const [ws, content] = _Indenter.SplitIndent(line);
const wsWidth = _Indenter.VisualWidth(ws, tabSize);
const target = Math.max(0, wsWidth + anchor.Shift);
if (wsWidth == target && (ws.indexOf(" ") >= 0 == tabStyle || reindented.has(content)))
return ws + content;
return _Indenter.BuildIndent(anchor.BaseIndent, target, tabSize) + content;
}
static LeadingWs(line) {
return _Indenter.SplitIndent(line)[0];
}
static IsBlank(s) {
return s == null || s.length == 0 || Array.from(s).every((c) => c === " " || c === " ");
}
static SplitIndent(line) {
if (line == null || line.length == 0)
return ["", ""];
const i = TextUtils.CountLeadingWhitespace(line);
return [line.substring(0, i), line.substring(i)];
}
static InferTabSize(lines) {
for (const ws of Array.from(lines).map(_Indenter.LeadingWs)) {
const lastTab = ws.lastIndexOf(" ");
if (lastTab >= 0) {
const spaces = ws.length - lastTab - 1;
if (spaces > 0)
return spaces;
}
}
return _Indenter.DefaultTabSize;
}
static VisualWidth(indent, tabSize) {
return Array.from(indent).map((c) => c == " " ? tabSize : 1).reduce((a, b) => a + b, 0);
}
static BuildIndent(baseIndent, targetW, tabSize) {
if (targetW <= 0)
return "";
const baseW = _Indenter.VisualWidth(baseIndent, tabSize);
const delta = targetW - baseW;
if (delta == 0)
return baseIndent;
if (delta > 0)
return baseIndent + " ".repeat(delta);
return baseIndent.indexOf(" ") >= 0 ? " ".repeat(Math.floor(targetW / tabSize)) + " ".repeat(targetW % tabSize) : " ".repeat(targetW);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/applier/matchHint.js
var MatchHint = class _MatchHint {
// A quoted line inside exactly one longer file line is the prose line-model
// mistake: the author's "line" (a sentence of an unwrapped paragraph, or a
// re-flowed hard wrap) is a slice of the file's physical line. Matching stays
// refused — a head fragment is byte-identical to an elided whole-line delete,
// so either auto-reading risks silent corruption — but naming the line is
// correct under both readings: the retry re-quotes it whole.
static SubLine(chunk, lines) {
for (const q of chunk.DeleteLinesWithContext()) {
if (q.trim().length < _MatchHint.MinHintLineContent)
continue;
if (lines.includes(q))
continue;
const containing = [];
for (let i = 0; i < lines.length && containing.length < 2; i++)
if (lines[i].length > q.length && lines[i].includes(q))
containing.push(i);
if (containing.length != 1)
continue;
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.`;
}
return null;
}
static Preview(s) {
const t = s.trim();
return t.length <= 60 ? t : t.slice(0, 57) + "...";
}
// Trimmed length a quoted line must reach before containment counts as
// evidence: short strings occur inside longer lines by coincidence.
static MinHintLineContent = 12;
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/applier/chunkApplier.js
var InputChunks = class {
InputFullText;
Chunks;
Options;
constructor(InputFullText, Chunks, Options) {
this.InputFullText = InputFullText;
this.Chunks = Chunks;
this.Options = Options;
}
};
var OutputEdits = class {
OutputText;
Edits;
Errors;
constructor(OutputText, Edits, Errors) {
this.OutputText = OutputText;
this.Edits = Edits;
this.Errors = Errors;
}
};
var ChunkApplier = class _ChunkApplier {
// NOTE: on partial failure, Apply is all-or-nothing — it returns the ORIGINAL
// text untouched alongside the errors, never a best-effort partial application.
static Apply(targetText, chunks, options) {
const inputChunks = new InputChunks(targetText, chunks, options);
if (!options.ContinueOnError)
return _ChunkApplier.ApplyChunks(inputChunks);
const errors = [];
for (let attempt = 0; attempt < options.MaxErrorIterations; attempt++) {
try {
const r = _ChunkApplier.ApplyChunks(inputChunks);
if (errors.length)
return new OutputEdits(targetText, [], errors);
return r;
} catch (ex) {
if (ex instanceof PatchException) {
errors.push(ex.Error);
const idx = chunks.indexOf(ex.Error.FailedMatch);
if (idx >= 0)
chunks.splice(idx, 1);
} else
throw ex;
}
}
return new OutputEdits(targetText, [], errors);
}
static ApplyChunks(args) {
args = DuplicateGuard.Dedupe(args);
OverlapGuard.Guard(args);
const lines = TextUtils.ToLines(args.InputFullText);
const edits = args.Chunks.map((chunk) => _ChunkApplier.CreateEdit(chunk, lines));
const workingLines = Array.from(lines);
for (const edit of ArrayUtils.OrderByDescending(edits, (e) => e.LineIndex))
edit.ApplyTo(workingLines);
const patched = TextUtils.RoundTripWhitespace(args.InputFullText, workingLines);
const minimalEdits = edits.flatMap((e) => Array.from(EditMinimizer.ToMinimalEdits(e)));
return new OutputEdits(patched, minimalEdits, []);
}
static CreateEdit(chunk, lines) {
const match = chunk.Match;
if (match.IsAmbiguous)
throw new PatchException(new PatchError("MatchAmbiguous", chunk));
if (match.IsNotFound)
throw new PatchException(new PatchError("MatchNotFound", chunk, null, MatchHint.SubLine(chunk, lines)));
return new Edit(match.LineIndex, Array.from(chunk.DeleteLines), Indenter.AlignInsert(lines, chunk), match.Fuzz);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/heuristics.js
var DiffContentHeuristics = class _DiffContentHeuristics {
/**
* Checks if a line starts with a doubled marker (++ or --)
*/
static IsDoubleMarker(line) {
return line.length > 1 && line[0] == line[1] && (line[0] == "-" || line[0] == "+");
}
/**
* Determines if a doubled marker represents content rather than diff syntax.
*
* Examples:
* - `--bg-color` → true (CSS variable, content)
* - `--` → true (exactly two hyphens, content)
* - `-- text` → false (deletion of markdown list `- text`, diff syntax)
* - `---comment` → false (deletion of `--comment`, diff syntax)
* - `----` → false (deletion of `--`, diff syntax)
* - `++var` → true (C++ code, content)
* - `++ text` → false (insertion of `+ text`, diff syntax)
* - `+++` → false (insertion of `+`, diff syntax)
*/
static IsDoubleMarkerContent(line) {
if (!_DiffContentHeuristics.IsDoubleMarker(line))
return false;
if (line.length == 2)
return true;
return line[2] != line[0] && line[2] != " ";
}
/**
* A '++X' line that is a candidate for the sloppy doubled-insert-marker
* collapse ('++X' → insert 'X'). Tripled markers are proper syntax
* ('+++X' = insert '++X') and never candidates.
*/
static IsSloppyDoublePlus(line) {
return line.length > 1 && line[0] == "+" && line[1] == "+" && (line.length == 2 || line[2] != "+");
}
/**
* True when the hunk body proves the edit region is itself diff-shaped:
* a context or deletion line whose CONTENT starts with '+' or '-' exists
* verbatim in the file. In such a region, doubled markers are diff syntax
* over diff-shaped content, not sloppy doubling.
*/
static IsDiffShapedRegion(rawLines, fileLines) {
if (!fileLines || fileLines.length == 0)
return false;
for (const l of rawLines) {
if (l.length < 2 || l[0] != " " && l[0] != "-")
continue;
if (l[1] != "+" && l[1] != "-")
continue;
const content = l.substring(1);
if (fileLines.some((f) => f == content))
return true;
}
return false;
}
/**
* Validates whether a line (after stripping indentation) represents valid diff syntax.
* Used by StripIndent to determine if indentation should be removed.
*
* Returns false for content that looks like diff markers:
* - `--text` (CSS variables)
* - `-abc-*` (hyphenated identifiers like -webkit-*)
*/
static IsValidDiffLineStart(line) {
if (line.length == 0)
return false;
const first = line[0];
if (first == " " || first == "+")
return true;
if (first == "-") {
if (line.length == 1)
return true;
if (line[1] == "-")
return line.length > 2 && line[2] == "-";
if (_DiffContentHeuristics.IsHyphenatedIdentifier(line))
return false;
return true;
}
return false;
}
/**
* Pattern: -[a-z]+- (e.g., -webkit-*, -moz-*)
* If hyphen is followed by letters then another hyphen, it's structural content.
*/
static IsHyphenatedIdentifier(line) {
if (line.length < 3 || line[0] != "-")
return false;
let i = 1;
while (i < line.length && line[i] >= "a" && line[i] <= "z")
i++;
return i > 1 && i < line.length && line[i] == "-";
}
/**
* Applies heuristics to classify raw lines (lines without explicit diff markers).
*
* Heuristic: Raw lines are treated as context until we've seen explicit context markers.
* Once explicit context has been seen, a raw line followed by an insertion before a
* context/deletion boundary is treated as a deletion.
*
* This helps handle sloppy diffs where deletion markers are omitted but can be inferred.
*/
static ApplyRawLineHeuristic(raw, rawLines, index, seenExplicitContext, fileLines) {
const insAhead = _DiffContentHeuristics.HasInsertionAheadBeforeContextOrDeletion(rawLines, index);
if (insAhead && seenExplicitContext) {
return new DiffLine(LineType.Delete, _DiffContentHeuristics.StripInlineComment(raw, fileLines));
}
if (insAhead) {
const verified = _DiffContentHeuristics.TryVerifiedCommentStrip(raw, fileLines);
if (verified !== null)
return new DiffLine(LineType.Delete, verified);
}
return new DiffLine(LineType.Context, raw);
}
static InlineCommentMarkers = [" //", " --", " #"];
/**
* Returns the comment-stripped form of a line when file content verifies it:
* the full line must NOT exist in the file and the stripped variant MUST — so
* real content containing " --" (SQL) or " #" (Python) is never mangled.
* Returns null when no verified strip exists.
*/
static TryVerifiedCommentStrip(input, fileLines) {
if (!fileLines || fileLines.length == 0)
return null;
if (fileLines.some((l) => l === input))
return null;
for (const marker of _DiffContentHeuristics.InlineCommentMarkers) {
for (let idx = input.indexOf(marker); idx >= 0; idx = input.indexOf(marker, idx + 1)) {
const candidate = input.substring(0, idx).trimEnd();
if (fileLines.some((l) => l === candidate))
return candidate;
}
}
return null;
}
/**
* Strips an inline comment the LLM appended to an inferred deletion line
* (e.g., "Line2 // Missing - marker" → "Line2").
* Prefers a content-verified strip; without file content (or when
* verification is inconclusive), falls back to stripping C-style " //" only.
*/
static StripInlineComment(input, fileLines) {
if (fileLines?.some((l) => l === input))
return input;
const verified = _DiffContentHeuristics.TryVerifiedCommentStrip(input, fileLines);
if (verified !== null)
return verified;
const idx = input.indexOf(" //");
return idx >= 0 ? input.substring(0, idx) : input;
}
/**
* Checks if there's an insertion (line starting with '+') ahead of the current position,
* before encountering a context line, deletion line, or blank line (boundaries).
*
* Used to infer whether a raw line should be treated as a deletion.
*/
static HasInsertionAheadBeforeContextOrDeletion(rawLines, currentIndex) {
for (let i = currentIndex + 1; i < rawLines.length; i++) {
const l = rawLines[i];
if (l.length == 0)
break;
const c = l[0];
if (c == " ")
break;
if (c == "-" && !_DiffContentHeuristics.IsDoubleMarkerContent(l))
break;
if (c == "+")
return true;
}
return false;
}
/**
* Checks if a line starting with "-" or "+" is likely content with a missing context prefix.
* Uses file content to disambiguate: if file has the line but NOT the would-be-modified content.
*
* Examples:
* - "- list item" might be markdown bullet, not deletion of " list item"
* - "+ Add feature" might be changelog entry, not insertion of " Add feature"
*
* @returns true if line should be treated as context, false if it should be treated as diff operation
*/
static IsMissingContextPrefix(line, fileLines) {
if (line.length < 2)
return false;
const marker = line[0];
if (marker !== "-" && marker !== "+")
return false;
if (marker === "+" && line[1] !== " ")
return false;
const hasContentMatch = fileLines.some((l) => l === line);
const hasOperationMatch = fileLines.some((l) => l === line.substring(1));
return hasContentMatch && !hasOperationMatch;
}
/**
* Resolves ambiguity when stripping indentation reveals "- " (hyphen + space).
*
* This pattern is ambiguous:
* - Could be an indented DELETE line (deleting content starting with a space)
* - Could be a properly formatted CONTEXT line containing a markdown bullet
*
* Example: " - Label them..." could mean:
* - DELETE: Remove line " Label them..." (space + content) from file
* - CONTEXT: Keep line " - Label them..." (3 spaces + bullet) in file
*
* Resolution: Check which interpretation matches the actual file content.
* If only one matches, use that. If both or neither, prefer CONTEXT (conservative).
*
* @param original - The original line before stripping (e.g., " - Label them...")
* @param stripped - The line after stripping indentation (e.g., "- Label them...")
* @param fileLines - Lines from the actual file being patched
* @returns The line to use (original if CONTEXT, stripped if DELETE)
*/
static ResolveHyphenSpaceAmbiguity(original, stripped, fileLines) {
const contextContent = original.length > 0 && original[0] === " " ? original.substring(1) : original;
const deleteContent = stripped.substring(1);
const hasContextMatch = fileLines.some((line) => line === contextContent);
const hasDeleteMatch = fileLines.some((line) => line === deleteContent);
if (hasContextMatch && !hasDeleteMatch)
return original;
if (hasDeleteMatch && !hasContextMatch)
return stripped;
return original;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/hunkBuilder.js
var Header = class _Header {
Path;
OldStart;
OldCount;
NewStart;
NewCount;
DiffBodyStartLine;
constructor(Path = null, OldStart = -1, OldCount = 0, NewStart = -1, NewCount = 0, DiffBodyStartLine = -1) {
this.Path = Path;
this.OldStart = OldStart;
this.OldCount = OldCount;
this.NewStart = NewStart;
this.NewCount = NewCount;
this.DiffBodyStartLine = DiffBodyStartLine;
}
static WithParsed(h, parsedHdr, diffBodyStartLine) {
return new _Header(h.Path, parsedHdr.OldStart, parsedHdr.OldCount, parsedHdr.NewStart, parsedHdr.NewCount, diffBodyStartLine);
}
static ParseSide(header, part, prefix, isOld) {
if (!part.startsWith(prefix))
return header;
const split = part.substring(1).split(",");
const start = split.length > 0 && !isNaN(parseInt(split[0])) ? parseInt(split[0]) : -1;
const count = split.length > 1 && !isNaN(parseInt(split[1])) ? parseInt(split[1]) : 1;
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);
}
};
var HunkBuilder = class _HunkBuilder {
hunks = [];
bodySeen = /* @__PURE__ */ new Map();
get Hunks() {
return this.hunks;
}
// Only the diff's FINAL hunk can be torn by a cutoff, so the parser reads this
// once, after the last commit; mid-diff the same signature is just sloppy counting.
LastHunkTruncated = false;
// A closing ``` fence is completion evidence a token cutoff cannot emit — the
// tear signature on a fence-closed hunk is miscount slop, not truncation.
// (Trailing prose already clears the flag via the pure-context commit path.)
ClearTruncation() {
this.LastHunkTruncated = false;
}
// Diff line of the first @@-headed body with real content but no change lines.
// Such a hunk can never be intentional (its '+'/'-' markers were lost, and the
// loss is ambiguous — adds or deletes?), so the parser rejects the whole diff
// instead of silently dropping it. Bodies outside any hunk (prose before or
// after a diff) and blank-only bodies keep flowing through silently.
// Diff line of each @@-headed body that had real content but no change lines. Such a
// hunk states no change, so it is skipped and its siblings apply — but the lines are
// surfaced so the caller can name the skip rather than report a clean success. Bodies
// outside any hunk (prose before or after a diff) and blank-only bodies are not hunks
// at all and are never recorded.
ContextOnlyHunkLines = [];
CommitIfAny(hdr, body, fileLines) {
if (body.length == 0)
return;
const blankContextDeletion = _HunkBuilder.IsBlankContextDeletion(hdr.OldCount, hdr.NewCount, body);
if (!blankContextDeletion && !body.some((l) => l.length > 0 && (l[0] == "-" || l[0] == "+"))) {
this.LastHunkTruncated = false;
if (hdr.DiffBodyStartLine >= 0 && body.some((l) => l.trim().length > 0))
this.ContextOnlyHunkLines.push(hdr.DiffBodyStartLine);
body.length = 0;
return;
}
const diffLines = _HunkBuilder.BuildLines(body, blankContextDeletion, fileLines);
this.LastHunkTruncated = _HunkBuilder.IsTruncated(hdr, diffLines);
const oldSb = [];
const newSb = [];
let oldLines = 0, newLines = 0;
for (const dl of diffLines) {
switch (dl.Type) {
case LineType.Insert:
newSb.push(dl.Text, "\n");
newLines++;
break;
case LineType.Delete:
oldSb.push(dl.Text, "\n");
oldLines++;
break;
case LineType.Context:
oldSb.push(dl.Text, "\n");
oldLines++;
newSb.push(dl.Text, "\n");
newLines++;
break;
}
}
const oldTxt = oldLines > 0 ? oldSb.join("") : "";
const newTxt = newLines > 0 ? newSb.join("") : "";
if (oldTxt == newTxt) {
body.length = 0;
return;
}
const hunk = new Hunk(hdr.Path ?? "", oldTxt, newTxt, hdr.OldStart);
hunk.NewStart = hdr.NewStart;
hunk.Lines = diffLines;
hunk.DiffBodyStartLine = hdr.DiffBodyStartLine;
this.AddHunk(hunk);
body.length = 0;
}
// Token-limit cutoff: body ends mid-change-run and the header declares more lines
// than delivered, asymmetrically. Symmetric deficits are stripped context (the
// anchorer recovers) and never count. An insert-ending body is torn only when the
// old side is ALSO short (promised trailing context never arrived): an old side
// fully delivered is complete by its own header, and a new-side deficit alone is
// overcount slop that must still apply — a complete append with a miscounted
// header is indistinguishable from a mid-run cut, and counts are tie-breakers,
// never sole grounds for rejection. A delete-ending body is the mirror: torn only
// when the NEW side is also short (newDeficit > 0) AND the old side is shorter
// still (the delete run was cut), so an over-counted-but-complete delete hunk
// (newDeficit == 0) applies as overcount slop. Cost (both branches): a cut with no
// trailing context owed on the completed side goes undetected.
static IsTruncated(hdr, lines) {
if (hdr.OldStart < 0 || hdr.NewStart < 0 || lines.length == 0)
return false;
const last = lines[lines.length - 1].Type;
if (last == LineType.Context)
return false;
const actualOld = lines.filter((l) => l.Type != LineType.Insert).length;
const actualNew = lines.filter((l) => l.Type != LineType.Delete).length;
const oldDeficit = hdr.OldCount - actualOld, newDeficit = hdr.NewCount - actualNew;
return last == LineType.Insert ? oldDeficit > 0 && newDeficit > oldDeficit : newDeficit > 0 && oldDeficit > newDeficit;
}
// Identical bodies collapse when the repeat adds no placement info (bare @@ or a
// start line already seen) — LLM-slop duplication. But git legitimately emits
// byte-identical hunks at distinct line numbers when a file repeats a pattern;
// those are separate edits, and the anchorer's line-number tiebreak places each
// at its own site. Distinct bodies always survive.
AddHunk(h) {
const bodyKey = `${h.OldText}
\u241E
${h.NewText}`;
const starts = this.bodySeen.get(bodyKey);
if (starts) {
if (h.OldStart < 0 || starts.has(-1) || starts.has(h.OldStart))
return;
starts.add(h.OldStart);
} else {
this.bodySeen.set(bodyKey, /* @__PURE__ */ new Set([h.OldStart]));
}
this.hunks.push(h);
}
static BuildLines(rawLines, blankContextDeletion, fileLines) {
const result = [];
let seenExplicitContext = false;
const diffShaped = DiffContentHeuristics.IsDiffShapedRegion(rawLines, fileLines);
for (let i = 0; i < rawLines.length; i++) {
const raw = rawLines[i];
if (raw.length == 0) {
if (i == rawLines.length - 1)
continue;
result.push(new DiffLine(blankContextDeletion ? LineType.Delete : LineType.Context, ""));
continue;
}
if (blankContextDeletion) {
const c = raw[0];
result.push(new DiffLine(LineType.Delete, c == " " || c == "-" ? _HunkBuilder.RemoveDiffPrefix(raw) : raw));
continue;
}
switch (raw[0]) {
case "+":
case "-":
if (fileLines && DiffContentHeuristics.IsMissingContextPrefix(raw, fileLines)) {
result.push(new DiffLine(LineType.Context, raw));
seenExplicitContext = true;
break;
}
if (raw[0] === "+") {
if (DiffContentHeuristics.IsSloppyDoublePlus(raw)) {
result.push(diffShaped ? new DiffLine(LineType.Insert, raw.substring(1)) : new DiffLine(LineType.Insert, _HunkBuilder.RemoveDiffPrefix(raw), raw));
break;
}
result.push(new DiffLine(LineType.Insert, _HunkBuilder.RemoveDiffPrefix(raw)));
break;
}
if (DiffContentHeuristics.IsDoubleMarkerContent(raw)) {
if (fileLines && fileLines.some((l) => l == raw.substring(1)) && !fileLines.some((l) => l == raw)) {
result.push(new DiffLine(LineType.Delete, raw.substring(1)));
break;
}
if (seenExplicitContext && raw.length > 2 && raw[2] === " ") {
result.push(new DiffLine(LineType.Delete, raw.substring(1)));
} else {
result.push(DiffContentHeuristics.ApplyRawLineHeuristic(raw, rawLines, i, seenExplicitContext, fileLines));
}
} else {
result.push(new DiffLine(LineType.Delete, _HunkBuilder.RemoveDiffPrefix(raw)));
}
break;
case " ":
result.push(new DiffLine(LineType.Context, _HunkBuilder.RemoveDiffPrefix(raw)));
seenExplicitContext = true;
break;
default:
result.push(DiffContentHeuristics.ApplyRawLineHeuristic(raw, rawLines, i, seenExplicitContext, fileLines));
break;
}
}
return result;
}
static IsBlankContextDeletion(oldCount, newCount, rawLines) {
return oldCount > 0 && newCount == 0 && rawLines.every((l) => l.length == 0 || l[0] == " ");
}
// Strip diff prefix: ` foo` → `foo`, `-bar` → `bar`, `+baz` → `baz`
// Sloppy doubled markers: `++code` → `code`, `++ code` → `code`
// But NOT tripled: `---comment` → `--comment` (proper syntax for deleting `--comment`)
// And NOT `-- text` which is deletion of `- text` (markdown list), not sloppy
static RemoveDiffPrefix(s) {
if (!s || s.length <= 1)
return "";
let result = s.substring(1);
if (DiffContentHeuristics.IsSloppyDoublePlus(s)) {
result = result.substring(1);
if (result[0] === " " || result[0] === " ")
result = result.substring(1);
}
return result;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/unifiedDiffParser.js
var UnifiedDiffParser = class _UnifiedDiffParser {
/**
* Parse a unified diff into structured hunks.
* @param diff - The diff text to parse
* @param files - Map of filename to content (content used for disambiguation of ambiguous lines)
* @param truncation - Verdict for a surviving tear signature on the final hunk
* @param controlChars - Verdict for raw control characters in insert lines
*/
static Parse(diff, files, truncation = "warn", controlChars = "error") {
const parsed = diff == null || diff.trim().length == 0 ? { Hunks: [], ContextOnlyLines: [] } : _UnifiedDiffParser.ParseHunks(diff, files, truncation, controlChars);
const hunks = parsed.Hunks;
const ctxOnly = parsed.ContextOnlyLines;
const fileKeySet = new Set(files.keys());
const headerless = hunks.filter((h) => h.Key == null || h.Key == "");
const explicitKnown = hunks.filter((h) => h.Key != null && h.Key != "" && fileKeySet.has(h.Key));
const explicitFallback = hunks.filter((h) => h.Key != null && h.Key != "" && !fileKeySet.has(h.Key));
const groups = [];
if (headerless.length > 0)
groups.push(new FileHunkGroup("", headerless, ctxOnly));
if (explicitKnown.length > 0)
ArrayUtils.GroupBy(explicitKnown, (h) => h.Key).map((g) => new FileHunkGroup(g.key, g.values, ctxOnly)).forEach((x) => groups.push(x));
const treatAsSingleFile = fileKeySet.size == 1 && fileKeySet.has("") && explicitKnown.length == 0 && headerless.length == 0 && Array.from(new Set(explicitFallback.map((h) => h.Key))).length == 1;
if (treatAsSingleFile && explicitFallback.length > 0)
groups.push(new FileHunkGroup("", explicitFallback, ctxOnly));
else if (explicitFallback.length > 0)
ArrayUtils.GroupBy(explicitFallback, (h) => h.Key).map((g) => new FileHunkGroup(g.key, g.values, ctxOnly)).forEach((x) => groups.push(x));
if (groups.length == 0 && ctxOnly.length > 0)
groups.push(new FileHunkGroup("", [], ctxOnly));
return groups;
}
static ParseHunks(diff, fileContents, truncation = "warn", controlChars = "error") {
const hunkBuilder = new HunkBuilder();
const body = [];
let header = new Header();
let inFence = false;
let inHunkBody = false;
let lineNo = -1;
const defaultFileLines = fileContents?.size == 1 && fileContents.has("") ? _UnifiedDiffParser.ToFileLines(fileContents.get("")) : void 0;
let currentFileLines = defaultFileLines;
let fileLineSet = _UnifiedDiffParser.ToLineSet(currentFileLines);
for (const raw of _UnifiedDiffParser.StripCommonIndent(diff.replace(/\r\n/g, "\n")).split("\n")) {
lineNo++;
if (raw.length > 1 && raw[0] == " " && fileLineSet != null && fileLineSet.has(raw.slice(1).replace(/\s+$/, ""))) {
body.push(raw);
continue;
}
const line = _UnifiedDiffParser.StripIndent(raw, currentFileLines);
if (line.startsWith("```diff")) {
inFence = true;
continue;
}
if (!inFence && line.startsWith("```")) {
continue;
}
if (inFence && line.startsWith("```")) {
hunkBuilder.CommitIfAny(header, body, currentFileLines);
hunkBuilder.ClearTruncation();
header.DiffBodyStartLine = -1;
inFence = false;
inHunkBody = false;
continue;
}
if (_UnifiedDiffParser.IsMeta(line))
continue;
const path = _UnifiedDiffParser.TryParseFileHeader(line);
if (path && _UnifiedDiffParser.IsFileHeader(line, path, inHunkBody, fileContents, fileLineSet, body, currentFileLines)) {
hunkBuilder.CommitIfAny(header, body, currentFileLines);
header = new Header(path);
currentFileLines = _UnifiedDiffParser.ToFileLines(fileContents?.get(path)) ?? defaultFileLines;
fileLineSet = _UnifiedDiffParser.ToLineSet(currentFileLines);
inHunkBody = false;
continue;
}
const parsedHdr = _UnifiedDiffParser.TryParseHunkHeader(line);
if (parsedHdr) {
hunkBuilder.CommitIfAny(header, body, currentFileLines);
header = Header.WithParsed(header, parsedHdr, lineNo + 1);
inHunkBody = true;
continue;
}
body.push(line);
}
hunkBuilder.CommitIfAny(header, body, currentFileLines);
const hunks = hunkBuilder.Hunks;
if (hunkBuilder.LastHunkTruncated && truncation != "ignore") {
if (truncation == "error")
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.");
if (hunks.length > 0)
hunks[hunks.length - 1].TruncationSuspected = true;
}
if (controlChars != "ignore") {
for (const hunk of hunks) {
const suspect = _UnifiedDiffParser.DescribeControlChars(hunk.Lines.filter((l) => l.Type == LineType.Insert).map((l) => l.Text));
if (suspect == null)
continue;
if (controlChars == "error")
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).`);
hunk.ControlCharsSuspected = true;
}
}
return { Hunks: hunks, ContextOnlyLines: hunkBuilder.ContextOnlyHunkLines };
}
static IsSuspectControlChar(c) {
const cp = c.charCodeAt(0);
return cp < 32 && c != " " && c != "\n" && c != "\r" && c != "\f" || cp == 127;
}
// "U+0000 x2, U+001B x1" — names the invisible damage so the caller can see it.
static DescribeControlChars(lines) {
const counts = /* @__PURE__ */ new Map();
for (const line of lines)
for (const c of line)
if (_UnifiedDiffParser.IsSuspectControlChar(c))
counts.set(c, (counts.get(c) ?? 0) + 1);
if (counts.size == 0)
return null;
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(", ");
}
static MetaPrefixes = [
"index ",
"new file mode ",
"deleted file mode ",
"similarity index ",
"rename from ",
"rename to ",
"Binary files ",
"GIT binary patch",
"mode change "
];
static IsMeta(line) {
return _UnifiedDiffParser.MetaPrefixes.some((p) => line.startsWith(p));
}
static ToFileLines(content) {
return content ? content.replace(/\r\n/g, "\n").split("\n") : void 0;
}
static ToLineSet(lines) {
return lines ? new Set(lines.map((l) => l.replace(/\s+$/, ""))) : void 0;
}
// Strips the longest common whitespace prefix from all non-empty diff body lines.
// This normalizes diffs that are uniformly indented (e.g. pasted inside a markdown block
// or another indented structure), while preserving the single-space context markers on
// lines whose content happens to start with '+' or '-'.
//
// @@ hunk headers are excluded from the prefix calculation because an LLM may indent
// body lines but not the header (or vice versa). The strip is applied only to lines
// that actually start with the computed prefix, so unindented headers are left intact.
//
// Runs before the per-line StripIndent pass: this handles uniform indentation
// (including a single-space indent that per-line stripping must not touch), while
// StripIndent handles ragged 2+ whitespace indentation line by line.
static StripCommonIndent(diff) {
const lines = diff.split("\n");
let common = null;
for (const line of lines) {
if (line.length == 0)
continue;
const i = TextUtils.CountLeadingWhitespace(line);
if (line.startsWith("@@", i))
continue;
const prefix = line.substring(0, i);
if (common == null) {
common = prefix;
continue;
}
const minLen = Math.min(common.length, prefix.length);
let j = 0;
while (j < minLen && common[j] == prefix[j])
j++;
common = common.substring(0, j);
if (common.length == 0)
return diff;
}
if (common == null || common.length == 0)
return diff;
const c = common;
if (!lines.some((l) => l.startsWith(c) && l.length > c.length && (l[c.length] == "+" || l[c.length] == "-")))
return diff;
return lines.map((l) => l.startsWith(c) ? l.substring(c.length) : l).join("\n");
}
static StripIndent(s, fileLines) {
if (s == null || s.length == 0)
return s;
const stripped = s.replace(/^[ \t]{2,}/, "");
if (fileLines && stripped.length > 1 && stripped[0] === "-" && stripped[1] === " ") {
return DiffContentHeuristics.ResolveHyphenSpaceAmbiguity(s, stripped, fileLines);
}
if (fileLines && stripped.length > 0 && stripped[0] === "+" && fileLines.some((f) => f.trim() == stripped.trimEnd()))
return s;
return DiffContentHeuristics.IsValidDiffLineStart(stripped) ? stripped : s;
}
static TryParseFileHeader(line) {
if (!(line.startsWith("diff --git ") || line.startsWith("--- ") || line.startsWith("+++ "))) {
return null;
}
const path = line.startsWith("diff --git ") ? _UnifiedDiffParser.Canon(line.split(" ").filter((s) => s.length > 0)[2]) : _UnifiedDiffParser.Canon(line.length >= 4 ? line.substring(4) : "");
return path;
}
// Inside a hunk body, `--- foo` could be deletion of `-- foo` (SQL comment,
// prose) and `+++ foo` insertion of `++ foo` — or the next file's header.
// Evidence ladder: a transition to a file we HOLD
// is a real header; otherwise content decides — the delete reading wins when
// the current file contains `-- foo` verbatim, the insert reading when the
// body so far is a verified diff-shaped region (real header pairs never hit
// this: their `+++` follows a header-read `---`, which ends the body). Only
// in an evidence vacuum does the structure heuristic decide: real paths have
// `.` (extension) or `/` (directory); prose doesn't.
static IsFileHeader(line, path, inHunkBody, files, fileLineSet, body, fileLines) {
if (!inHunkBody)
return true;
if (line.startsWith("diff --git "))
return true;
if (path != "" && files?.has(path))
return true;
if (line.startsWith("--- ") && fileLineSet != null && fileLineSet.has(line.slice(1).replace(/\s+$/, "")))
return false;
if (line.startsWith("+++ ") && body != null && DiffContentHeuristics.IsDiffShapedRegion(body, fileLines))
return false;
return _UnifiedDiffParser.LooksLikePath(path);
}
static LooksLikePath(path) {
return path.includes(".") || path.includes("/") || path.includes("\\") || path === "/dev/null";
}
static Canon(p) {
if (p == null || p.length == 0)
return p;
p = p.trim();
if (p.startsWith("a/") || p.startsWith("b/"))
p = p.substring(2);
return p;
}
static TryParseHunkHeader(line) {
const t = line.trim();
if (!t.startsWith("@@"))
return null;
let header = new Header();
const parts = t.replace(/^[@ ]+|[@ ]+$/g, "").split(" ").filter((s) => s.length > 0);
if (parts.length > 0)
header = Header.ParseSide(header, parts[0], "-", true);
if (parts.length > 1)
header = Header.ParseSide(header, parts[1], "+", false);
return header;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/anchorer/hunkSlicer.js
var Block = class {
StartIndex;
EndIndex;
DeleteLines;
InsertLines;
constructor(StartIndex, EndIndex, DeleteLines, InsertLines) {
this.StartIndex = StartIndex;
this.EndIndex = EndIndex;
this.DeleteLines = DeleteLines;
this.InsertLines = InsertLines;
}
};
var SlicedHunk = class {
Hunk;
Blocks;
constructor(Hunk2, Blocks) {
this.Hunk = Hunk2;
this.Blocks = Blocks;
}
};
var HunkSlicer = class _HunkSlicer {
static Slice(hunks) {
return Array.from(hunks).map((h) => new SlicedHunk(h, Array.from(_HunkSlicer.SliceHunk(h))));
}
static *SliceHunk(hunk) {
const lines = hunk.Lines;
if (lines == null || lines.length == 0) {
return;
}
let i = 0;
while (i < lines.length) {
if (lines[i].Type == LineType.Context) {
i++;
continue;
}
const start = i;
const del = [];
const ins = [];
while (i < lines.length && lines[i].Type != LineType.Context) {
const l = lines[i++];
if (l.Type == LineType.Delete)
del.push(l.Text);
else if (l.Type == LineType.Insert)
ins.push(l.Text);
}
const end = i - 1;
if (del.length > 0 || ins.length > 0)
yield new Block(start, end, del, ins);
}
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/anchorer/search.js
var Match = class {
LineIndex;
Fuzz;
constructor(LineIndex, Fuzz) {
this.LineIndex = LineIndex;
this.Fuzz = Fuzz;
}
};
var Search = class _Search {
// Whitespace matching modes with their per-line fuzz
static StrictnessPasses = [
{ Transform: (s) => s, Fuzz: 0 },
// exact
{ Transform: (s) => s.trimEnd(), Fuzz: 1 },
// rstrip
{ Transform: (s) => s.trim(), Fuzz: 100 },
// strip
{ Transform: (s) => TextUtils.NormalizeHomoglyphs(s).trim(), Fuzz: 200 },
// homoglyphs + strip
{ Transform: (s) => TextUtils.NormalizeHomoglyphs(TextUtils.StripInvisibles(s)).trim(), Fuzz: 300 },
// invisibles stripped + homoglyphs + strip
{ Transform: (s) => TextUtils.NormalizeHomoglyphs(TextUtils.FoldCanonicalAndFullwidth(TextUtils.StripInvisibles(s))).trim(), Fuzz: 400 }
// + NFC and fullwidth folds
];
///
/// Attempts to locate inside ,
/// applying the strictness passes in order, strictest first.
/// Returns a FindResult with Index == -1 if no match.
/// Passes whose per-line fuzz exceeds are skipped:
/// callers that treat a match as evidence the file already contains specific text
/// (rather than as an anchor to edit) pass 100 to exclude the homoglyph and
/// invisible-stripping passes.
///
static Find(haystack, needle, startIndex = 0, maxPassFuzz = Number.MAX_SAFE_INTEGER) {
if (needle.length == 0)
return new Match(Math.min(Math.max(startIndex, 0), haystack.length), 0);
if (haystack.length < needle.length)
return new Match(-1, 0);
for (const { Transform: transform, Fuzz: fuzz } of _Search.StrictnessPasses.filter((p) => p.Fuzz <= maxPassFuzz)) {
const transformedNeedle = Array.from(needle).map(transform);
for (let i = startIndex; i <= haystack.length - needle.length; i++) {
let match = true;
for (let j = 0; j < needle.length; j++) {
if (transform(haystack[i + j]) != transformedNeedle[j]) {
match = false;
break;
}
}
if (!match)
continue;
let affected = 0;
for (let k = 0; k < needle.length; k++) {
if (transform(haystack[i + k]) != haystack[i + k] || transformedNeedle[k] != needle[k])
affected++;
}
return new Match(i, fuzz * affected);
}
}
return new Match(-1, 0);
}
///
/// True when two lines are equal under any of the strictness passes the anchor
/// search itself uses. Used to revalidate context at a line-number-tiebroken slot:
/// the revalidation must not be stricter than the search that produced the
/// candidates, or slots the search considered equivalent (indent slop, homoglyphs)
/// get rejected loudly instead of applied.
///
static LinesEquivalent(a, b) {
return _Search.StrictnessPasses.some((p) => p.Transform(a) == p.Transform(b));
}
///
/// Attempts to find a unique match and reports ambiguity without requiring a separate call.
/// Success=true when exactly one match; Ambiguous=true when more than one match exists; both false when none.
///
static FindUnique(haystack, needle) {
const first = _Search.Find(haystack, needle);
if (first.LineIndex < 0)
return UniqueMatch.NotFound;
const second = _Search.Find(haystack, needle, first.LineIndex + 1);
if (second.LineIndex >= 0)
return UniqueMatch.Ambiguous;
return new UniqueMatch(MatchState.Success, first.LineIndex, first.Fuzz);
}
///
/// Finds the best insertion anchor for a pure-insert hunk using provided context lines.
/// Attempts, in order: full pattern (pre+post), pre-only (insert after pre), post-only (insert before post).
/// If none uniquely match, returns a non-success result with Ambiguous flag when any side matches somewhere.
///
static FindContextAnchor(haystack, contextBefore, contextAfter) {
const preCount = contextBefore.length;
let sawAmbiguity = false;
const searchPatterns = [
{ pattern: contextBefore.concat(contextAfter), insertIndex: preCount, condition: preCount + contextAfter.length > 0 },
{ pattern: contextBefore, insertIndex: preCount, condition: preCount > 0 },
{ pattern: contextAfter, insertIndex: 0, condition: contextAfter.length > 0 }
];
for (const { pattern, insertIndex, condition } of searchPatterns) {
if (!condition)
continue;
const match = _Search.FindUnique(haystack, pattern);
if (match.IsSuccess)
return new UniqueMatch(MatchState.Success, match.LineIndex + insertIndex, match.Fuzz);
if (match.IsAmbiguous)
sawAmbiguity = true;
}
return new UniqueMatch(sawAmbiguity ? MatchState.Ambiguous : MatchState.NotFound);
}
///
/// Returns every position in where
/// matches exactly (no whitespace fuzz). Used for line-number tiebreaking among
/// ambiguous candidates — we deliberately exclude fuzzy matches so the candidate set
/// reflects only positions the LLM could plausibly have meant.
///
static FindAllExact(haystack, needle) {
const results = [];
if (needle.length == 0)
return results;
for (let i = 0; i + needle.length <= haystack.length; i++)
if (needle.every((v, j) => haystack[i + j] === v))
results.push(i);
return results;
}
static TiebreakByLineNumber(candidates, expectedLine) {
if (candidates.length == 0)
return null;
if (candidates.length == 1)
return candidates[0];
const sorted = ArrayUtils.OrderBy(candidates.map((c) => ({ line: c, dist: Math.abs(c - expectedLine) })), (x) => x.dist);
if (sorted[0].dist == 0)
return sorted[0].line;
const gapToNext = sorted[1].dist - sorted[0].dist;
if (gapToNext > sorted[0].dist * 10)
return sorted[0].line;
return null;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/anchorer/blockAnchorer.js
var BlockAnchorer = class _BlockAnchorer {
static AnchorBlocks(hunk, blocks, lines, options) {
return blocks.map((focus, index) => {
let aboveEdge = focus.StartIndex;
let belowEdge = focus.EndIndex;
let aboveBlockIdx = index - 1;
let belowBlockIdx = index + 1;
let chunk = null;
while (true) {
const aboveBound = aboveBlockIdx >= 0 ? blocks[aboveBlockIdx].EndIndex + 1 : 0;
const belowBound = belowBlockIdx < blocks.length ? blocks[belowBlockIdx].StartIndex - 1 : hunk.Lines.length - 1;
for (let k = 1; k <= options.ContextWindowMax; k++) {
const ctxAbove = _BlockAnchorer.ContextLines(hunk, aboveEdge - 1, aboveBound, k, true);
const ctxBelow = _BlockAnchorer.ContextLines(hunk, belowEdge + 1, belowBound, k, false);
const needle = _BlockAnchorer.BuildNeedle(hunk, aboveEdge, belowEdge);
const match = _BlockAnchorer.Anchor(lines, ctxAbove, needle, ctxBelow);
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));
if (!chunk.Match.IsAmbiguous)
return chunk;
if (ctxAbove.length < k && ctxBelow.length < k)
break;
}
if (aboveBlockIdx < 0 && belowBlockIdx >= blocks.length)
break;
const ctxAboveCount = aboveBlockIdx >= 0 ? aboveEdge - (blocks[aboveBlockIdx].EndIndex + 1) : Number.MAX_SAFE_INTEGER;
const ctxBelowCount = belowBlockIdx < blocks.length ? blocks[belowBlockIdx].StartIndex - (belowEdge + 1) : Number.MAX_SAFE_INTEGER;
if (ctxBelowCount <= ctxAboveCount && belowBlockIdx < blocks.length)
belowEdge = blocks[belowBlockIdx++].EndIndex;
else
aboveEdge = blocks[aboveBlockIdx--].StartIndex;
}
if (chunk.Match.IsAmbiguous && hunk.OldStart >= 0) {
const tiebroken = _BlockAnchorer.TryLineNumberTiebreak(hunk, focus, lines);
if (tiebroken != null)
chunk = chunk.with({ Match: tiebroken });
}
return chunk;
});
}
static TryLineNumberTiebreak(hunk, focus, lines) {
let expectedLine = hunk.OldStart - 1;
for (let i = 0; i < focus.StartIndex; i++)
if (hunk.Lines[i].Type != LineType.Insert)
expectedLine++;
if (focus.DeleteLines.length == 0) {
if (expectedLine < 0 || expectedLine > lines.length)
return null;
if (!_BlockAnchorer.CtxMatchesAtSlot(hunk, focus, lines, expectedLine))
return null;
return new UniqueMatch(MatchState.Success, expectedLine);
}
const candidates = Search.FindAllExact(lines, focus.DeleteLines);
const resolved = Search.TiebreakByLineNumber(candidates, expectedLine);
return resolved != null && _BlockAnchorer.CtxMatchesAtSlot(hunk, focus, lines, resolved) ? new UniqueMatch(MatchState.Success, resolved) : null;
}
// Validates the hunk's immediate context lines against a resolved slot's
// neighbours so a stale or shifted header errs loudly instead of silently
// mis-placing the edit. Content-bearing context compares under the search's own
// strictness ladder — the revalidation must not be stricter than the search that
// produced the candidates, or indent-slopped slots the search matched get
// rejected loudly. But a line that trims to near-nothing (brace/paren-only)
// carries its signal IN the indentation, which every ladder pass beyond rstrip
// erases — such context stays whitespace-right-trimmed strict, or a shifted
// header walks the edit to any same-shaped brace. Both tolerances corpus-measured
// 2026-07-06 (ladder-everywhere: 3 silent mis-applies; this gate: 0, +14 recall).
// Serves both callers: a pure insert (DeleteLines empty, after-context sits at
// `slot`) and a delete-block tiebreak (deletes occupy [slot, slot + DeleteLines.length)).
static CtxMatchesAtSlot(hunk, focus, lines, slot) {
const MinLadderContent = 3;
const matches = (hunkIdx, lineIdx) => {
if (hunkIdx < 0 || hunkIdx >= hunk.Lines.length || hunk.Lines[hunkIdx].Type != LineType.Context)
return true;
if (lineIdx < 0 || lineIdx >= lines.length)
return false;
const text = hunk.Lines[hunkIdx].Text;
return text.trim().length >= MinLadderContent ? Search.LinesEquivalent(lines[lineIdx], text) : lines[lineIdx].trimEnd() == text.trimEnd();
};
return matches(focus.StartIndex - 1, slot - 1) && matches(focus.EndIndex + 1, slot + focus.DeleteLines.length);
}
static AlignMatchToFocus(match, hunk, needleStartIndex, focusStartIndex) {
if (!match.IsSuccess)
return match;
return new UniqueMatch(match.State, match.LineIndex + hunk.Lines.slice(needleStartIndex, focusStartIndex).filter((l) => l.Type != LineType.Insert).length, match.Fuzz);
}
static ContextLines(hunk, startLine, bound, maxTake, isAbove) {
if (maxTake <= 0)
return [];
const lines = [];
for (let i = 0; i < maxTake; i++) {
const index = startLine + (isAbove ? -i : i);
if (!(index >= 0 && index < hunk.Lines.length))
break;
if (!(isAbove ? index >= bound : index <= bound))
break;
const line = hunk.Lines[index];
if (line.Type != LineType.Context)
break;
lines.push(line.Text);
}
return isAbove ? [...lines].reverse() : lines;
}
static BuildNeedle(h, startIndex, endIndex) {
return h.Lines.slice(startIndex, endIndex + 1).filter((line) => line.Type != LineType.Insert).map((line) => line.Text);
}
static Anchor(lines, contextAbove, deleteLines, contextBelow) {
if (deleteLines.length == 0)
return Search.FindContextAnchor(lines, contextAbove, contextBelow);
const pattern = contextAbove.concat(deleteLines).concat(contextBelow);
const match = Search.FindUnique(lines, pattern);
if (match.IsAmbiguous)
return UniqueMatch.Ambiguous;
if (match.IsSuccess)
return new UniqueMatch(match.State, match.LineIndex + contextAbove.length, match.Fuzz);
return Search.FindUnique(lines, deleteLines);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/anchorer/hunkAnchorer.js
var AnchorOutcome = class {
Chunks;
AlreadyAppliedCount;
constructor(Chunks, AlreadyAppliedCount) {
this.Chunks = Chunks;
this.AlreadyAppliedCount = AlreadyAppliedCount;
}
};
var HunkAnchorer = class _HunkAnchorer {
static Anchor(originalText, hunks, options) {
const lines = TextUtils.ToLines(originalText);
const slicedHunks = HunkSlicer.Slice(hunks);
const chunks = [];
for (const sliced of slicedHunks)
for (const chunk of BlockAnchorer.AnchorBlocks(sliced.Hunk, sliced.Blocks, lines, options))
chunks.push(_HunkAnchorer.UseFallbackAnchorIfNecessary(sliced.Hunk, chunk, lines, slicedHunks.length == 1));
const deleteCounts = /* @__PURE__ */ new Map();
for (const l of chunks.flatMap((c) => c.DeleteLines))
if (l.trim().length > 0)
deleteCounts.set(l, (deleteCounts.get(l) ?? 0) + 1);
const movedFromElsewhere = (c, line) => (deleteCounts.get(line) ?? 0) > c.DeleteLines.filter((d) => d === line).length;
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));
return new AnchorOutcome(kept, chunks.length - kept.length);
}
static UseFallbackAnchorIfNecessary(hunk, chunk, lines, soleHunk) {
const lineCount = lines.length;
if (chunk.Match.IsNotFound && hunk.Lines.length == chunk.InsertLines.length) {
const targetIsEmpty = lineCount == 0 || lineCount == 1 && lines[0].length == 0;
if (hunk.OldStart < 0 && !soleHunk && !targetIsEmpty)
return chunk;
const anchorLine = hunk.OldStart <= 1 ? 0 : hunk.OldStart;
if (anchorLine > lineCount)
return chunk;
return new Chunk(chunk.ContextBefore, chunk.DeleteLines, chunk.InsertLines, chunk.ContextAfter, new UniqueMatch(MatchState.Success, anchorLine), chunk.DiffLocation);
}
return chunk;
}
// Sub-line splice fallback: a delete line that matches no file line but occurs
// as a substring of exactly one line is an author-quoted fragment — an author
// cannot intend to delete text they never mentioned, so the only coherent
// reading replaces the fragment within the line and preserves the rest. Every
// guard failure means fragment-vs-mangled-whole-line is undecidable: the chunk
// stays NotFound and errors loudly. The insert-side guards catch damaged
// whole-line edits: an insert that echoes text from outside the fragment, or
// dwarfs it, was authored by someone who saw the whole line.
// MUST run only on chunks that survived the already-applied filter: an applied
// edit that wrapped the old line (comment-out, prefix insertion) leaves the
// delete line as a substring of the new line, and splicing it again would
// stack the wrapper (corpus wave-1 case 177: "#x" became "##x").
static TrySubLineSplice(chunk, lines) {
if (!chunk.Match.IsNotFound || chunk.DeleteLines.length != 1 || chunk.InsertLines.length > 1)
return chunk;
const frag = chunk.DeleteLines[0];
if (frag.trim().length < _HunkAnchorer.MinSpliceFragmentContent)
return chunk;
let lineIndex = -1;
for (let i = 0; i < lines.length; i++) {
const first = lines[i].indexOf(frag);
if (first < 0)
continue;
const again = lines[i].indexOf(frag, first + 1) >= 0;
if (lineIndex >= 0 || again)
return chunk;
lineIndex = i;
}
if (lineIndex < 0)
return chunk;
const target = lines[lineIndex];
const at = target.indexOf(frag);
const prefix = target.slice(0, at), suffix = target.slice(at + frag.length);
const insert = chunk.InsertLines.length == 1 ? chunk.InsertLines[0] : "";
if (prefix.length == 0 && suffix.length > 0)
return chunk;
if (insert.length == 0 && prefix.length > 0 && suffix.length > 0 && /\s/.test(prefix[prefix.length - 1]) && /\s/.test(suffix[0]))
return chunk;
const remnantOk = (r) => r.length == 0 || r.trim().length >= _HunkAnchorer.MinSpliceRemnantContent;
const echoes = (r) => r.length > 0 && insert.includes(r.trim());
if (!remnantOk(prefix) || !remnantOk(suffix) || echoes(prefix) || echoes(suffix) || insert.length > frag.length * 2 + 8 && _HunkAnchorer.SharedStem(frag, insert) < _HunkAnchorer.MinSpliceStemContent)
return chunk;
return new Chunk([], [target], [prefix + insert + suffix], [], new UniqueMatch(MatchState.Success, lineIndex, _HunkAnchorer.SubLineSpliceFuzz), chunk.DiffLocation);
}
// Trimmed length a fragment must reach before substring evidence counts.
static MinSpliceFragmentContent = 8;
// Trimmed length a non-empty out-of-fragment remnant must carry.
static MinSpliceRemnantContent = 4;
// Trimmed length the fragment/insert shared stem must reach to waive the dwarf cap.
static MinSpliceStemContent = 8;
// The longest run the insert shares with the fragment's start or end, in trimmed
// chars — the fragment-edit evidence that waives the dwarf cap.
static SharedStem(frag, insert) {
let p = 0;
while (p < frag.length && p < insert.length && frag[p] == insert[p])
p++;
let s = 0;
while (s < frag.length && s < insert.length && frag[frag.length - 1 - s] == insert[insert.length - 1 - s])
s++;
return Math.max(frag.slice(0, p).trim().length, s == 0 ? 0 : frag.slice(-s).trim().length);
}
// Reported fuzz for a spliced edit: looser than every line-match pass.
static SubLineSpliceFuzz = 500;
// A sanity check to avoid errors for a chunk that is already applied.
// Asymmetric strictness: insert-side evidence is capped at whitespace-level
// passes (a homoglyph variant of the post-image is not proof the edit landed,
// and would turn a loud MatchNotFound into a silent no-op), while the delete
// side stays uncapped (finding the pre-image even loosely proves the edit is
// still pending, blocking a false already-applied verdict).
static AlreadyApplied(lines, c) {
if (!c.HasContextLines() && (c.IsPureDelete || c.IsPureInsert))
return false;
const insertImage = c.HasContextLines() ? c.InsertLinesWithContext() : c.InsertLines;
const deleteImage = c.HasContextLines() ? c.DeleteLinesWithContext() : c.DeleteLines;
const insertAt = Search.Find(lines, insertImage, 0, _HunkAnchorer.MaxAppliedEvidenceFuzz);
return insertAt.LineIndex != -1 && Search.Find(lines, deleteImage).LineIndex == -1 && !_HunkAnchorer.OldImagePresentElsewhere(lines, c.DeleteLines, c.IsPureDelete ? null : insertImage, insertAt.LineIndex);
}
// Veto on the already-applied verdict: "old image absent" cannot
// distinguish an applied edit from a delete block damaged in transit — one bad
// char defeats every search pass — so a window nearly matching the delete block
// proves the old image is still in the file and the edit pending: keep the chunk
// and let it error loudly. Refinements, each forced by a corpus counter-example:
// - Windows overlapping an insert-image occurrence never veto: after a genuine
// apply the region shares most lines with the old image (rewrites re-state
// unchanged lines as -/+), and vetoing there would break idempotent re-apply.
// - Pure deletes skip that exclusion (exclusionImage = null): their post-image
// is thin context that occurs everywhere — even inside the pending block
// itself — so its occurrences prove nothing.
// - The window is the delete block WITHOUT context: context sits in both images
// and would glue the pending old image to a context match, masking it.
// - Only content-bearing lines vote, with an absolute floor: blank/brace lines
// near-match everywhere and tiny blocks carry too little signal either way.
static OldImagePresentElsewhere(lines, dels, exclusionImage, insertIndex) {
const contentIdx = [...dels.keys()].filter((i) => dels[i].trim().length > 0);
if (contentIdx.length < _HunkAnchorer.MinNearImageMatches)
return false;
const insertStarts = exclusionImage == null ? [] : [...Search.FindAllExact(lines, exclusionImage), insertIndex];
const overlapsInsert = (start) => insertStarts.some((s) => start < s + exclusionImage.length && s < start + dels.length);
for (let start = 0; start + dels.length <= lines.length; start++) {
if (overlapsInsert(start))
continue;
const matched = contentIdx.filter((j) => lines[start + j] === dels[j]).length;
if (matched >= _HunkAnchorer.MinNearImageMatches && matched * 2 > contentIdx.length)
return true;
}
return false;
}
// Exact-matching content lines required before a near-old-image window counts.
static MinNearImageMatches = 3;
// Highest per-line fuzz (strip pass) that still counts as already-applied evidence.
static MaxAppliedEvidenceFuzz = 100;
// A successfully anchored pure-insert chunk whose full post-image (context +
// inserted lines + context) already sits at the resolved slot — or immediately
// before it — would duplicate itself if applied: the file already reflects this
// chunk. Including the context lines in the comparison distinguishes "already
// applied" from a diff that intentionally duplicates adjacent lines (there the
// old-side context sits where the copy would be, so the post-image cannot match).
static AppliedAtAnchor(lines, c) {
if (!c.IsPureInsert || !c.Match.IsSuccess)
return false;
const post = c.InsertLinesWithContext();
const slot = c.Match.LineIndex;
return _HunkAnchorer.RegionEquals(lines, slot - c.ContextBefore.length, post) || _HunkAnchorer.RegionEquals(lines, slot - c.ContextBefore.length - c.InsertLines.length, post);
}
static RegionEquals(lines, start, region) {
if (start < 0 || start + region.length > lines.length)
return false;
for (let i = 0; i < region.length; i++)
if (lines[start + i] !== region[i])
return false;
return true;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/selectionTarget.js
var SelectionTarget = class {
FullText;
SelectedText;
UseSelection = false;
LineOffset = 0;
get TargetText() {
return this.UseSelection ? this.SelectedText : this.FullText;
}
constructor(fullText, selection = "") {
this.FullText = fullText;
this.SelectedText = selection;
if (!(this.SelectedText == null || this.SelectedText.length == 0)) {
const hay = TextUtils.ToLines(this.FullText);
const needle = TextUtils.ToLines(this.SelectedText);
const result = Search.Find(hay, needle);
this.UseSelection = result.LineIndex >= 0;
this.LineOffset = this.UseSelection ? Math.max(0, result.LineIndex) : 0;
}
}
Replace(replace) {
if (!this.UseSelection)
return replace;
const fullLines = TextUtils.ToLines(this.FullText);
const selectionLines = TextUtils.ToLines(this.SelectedText);
const replaceLines = TextUtils.ToLines(replace);
const edit = new Edit(this.LineOffset, selectionLines, replaceLines);
edit.ApplyTo(fullLines);
return TextUtils.RoundTripWhitespace(this.FullText, fullLines);
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/shared.js
function isDiffMarker(line) {
return line.startsWith("--- ") || line.startsWith("+++ ") || line.startsWith("@@") || line.startsWith("diff --git");
}
function isHunkBoundary(line, trimmed) {
return isDiffMarker(line) || trimmed.startsWith("```");
}
function isValidPrefix(char) {
return char === "+" || char === "-" || char === " ";
}
function transformOutsideHunkBodies(text, transform) {
let remainingOld = 0, remainingNew = 0;
let inBareBody = false;
const kept = [];
for (const line of text.split("\n")) {
const trimmed = line.trim();
const m = /^@@ -\d+(?:,(\d+))? \+\d+(?:,(\d+))? @@/.exec(trimmed);
if (m) {
remainingOld = m[1] !== void 0 ? parseInt(m[1]) : 1;
remainingNew = m[2] !== void 0 ? parseInt(m[2]) : 1;
inBareBody = false;
kept.push(line);
continue;
}
if (remainingOld > 0 || remainingNew > 0) {
const c = line.length > 0 ? line[0] : " ";
if (c === "-")
remainingOld--;
else if (c === "+")
remainingNew--;
else {
remainingOld--;
remainingNew--;
}
kept.push(line);
continue;
}
if (trimmed.startsWith("@@")) {
inBareBody = true;
kept.push(line);
continue;
}
if (inBareBody && line.length > 0 && isValidPrefix(line[0]) && !isLikelyFileHeader(trimmed)) {
kept.push(line);
continue;
}
inBareBody = false;
const t = transform(line);
if (t !== null)
kept.push(t);
}
return kept.join("\n");
}
function isLikelyFileHeader(trimmed) {
return trimmed.startsWith("diff --git") || (trimmed.startsWith("--- ") || trimmed.startsWith("+++ ")) && (trimmed.includes(".") || trimmed.includes("/") || trimmed.includes("\\"));
}
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/customFormatSanitizer.js
var CustomFormatSanitizer = class _CustomFormatSanitizer {
static OPERATIONS = [
{ header: /^\s*\*\*\* Update File: (.+)$/i, fromPath: (p) => `a/${p}`, toPath: (p) => `b/${p}` },
{ header: /^\s*\*\*\* Add File: (.+)$/i, fromPath: (_p) => "/dev/null", toPath: (p) => `b/${p}` },
{ header: /^\s*\*\*\* Delete File: (.+)$/i, fromPath: (p) => `a/${p}`, toPath: (_p) => "/dev/null" }
];
static process(text) {
return transformOutsideHunkBodies(text, (line) => {
const hadCr = line.endsWith("\r");
const core = hadCr ? line.slice(0, -1) : line;
for (const op of _CustomFormatSanitizer.OPERATIONS) {
const m = core.match(op.header);
if (!m)
continue;
const path = m[1].trim();
const tail = hadCr ? "\r" : "";
return `--- ${op.fromPath(path)}${tail}
+++ ${op.toPath(path)}${tail}`;
}
return line;
});
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/decoratedHeaderSanitizer.js
var DecoratedHeaderSanitizer = class _DecoratedHeaderSanitizer {
static DECORATED_HEADER_REGEX = /^(---|\+\+\+)\s+(.+?)\s+(---|\+\+\+)+\s*$/;
static process(text) {
return transformOutsideHunkBodies(text, (line) => {
const hadCr = line.endsWith("\r");
const core = hadCr ? line.slice(0, -1) : line;
const replaced = core.replace(_DecoratedHeaderSanitizer.DECORATED_HEADER_REGEX, "$1 $2");
return replaced === core ? line : hadCr ? replaced + "\r" : replaced;
});
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/decorativeMarkerSanitizer.js
var DecorativeMarkerSanitizer = class _DecorativeMarkerSanitizer {
static process(text) {
return transformOutsideHunkBodies(text, (line) => _DecorativeMarkerSanitizer.isDecorativeMarker(line.trim()) ? null : line);
}
static isDecorativeMarker(line) {
if (!line)
return false;
if (/^diff --git\s+/.test(line))
return false;
if (/^[*#_]{3,}$/.test(line))
return true;
if (/^[=\-]{5,}$/.test(line))
return true;
const wholeLineKeyword = /^[*=\-#_\s]*(BEGIN|END|START|STOP)\s+(PATCH|DIFF|HUNK|SECTION|BLOCK)[*=\-#_\s]*$/i.test(line);
if (wholeLineKeyword && /[*=\-#_]{2,}/.test(line))
return true;
return false;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/backtickSanitizer.js
var BacktickSanitizer = class {
static process(text) {
return text.replace(/^```+\s*diff\b/gmi, "```diff").replace(/^```+$/gm, "```");
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/diffBlockReunifier.js
var DiffBlockReunifier = class {
static process(text) {
const lines = text.split("\n");
const result = [];
let inDiff = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (trimmed.startsWith("```diff")) {
inDiff = true;
result.push(line);
} else if (trimmed === "```" && line.startsWith("```")) {
if (inDiff && this.hasDiffContentBeforeNextFence(lines, i)) {
continue;
}
inDiff = false;
result.push(line);
} else {
result.push(line);
}
}
return result.join("\n");
}
static hasDiffContentBeforeNextFence(lines, currentIndex) {
for (let j = currentIndex + 1; j < lines.length; j++) {
const ahead = lines[j].trim();
if (lines[j].startsWith("```"))
return false;
if (isDiffMarker(ahead))
return true;
}
return false;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/devNullSanitizer.js
var DevNullSanitizer = class {
static process(text, fileKeys) {
if (!fileKeys?.length)
return text;
const existingFiles = new Set(fileKeys);
const lines = text.split("\n");
const result = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (this.isNewFileModeForExistingFile(trimmed, lines, i, existingFiles)) {
continue;
}
if (trimmed.startsWith("--- /dev/null")) {
const targetFile = this.getTargetFileFromNextLine(lines[i + 1]);
if (targetFile && existingFiles.has(targetFile)) {
result.push(line.replace("/dev/null", `a/${targetFile}`));
continue;
}
}
result.push(line);
}
return result.join("\n");
}
static isNewFileModeForExistingFile(line, allLines, currentIndex, existingFiles) {
if (!line.startsWith("new file mode"))
return false;
const prevLine = allLines[currentIndex - 1]?.trim() || "";
if (!prevLine.startsWith("diff --git"))
return false;
const match = prevLine.match(/diff --git\s+[ab]\/(\S+)\s+[ab]\/\S+/);
const filePath = match?.[1];
return filePath ? existingFiles.has(filePath) : false;
}
static getTargetFileFromNextLine(nextLine) {
if (!nextLine?.trim().startsWith("+++ "))
return null;
const match = nextLine.match(/^\+\+\+\s+(?:[ab]\/)?(\S+)/);
return match?.[1] ?? null;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/sanitizers/prefixInjector.js
var PrefixInjector = class {
static process(text) {
const lines = text.split("\n");
let inHunk = false;
let deletes = { remaining: 0, consumed: 0 };
let adds = { remaining: 0, consumed: 0 };
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.length === 0 && i === lines.length - 1)
continue;
const trimmed = line.trim();
if (trimmed.startsWith("@@")) {
const { deleteCount, addCount } = this.parseHunkHeader(trimmed);
inHunk = true;
deletes = { remaining: deleteCount, consumed: 0 };
adds = { remaining: addCount, consumed: 0 };
continue;
}
if (isHunkBoundary(line, trimmed)) {
inHunk = false;
continue;
}
if (inHunk) {
const first = line[0] ?? "";
const hasValidPrefix = isValidPrefix(first);
const prefix = hasValidPrefix ? first : this.inferPrefix(deletes.remaining - deletes.consumed, adds.remaining - adds.consumed);
if (!hasValidPrefix && prefix !== null) {
lines[i] = prefix + line;
}
if (prefix === "+")
adds.consumed++;
else if (prefix === "-")
deletes.consumed++;
else {
adds.consumed++;
deletes.consumed++;
}
}
}
return lines.join("\n");
}
static parseHunkHeader(header) {
const match = header.match(/@@\s*-(\d+)(?:,(\d+))?\s*\+(\d+)(?:,(\d+))?\s*@@/);
return match ? { deleteCount: parseInt(match[2] ?? "1"), addCount: parseInt(match[4] ?? "1") } : { deleteCount: 0, addCount: 0 };
}
// Only inject when the header arithmetic is unambiguous. When both deletes and
// adds remain (or the header had no usable counts), leave the line untouched —
// the parser's raw-line heuristics use content evidence and insertion-lookahead
// to classify it better than count-based guessing can.
static inferPrefix(remainingDeletes, remainingAdds) {
if (remainingDeletes > 0 && remainingAdds <= 0)
return "-";
if (remainingAdds > 0 && remainingDeletes <= 0)
return "+";
return null;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/parser/diffSanitizer.js
var DiffSanitizer = class {
static Process(diff, fileKeys) {
if (!diff?.trim())
return diff;
let result = CustomFormatSanitizer.process(diff);
result = DecoratedHeaderSanitizer.process(result);
result = DecorativeMarkerSanitizer.process(result);
result = BacktickSanitizer.process(result);
result = DiffBlockReunifier.process(result);
result = DevNullSanitizer.process(result, fileKeys);
result = PrefixInjector.process(result);
return result;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/headerlessRouter.js
var HeaderlessRouter = class {
static Route(fileHunks, files, options) {
const group = fileHunks.find((g) => g.Key == "");
if (group == null || files.some((f) => f.Key == ""))
return [];
const probeOptions = Object.assign(new PatchOptions(), options, { ContinueOnError: true });
const candidates = files.filter((f) => {
const target = new SelectionTarget(f.InputFullText, f.InputSelectedText).TargetText;
const anchored = HunkAnchorer.Anchor(target, group.Hunks, probeOptions);
return ChunkApplier.Apply(target, anchored.Chunks, probeOptions).Errors.length == 0;
});
if (candidates.length == 1) {
fileHunks.splice(fileHunks.indexOf(group), 1);
const existing = fileHunks.find((g) => g.Key == candidates[0].Key);
if (existing)
existing.Hunks.push(...group.Hunks);
else
fileHunks.push(new FileHunkGroup(candidates[0].Key, group.Hunks));
return [];
}
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 '--- ' / '+++ ' 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 '--- ' / '+++ ' headers naming the intended file.")];
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/patchNotices.js
var PatchNotices = class {
static For(hunks, file, contextOnlyHunkLines) {
const notices = [];
if (file.Edits.length === 0)
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.");
if (file.Fuzz > 0)
notices.push(`Note: applied with fuzz factor ${file.Fuzz} \u2014 the diff did not match exactly, so confirm the edits landed where you intended.`);
const suspects = TextUtils.DescribeSuspectCodePoints(file.Edits.flatMap((e) => e.InsertLines));
if (suspects)
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.`);
const collapsed = hunks.flatMap((h) => h.Lines).filter((l) => l.CollapsedFrom != null).map((l) => l.CollapsedFrom);
if (collapsed.length > 0)
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.`);
if (hunks.some((h) => h.TruncationSuspected))
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.");
if (hunks.some((h) => h.ControlCharsSuspected))
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.");
if (contextOnlyHunkLines.length > 0)
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.`);
return notices;
}
};
// ../node_modules/.pnpm/matchu-patchu@0.3.3/node_modules/matchu-patchu/dist/patcher.js
var Patcher = class {
static Apply(diff, files, options) {
options = options ?? new PatchOptions();
const fileKeys = files.map((f) => f.Key);
const fileContents = new Map(files.map((f) => [f.Key, f.InputFullText]));
diff = options.SanitizeDiff ? DiffSanitizer.Process(diff, fileKeys) : diff;
const fileHunks = UnifiedDiffParser.Parse(diff, fileContents, options.Truncation, options.ControlChars);
const routingErrors = HeaderlessRouter.Route(fileHunks, files, options);
const namedKeys = fileKeys.filter((k) => k != "");
const rosterHint = namedKeys.length > 0 ? `Files being patched: ${namedKeys.join(", ")}.` : null;
const reportEntry = (key, errors) => new PatchOutputFile(key, 0, [], "", "", "", errors);
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)]));
if (routingErrors.length > 0)
reportFiles.unshift(reportEntry("", routingErrors));
const reportErrors = reportFiles.flatMap((f) => f.Errors);
if (reportErrors.length > 0 && !options.ContinueOnError)
throw new PatchException(reportErrors[0]);
const patchOutputFiles = files.map((f) => {
const targetSelection = new SelectionTarget(f.InputFullText, f.InputSelectedText);
const targetText = targetSelection.TargetText;
const group = fileHunks.find((h) => h.Key == f.Key);
const hunks = group?.Hunks ?? [];
const anchored = HunkAnchorer.Anchor(targetText, hunks, options);
const outputEdits = ChunkApplier.Apply(targetText, anchored.Chunks, options);
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);
file.Notices = PatchNotices.For(hunks, file, group?.ContextOnlyHunkLines ?? []);
return file;
});
return new PatchOutput([...patchOutputFiles, ...reportFiles]);
}
};
// cli/agents/pi/server/piPatcherExtension.ts
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.";
var BUILD_TAG = true ? "matchu-patchu-pi 0.3.3, built 2026-07-27 14:58:21" : "matchu-patchu-pi dev";
var errorBlocks = (errors) => {
const parts = [`Patch failed with ${errors.length} error(s):`];
for (const e of errors) parts.push("", e.toString());
return parts.join("\n");
};
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("");
function discoverDiffKeys(diff) {
const keys = [];
for (const f of Patcher.Apply(diff, []).Files) {
if (f.Key && !keys.includes(f.Key)) keys.push(f.Key);
}
return keys;
}
function applySingle(cwd, filePath, diff, dryRun) {
const abs = resolve(cwd, filePath);
if (!existsSync(abs)) return { ok: false, message: `Error: file not found: ${filePath}` };
const out = Patcher.Apply(diff, [new PatchInputFile("", readFileSync(abs, "utf-8"))]).Files[0];
if (out.Errors.length > 0) return { ok: false, message: errorBlocks(out.Errors) };
if (out.Edits.length > 0 && !dryRun) writeFileSync(abs, out.OutputFullText);
return {
ok: true,
message: outcomeMessage(filePath, out) + (dryRun ? `
--- patched output ---
${out.OutputFullText}` : "")
};
}
function applyMulti(cwd, diff, dryRun) {
const keys = discoverDiffKeys(diff);
if (keys.length === 0)
return { ok: false, message: "Error: no file headers (---/+++) found in the diff \u2014 pass filePath to patch a headerless diff into one file" };
const missing = keys.filter((k) => !existsSync(resolve(cwd, k)));
if (missing.length > 0)
return { ok: false, message: missing.map((k) => `Error: file not found: ${k}`).join("\n") };
const inputs = keys.map((k) => new PatchInputFile(k, readFileSync(resolve(cwd, k), "utf-8")));
const result = Patcher.Apply(diff, inputs);
const errors = result.Files.flatMap((f) => f.Errors);
if (errors.length > 0) return { ok: false, message: errorBlocks(errors) };
const lines = [];
const dryOutputs = [];
for (const out of result.Files) {
if (out.Edits.length > 0) {
if (!dryRun) writeFileSync(resolve(cwd, out.Key), out.OutputFullText);
else dryOutputs.push(`--- patched output: ${out.Key} ---
${out.OutputFullText}`);
}
lines.push(outcomeMessage(out.Key, out));
}
return { ok: true, message: [lines.join("\n"), ...dryOutputs].join("\n\n") };
}
function applyPatch(cwd, args) {
if (!args.diff || !args.diff.trim()) return { ok: false, message: "Error: patch is empty" };
try {
const out = args.filePath ? applySingle(cwd, args.filePath, args.diff, args.dryRun === true) : applyMulti(cwd, args.diff, args.dryRun === true);
return out.ok ? { ok: true, message: `${out.message} [${BUILD_TAG}]` } : out;
} catch (ex) {
if (ex instanceof PatchParserException)
return { ok: false, message: `Error: failed to parse patch \u2014 ${ex.message}` };
return { ok: false, message: `Error: ${ex instanceof Error ? ex.message : String(ex)}` };
}
}
function logErr(...a) {
try {
console.error("[matchu-patchu]", ...a);
} catch {
}
}
function patchOwnership(tools, registeredByUs) {
const patch = tools.find((t) => t.name === "patch");
if (!patch) return "absent";
return registeredByUs || /matchu-patchu/i.test(patch.sourceInfo?.path ?? "") ? "ours" : "foreign";
}
var PATCH_TOOL = {
name: "patch",
label: "Patch",
description: DESCRIPTION,
promptSnippet: "Modify one or more existing files by applying a unified diff",
promptGuidelines: [
"Use patch for every edit to an existing file; write is only for creating new files or full rewrites.",
"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."
],
parameters: Type.Object({
diff: Type.String({ description: "Unified diff content to apply" }),
filePath: Type.Optional(Type.String({
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."
})),
dryRun: Type.Optional(Type.Boolean({ description: "If true, return the patched content without writing to disk" }))
}),
async execute(_id, params, _signal, _onUpdate, ctx) {
const { ok, message } = applyPatch(ctx.cwd, params);
if (!ok) throw new Error(message);
return { content: [{ type: "text", text: message }], details: {} };
}
};
function piPatcherExtension_default(pi) {
let registeredPatch = false;
try {
pi.on("session_start", () => {
try {
const owner = patchOwnership(pi.getAllTools(), registeredPatch);
if (owner === "foreign") {
logErr('another extension already provides "patch" \u2014 typebulb patcher standing down (built-in edit kept)');
return;
}
if (owner === "absent") {
pi.registerTool(PATCH_TOOL);
registeredPatch = true;
}
const active = pi.getActiveTools();
if (active.includes("edit")) pi.setActiveTools(active.filter((n) => n !== "edit"));
} catch (e) {
logErr("session_start", e);
}
});
} catch (e) {
logErr("load", e);
}
}
export {
applyPatch,
piPatcherExtension_default as default,
discoverDiffKeys,
patchOwnership
};