@howaboua/pi-codex-conversion 1.0.15 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/patch/core.ts +2 -1
- package/src/patch/matching.ts +30 -0
- package/src/patch/parser.ts +24 -46
- package/src/tools/apply-patch-tool.ts +21 -9
package/package.json
CHANGED
package/src/patch/core.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { linesMatch } from "./matching.ts";
|
|
3
4
|
import { parsePatchActions, parseUpdateFile } from "./parser.ts";
|
|
4
5
|
import { openFileAtPath, pathExists, removeFileAtPath, resolvePatchPath, writeFileAtPath } from "./paths.ts";
|
|
5
6
|
import { DiffError, ExecutePatchError, type ExecutePatchResult, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
@@ -64,7 +65,7 @@ function getUpdatedFile({ text, action, path }: { text: string; action: PatchAct
|
|
|
64
65
|
destIndex += delta;
|
|
65
66
|
|
|
66
67
|
for (const line of chunk.delLines) {
|
|
67
|
-
if (origLines[origIndex]
|
|
68
|
+
if (!linesMatch(origLines[origIndex] ?? "", line)) {
|
|
68
69
|
throw new DiffError(`_get_updated_file: ${path}: Expected ${line} but got ${origLines[origIndex]} at line ${origIndex + 1}`);
|
|
69
70
|
}
|
|
70
71
|
origIndex += 1;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type LinesMatchQuality = {
|
|
2
|
+
fuzz: number;
|
|
3
|
+
worstLineFuzz: number;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function lineMatchFuzz(left: string, right: string): number | undefined {
|
|
7
|
+
if (left === right) return 0;
|
|
8
|
+
if (left.trimEnd() === right.trimEnd()) return 1;
|
|
9
|
+
if (left.trim() === right.trim()) return 100;
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function linesMatch(left: string, right: string): boolean {
|
|
14
|
+
return left === right || left.trimEnd() === right.trimEnd();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function linesEqualFuzz({ left, right }: { left: string[]; right: string[] }): LinesMatchQuality | undefined {
|
|
18
|
+
if (left.length !== right.length) return undefined;
|
|
19
|
+
|
|
20
|
+
let fuzz = 0;
|
|
21
|
+
let worstLineFuzz = 0;
|
|
22
|
+
for (let index = 0; index < left.length; index++) {
|
|
23
|
+
const lineFuzz = lineMatchFuzz(left[index], right[index]);
|
|
24
|
+
if (lineFuzz === undefined) return undefined;
|
|
25
|
+
fuzz += lineFuzz;
|
|
26
|
+
worstLineFuzz = Math.max(worstLineFuzz, lineFuzz);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { fuzz, worstLineFuzz };
|
|
30
|
+
}
|
package/src/patch/parser.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { lineMatchFuzz, linesEqualFuzz } from "./matching.ts";
|
|
1
2
|
import { normalizePatchPath } from "./paths.ts";
|
|
2
3
|
import { DiffError, type Chunk, type ParseMode, type ParsedPatchAction, type ParserState, type PatchAction } from "./types.ts";
|
|
3
4
|
|
|
@@ -48,14 +49,6 @@ function splitFileLines(text: string): string[] {
|
|
|
48
49
|
return lines;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function linesEqual({ left, right }: { left: string[]; right: string[] }): boolean {
|
|
52
|
-
if (left.length !== right.length) return false;
|
|
53
|
-
for (let index = 0; index < left.length; index++) {
|
|
54
|
-
if (left[index] !== right[index]) return false;
|
|
55
|
-
}
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
52
|
function findContextCore({ lines, context, start }: { lines: string[]; context: string[]; start: number }): {
|
|
60
53
|
newIndex: number;
|
|
61
54
|
fuzz: number;
|
|
@@ -64,25 +57,30 @@ function findContextCore({ lines, context, start }: { lines: string[]; context:
|
|
|
64
57
|
return { newIndex: start, fuzz: 0 };
|
|
65
58
|
}
|
|
66
59
|
|
|
67
|
-
for (
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
for (const tier of [0, 1, 100]) {
|
|
61
|
+
for (let index = start; index <= lines.length - context.length; index++) {
|
|
62
|
+
const quality = linesEqualFuzz({ left: lines.slice(index, index + context.length), right: context });
|
|
63
|
+
if (quality?.worstLineFuzz === tier) {
|
|
64
|
+
return { newIndex: index, fuzz: quality.fuzz };
|
|
65
|
+
}
|
|
70
66
|
}
|
|
71
67
|
}
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
return { newIndex: -1, fuzz: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function findSectionAnchor({ lines, target, start }: { lines: string[]; target: string; start: number }): { newIndex: number; fuzz: number } {
|
|
73
|
+
for (const tier of [0, 1, 100]) {
|
|
74
|
+
const alreadySeen = lines.slice(0, start).some((line) => lineMatchFuzz(line, target) === tier);
|
|
75
|
+
if (alreadySeen) {
|
|
76
|
+
continue;
|
|
78
77
|
}
|
|
79
|
-
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
for (let index = start; index < lines.length; index++) {
|
|
80
|
+
const fuzz = lineMatchFuzz(lines[index], target);
|
|
81
|
+
if (fuzz === tier) {
|
|
82
|
+
return { newIndex: index, fuzz };
|
|
83
|
+
}
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -263,30 +261,10 @@ export function parseUpdateFile({ state, text, path }: { state: ParserState; tex
|
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
if (defStr.trim().length > 0) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (let lineIndex = index; lineIndex < lines.length; lineIndex++) {
|
|
271
|
-
if (lines[lineIndex] === defStr) {
|
|
272
|
-
index = lineIndex + 1;
|
|
273
|
-
found = true;
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (!found) {
|
|
280
|
-
const trimAlreadySeen = lines.slice(0, index).some((line) => line.trim() === defStr.trim());
|
|
281
|
-
if (!trimAlreadySeen) {
|
|
282
|
-
for (let lineIndex = index; lineIndex < lines.length; lineIndex++) {
|
|
283
|
-
if (lines[lineIndex].trim() === defStr.trim()) {
|
|
284
|
-
index = lineIndex + 1;
|
|
285
|
-
state.fuzz += 1;
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
264
|
+
const sectionAnchor = findSectionAnchor({ lines, target: defStr, start: index });
|
|
265
|
+
if (sectionAnchor.newIndex !== -1) {
|
|
266
|
+
index = sectionAnchor.newIndex + 1;
|
|
267
|
+
state.fuzz += sectionAnchor.fuzz;
|
|
290
268
|
}
|
|
291
269
|
}
|
|
292
270
|
|
|
@@ -187,17 +187,29 @@ function uniqueStrings(values: Array<string | undefined>): string[] {
|
|
|
187
187
|
return Array.from(new Set(values.filter((value): value is string => typeof value === "string" && value.length > 0)));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
function
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
function getFailedPaths(error: ExecutePatchError): string[] {
|
|
191
|
+
if (!error.failedAction) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
return uniqueStrings([
|
|
195
|
+
error.failedAction.path,
|
|
196
|
+
error.failedAction.type === "update" ? error.failedAction.movePath : undefined,
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getAppliedPaths(result: ExecutePatchResult, failedFiles: string[]): string[] {
|
|
201
|
+
return result.changedFiles.filter((path) => !failedFiles.includes(path));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildPartialFailureMessage(message: string, failedFiles: string[], appliedFiles: string[]): string {
|
|
193
205
|
const lines = [message];
|
|
194
206
|
if (failedFiles.length > 0) {
|
|
195
|
-
lines.push(`Failed file: ${failedFiles.join(", ")}`);
|
|
207
|
+
lines.push(`Failed file${failedFiles.length === 1 ? "" : "s"}: ${failedFiles.join(", ")}`);
|
|
196
208
|
lines.push(`Recovery: MUST read ${failedFiles.join(", ")} before retrying.`);
|
|
197
209
|
}
|
|
198
210
|
if (appliedFiles.length > 0) {
|
|
199
|
-
lines.push(
|
|
200
|
-
lines.push(
|
|
211
|
+
lines.push("Earlier file actions in this patch were already applied.");
|
|
212
|
+
lines.push("Recovery: MUST NOT reread other files from this patch unless a specific dependency requires it.");
|
|
201
213
|
}
|
|
202
214
|
return lines.join("\n");
|
|
203
215
|
}
|
|
@@ -278,9 +290,9 @@ export function registerApplyPatchTool(pi: ExtensionAPI): void {
|
|
|
278
290
|
: "apply_patch failed";
|
|
279
291
|
const message = failedTarget ? `${prefix} while patching ${failedTarget}: ${error.message}` : `${prefix}: ${error.message}`;
|
|
280
292
|
if (partial) {
|
|
281
|
-
const failedFiles =
|
|
282
|
-
const appliedFiles = error.result
|
|
283
|
-
const recoveryMessage = buildPartialFailureMessage(message,
|
|
293
|
+
const failedFiles = getFailedPaths(error);
|
|
294
|
+
const appliedFiles = getAppliedPaths(error.result, failedFiles);
|
|
295
|
+
const recoveryMessage = buildPartialFailureMessage(message, failedFiles, appliedFiles);
|
|
284
296
|
markApplyPatchPartialFailure(toolCallId, failedTarget);
|
|
285
297
|
return {
|
|
286
298
|
content: [{ type: "text", text: recoveryMessage }],
|