fix: improve apply-patch add-file guidance
This commit is contained in:
@@ -115,6 +115,37 @@ export function applyPatchV2HelpPayload() {
|
||||
firstLine: beginMarker,
|
||||
lastLine: endMarker
|
||||
},
|
||||
rules: [
|
||||
"Add File has no @@ hunk marker: put content immediately after `*** Add File: <path>` and prefix every content line with +.",
|
||||
"A blank line in Add File is a line containing only +.",
|
||||
"Update File uses @@ or @@ context markers, followed by context lines starting with space and changed lines starting with - or +.",
|
||||
"Prefer `trans <route> apply-patch < /tmp/patch.diff` for long patches, Windows paths, or quoting-sensitive content."
|
||||
],
|
||||
examples: {
|
||||
addFile: [
|
||||
beginMarker,
|
||||
`${addFileMarker}path/to/new.txt`,
|
||||
"+first line",
|
||||
"+",
|
||||
"+third line after a blank line",
|
||||
endMarker
|
||||
].join("\n"),
|
||||
updateFile: [
|
||||
beginMarker,
|
||||
`${updateFileMarker}path/to/existing.txt`,
|
||||
emptyChangeContextMarker,
|
||||
" context line",
|
||||
"-old line",
|
||||
"+new line",
|
||||
" more context",
|
||||
endMarker
|
||||
].join("\n")
|
||||
},
|
||||
commonPitfalls: [
|
||||
"Do not put @@ after `*** Add File:`; @@ is only for Update File.",
|
||||
"Do not paste unified diff headers such as `@@ -1,3 +1,4 @@`.",
|
||||
"Do not use remote Python/Perl/sed heredocs for text patches when `trans <route> apply-patch` is available."
|
||||
],
|
||||
note: "apply-patch reads patch text from stdin and uses the v2 engine by default. Use `apply-patch-v1` only for the legacy helper."
|
||||
};
|
||||
}
|
||||
@@ -148,7 +179,7 @@ export function parseApplyPatchV2(patchText: string): PatchParseResult {
|
||||
while (index < lines.length - 1 && !isFileHeader(lines[index] ?? "")) {
|
||||
const addLine = lines[index] ?? "";
|
||||
if (!addLine.startsWith("+")) {
|
||||
throw new ApplyPatchV2Error("invalid add file line; every added line must start with +", { line: index + 1, path: filePath });
|
||||
throw invalidAddFileLineError(addLine, index + 1, filePath);
|
||||
}
|
||||
added.push(addLine.slice(1));
|
||||
index += 1;
|
||||
@@ -189,6 +220,26 @@ export function parseApplyPatchV2(patchText: string): PatchParseResult {
|
||||
return { patch, hunks };
|
||||
}
|
||||
|
||||
function invalidAddFileLineError(lineText: string, line: number, filePath: string): ApplyPatchV2Error {
|
||||
const details = { line, path: filePath, text: lineText };
|
||||
if (lineText.trimStart().startsWith("@@")) {
|
||||
return new ApplyPatchV2Error(
|
||||
"invalid add file line: remove @@ for Add File. Add File sections do not use hunk markers; put content directly after the header and prefix every content line with +.",
|
||||
details,
|
||||
);
|
||||
}
|
||||
if (lineText.trim().length === 0) {
|
||||
return new ApplyPatchV2Error(
|
||||
"invalid add file line: blank lines in Add File must be written as a line containing only +.",
|
||||
details,
|
||||
);
|
||||
}
|
||||
return new ApplyPatchV2Error(
|
||||
"invalid add file line: every Add File content line must start with +. Add File has no @@ marker; use @@ only under Update File.",
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
export function deriveUpdatedContent(filePath: string, originalContent: string, chunks: UpdateChunk[]): PatchUpdateResult {
|
||||
const originalLines = splitContentLines(originalContent);
|
||||
const replacements = computeReplacements(filePath, originalLines, chunks);
|
||||
|
||||
@@ -905,6 +905,24 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
|
||||
assertCondition(addBeforeFailedUpdateV2.files.hwpod?.includes("device-pod-cli.mjs"), "v2 should preserve an earlier Add File from a large patch when a later hunk misses", addBeforeFailedUpdateV2);
|
||||
assertCondition(addBeforeFailedUpdateV2.files["scripts/artifact-publish.mjs"] === "actual artifact line\n", "v2 must leave the failed later file unchanged", addBeforeFailedUpdateV2);
|
||||
|
||||
const addFileWithHunkMarkerV2 = await applyPatchV2FixtureAttempt([
|
||||
"*** Begin Patch",
|
||||
"*** Add File: bad-add.txt",
|
||||
"@@",
|
||||
"+content",
|
||||
"*** End Patch",
|
||||
"",
|
||||
].join("\n"), {}, { stderrOutput: true });
|
||||
assertCondition(addFileWithHunkMarkerV2.exitCode === 1 && addFileWithHunkMarkerV2.error === null, "v2 CLI path should return a visible parse error for Add File @@ misuse", addFileWithHunkMarkerV2);
|
||||
assertCondition(addFileWithHunkMarkerV2.stdout === "", "v2 Add File parse failures should keep Codex-style empty stdout", addFileWithHunkMarkerV2);
|
||||
assertCondition(
|
||||
addFileWithHunkMarkerV2.stderr.includes("remove @@ for Add File")
|
||||
&& addFileWithHunkMarkerV2.stderr.includes("prefix every content line with +"),
|
||||
"v2 Add File @@ misuse should tell agents exactly how to repair the patch",
|
||||
addFileWithHunkMarkerV2.stderr,
|
||||
);
|
||||
assertCondition(!addFileWithHunkMarkerV2.commands.some((command) => command.startsWith("write-b64") || command.startsWith("delete")), "v2 Add File parse failures must not touch remote files", addFileWithHunkMarkerV2.commands);
|
||||
|
||||
const sequentialCompoundV2 = await applyPatchV2Fixture([
|
||||
"*** Begin Patch",
|
||||
"*** Update File: sequence.txt",
|
||||
|
||||
Reference in New Issue
Block a user