@maxiaochao/pi-toolkit 0.9.4 → 0.9.6
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/packages/codex-edit/CHANGELOG.md +15 -0
- package/packages/codex-edit/README.md +10 -3
- package/packages/codex-edit/TESTING.md +40 -9
- package/packages/codex-edit/benchmark/explicit-edit-100.json +2824 -0
- package/packages/codex-edit/benchmark/export-evidence.mjs +77 -0
- package/packages/codex-edit/benchmark/incident-cases.mjs +37 -0
- package/packages/codex-edit/benchmark/prepare.mjs +181 -0
- package/packages/codex-edit/benchmark/results/explicit-edit-100-sol-low-v3.json +4562 -0
- package/packages/codex-edit/benchmark/run-with-budget.mjs +77 -0
- package/packages/codex-edit/benchmark/schedule.mjs +19 -0
- package/packages/codex-edit/benchmark/select-tasks.mjs +167 -0
- package/packages/codex-edit/codex-edit-explainer.html +4 -4
- package/packages/codex-edit/docs/test-report.html +181 -0
- package/packages/codex-edit/docs/test-report.md +242 -0
- package/packages/codex-edit/docs/test-summary.md +9 -0
- package/packages/codex-edit/extensions/codex-edit.ts +4 -2
- package/packages/codex-edit/package.json +8 -2
- package/packages/codex-edit/src/parser.ts +26 -3
- package/packages/codex-edit/tests/benchmark.test.mjs +81 -0
- package/packages/codex-edit/tests/comparison-cases.mjs +396 -0
- package/packages/codex-edit/tests/comparison-harness.mjs +44 -0
- package/packages/codex-edit/tests/generate-test-report.mjs +589 -0
- package/packages/codex-edit/tests/legacy-parser-v0.1.7.mjs +128 -0
- package/packages/codex-edit/tests/native-vs-codex.ts +109 -0
- package/packages/codex-edit/tests/package.test.mjs +8 -0
- package/packages/codex-edit/tests/parser.test.mjs +123 -1
- package/packages/codex-edit/tests/released-vs-local.test.mjs +31 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
export const comparisonCases = [
|
|
2
|
+
{
|
|
3
|
+
id: "unique-replacement",
|
|
4
|
+
kind: "valid-edit",
|
|
5
|
+
input: { "app.ts": "const enabled = false;\n" },
|
|
6
|
+
expected: { "app.ts": "const enabled = true;\n" },
|
|
7
|
+
patch: `*** Begin Patch
|
|
8
|
+
*** Update File: app.ts
|
|
9
|
+
@@
|
|
10
|
+
-const enabled = false;
|
|
11
|
+
+const enabled = true;
|
|
12
|
+
*** End Patch`,
|
|
13
|
+
nativeCalls: [{ path: "app.ts", edits: [{ oldText: "const enabled = false;", newText: "const enabled = true;" }] }],
|
|
14
|
+
outcomes: { native: "pass", released: "pass", local: "pass" },
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "disjoint-edits-one-file",
|
|
18
|
+
kind: "valid-edit",
|
|
19
|
+
input: { "app.ts": "const first = 1;\nconst middle = 2;\nconst last = 3;\n" },
|
|
20
|
+
expected: { "app.ts": "const first = 10;\nconst middle = 2;\nconst last = 30;\n" },
|
|
21
|
+
patch: `*** Begin Patch
|
|
22
|
+
*** Update File: app.ts
|
|
23
|
+
@@
|
|
24
|
+
-const first = 1;
|
|
25
|
+
+const first = 10;
|
|
26
|
+
@@
|
|
27
|
+
-const last = 3;
|
|
28
|
+
+const last = 30;
|
|
29
|
+
*** End Patch`,
|
|
30
|
+
nativeCalls: [{
|
|
31
|
+
path: "app.ts",
|
|
32
|
+
edits: [
|
|
33
|
+
{ oldText: "const first = 1;", newText: "const first = 10;" },
|
|
34
|
+
{ oldText: "const last = 3;", newText: "const last = 30;" },
|
|
35
|
+
],
|
|
36
|
+
}],
|
|
37
|
+
outcomes: { native: "pass", released: "pass", local: "pass" },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "repeated-markdown-separator",
|
|
41
|
+
kind: "valid-edit",
|
|
42
|
+
input: { "SKILL.md": "---\nname: demo\ndescription: old\n---\n\n# Demo\n\n---\nFooter\n" },
|
|
43
|
+
expected: { "SKILL.md": "---\nname: demo\ndescription: new\n---\nInserted after frontmatter.\n\n# Demo\n\n---\nFooter\n" },
|
|
44
|
+
patch: `*** Begin Patch
|
|
45
|
+
*** Update File: SKILL.md
|
|
46
|
+
@@
|
|
47
|
+
-description: old
|
|
48
|
+
+description: new
|
|
49
|
+
@@
|
|
50
|
+
---
|
|
51
|
+
+Inserted after frontmatter.
|
|
52
|
+
*** End Patch`,
|
|
53
|
+
nativeCalls: [{ path: "SKILL.md", edits: [{ oldText: "---", newText: "---\nInserted after frontmatter." }] }],
|
|
54
|
+
outcomes: { native: "fail", released: "pass", local: "pass" },
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "repeated-update-same-path",
|
|
58
|
+
kind: "valid-edit",
|
|
59
|
+
input: { "tool.ts": "const state = \"old\";\n" },
|
|
60
|
+
expected: { "tool.ts": "const state = \"new\";\n" },
|
|
61
|
+
patch: `*** Begin Patch
|
|
62
|
+
*** Update File: tool.ts
|
|
63
|
+
@@
|
|
64
|
+
-const state = "old";
|
|
65
|
+
+const state = "middle";
|
|
66
|
+
*** Update File: tool.ts
|
|
67
|
+
@@
|
|
68
|
+
-const state = "middle";
|
|
69
|
+
+const state = "new";
|
|
70
|
+
*** End Patch`,
|
|
71
|
+
nativeCalls: [
|
|
72
|
+
{ path: "tool.ts", edits: [{ oldText: "const state = \"old\";", newText: "const state = \"middle\";" }] },
|
|
73
|
+
{ path: "tool.ts", edits: [{ oldText: "const state = \"middle\";", newText: "const state = \"new\";" }] },
|
|
74
|
+
],
|
|
75
|
+
outcomes: { native: "pass", released: "fail", local: "pass" },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "ordered-runner-type-renames",
|
|
79
|
+
kind: "valid-edit",
|
|
80
|
+
input: {
|
|
81
|
+
"packages/herdr-subagents/src/runner.ts": `export interface TinySubagentDocument {
|
|
82
|
+
\tpath: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface TinySubagentReport {
|
|
86
|
+
\tdocuments: TinySubagentDocument[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TinySubagentOptions {
|
|
90
|
+
\tonUpdate?: (result: TinySubagentResult) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface TinySubagentResult {
|
|
94
|
+
\tdocuments: TinySubagentDocument[];
|
|
95
|
+
\tstatus: "running" | "failed";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function compactText(report: TinySubagentReport): string {
|
|
99
|
+
\treturn report.documents.map((document) => document.path).join("\\n");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<TinySubagentResult> = {}): TinySubagentResult {
|
|
103
|
+
\treturn { documents: [], status: "failed", ...fields };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validReport(value: unknown): value is TinySubagentReport {
|
|
107
|
+
\treturn Boolean(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function readReport(path: string): Promise<TinySubagentReport | undefined> {
|
|
111
|
+
\treturn undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function runTinySubagent(prompt: string, options: TinySubagentOptions = {}): Promise<TinySubagentResult> {
|
|
115
|
+
\tlet result: TinySubagentResult | undefined;
|
|
116
|
+
\tconst common = (): Partial<TinySubagentResult> => ({});
|
|
117
|
+
\tconst update = (status: TinySubagentResult["status"], summary: string) => {};
|
|
118
|
+
\treturn result ?? failure(".", 0, prompt, common());
|
|
119
|
+
}
|
|
120
|
+
`,
|
|
121
|
+
},
|
|
122
|
+
expected: {
|
|
123
|
+
"packages/herdr-subagents/src/runner.ts": `export interface HerdrSubagentsDocument {
|
|
124
|
+
\tpath: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface HerdrSubagentsReport {
|
|
128
|
+
\tdocuments: HerdrSubagentsDocument[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface HerdrSubagentsOptions {
|
|
132
|
+
\tonUpdate?: (result: HerdrSubagentsResult) => void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface HerdrSubagentsResult {
|
|
136
|
+
\tdocuments: HerdrSubagentsDocument[];
|
|
137
|
+
\tstatus: "running" | "failed";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function compactText(report: HerdrSubagentsReport): string {
|
|
141
|
+
\treturn report.documents.map((document) => document.path).join("\\n");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<HerdrSubagentsResult> = {}): HerdrSubagentsResult {
|
|
145
|
+
\treturn { documents: [], status: "failed", ...fields };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function validReport(value: unknown): value is HerdrSubagentsReport {
|
|
149
|
+
\treturn Boolean(value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function readReport(path: string): Promise<HerdrSubagentsReport | undefined> {
|
|
153
|
+
\treturn undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export async function runHerdrSubagents(prompt: string, options: HerdrSubagentsOptions = {}): Promise<HerdrSubagentsResult> {
|
|
157
|
+
\tlet result: HerdrSubagentsResult | undefined;
|
|
158
|
+
\tconst common = (): Partial<HerdrSubagentsResult> => ({});
|
|
159
|
+
\tconst update = (status: HerdrSubagentsResult["status"], summary: string) => {};
|
|
160
|
+
\treturn result ?? failure(".", 0, prompt, common());
|
|
161
|
+
}
|
|
162
|
+
`,
|
|
163
|
+
},
|
|
164
|
+
patch: `*** Begin Patch
|
|
165
|
+
*** Update File: packages/herdr-subagents/src/runner.ts
|
|
166
|
+
@@
|
|
167
|
+
-export interface TinySubagentDocument {
|
|
168
|
+
+export interface HerdrSubagentsDocument {
|
|
169
|
+
@@
|
|
170
|
+
-export interface TinySubagentReport {
|
|
171
|
+
+export interface HerdrSubagentsReport {
|
|
172
|
+
@@
|
|
173
|
+
-\tdocuments: TinySubagentDocument[];
|
|
174
|
+
+\tdocuments: HerdrSubagentsDocument[];
|
|
175
|
+
@@
|
|
176
|
+
-export interface TinySubagentOptions {
|
|
177
|
+
+export interface HerdrSubagentsOptions {
|
|
178
|
+
@@
|
|
179
|
+
-\tonUpdate?: (result: TinySubagentResult) => void;
|
|
180
|
+
+\tonUpdate?: (result: HerdrSubagentsResult) => void;
|
|
181
|
+
@@
|
|
182
|
+
-export interface TinySubagentResult {
|
|
183
|
+
+export interface HerdrSubagentsResult {
|
|
184
|
+
@@
|
|
185
|
+
-\tdocuments: TinySubagentDocument[];
|
|
186
|
+
+\tdocuments: HerdrSubagentsDocument[];
|
|
187
|
+
@@
|
|
188
|
+
-function compactText(report: TinySubagentReport): string {
|
|
189
|
+
+function compactText(report: HerdrSubagentsReport): string {
|
|
190
|
+
@@
|
|
191
|
+
-function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<TinySubagentResult> = {}): TinySubagentResult {
|
|
192
|
+
+function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<HerdrSubagentsResult> = {}): HerdrSubagentsResult {
|
|
193
|
+
@@
|
|
194
|
+
-function validReport(value: unknown): value is TinySubagentReport {
|
|
195
|
+
+function validReport(value: unknown): value is HerdrSubagentsReport {
|
|
196
|
+
@@
|
|
197
|
+
-async function readReport(path: string): Promise<TinySubagentReport | undefined> {
|
|
198
|
+
+async function readReport(path: string): Promise<HerdrSubagentsReport | undefined> {
|
|
199
|
+
@@
|
|
200
|
+
-export async function runTinySubagent(prompt: string, options: TinySubagentOptions = {}): Promise<TinySubagentResult> {
|
|
201
|
+
+export async function runHerdrSubagents(prompt: string, options: HerdrSubagentsOptions = {}): Promise<HerdrSubagentsResult> {
|
|
202
|
+
@@
|
|
203
|
+
-\tlet result: TinySubagentResult | undefined;
|
|
204
|
+
-\tconst common = (): Partial<TinySubagentResult> => ({});
|
|
205
|
+
+\tlet result: HerdrSubagentsResult | undefined;
|
|
206
|
+
+\tconst common = (): Partial<HerdrSubagentsResult> => ({});
|
|
207
|
+
@@
|
|
208
|
+
-\tconst update = (status: TinySubagentResult["status"], summary: string) => {};
|
|
209
|
+
+\tconst update = (status: HerdrSubagentsResult["status"], summary: string) => {};
|
|
210
|
+
*** End Patch`,
|
|
211
|
+
nativeCalls: [{
|
|
212
|
+
path: "packages/herdr-subagents/src/runner.ts",
|
|
213
|
+
edits: [{
|
|
214
|
+
oldText: `export interface TinySubagentDocument {
|
|
215
|
+
\tpath: string;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface TinySubagentReport {
|
|
219
|
+
\tdocuments: TinySubagentDocument[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface TinySubagentOptions {
|
|
223
|
+
\tonUpdate?: (result: TinySubagentResult) => void;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface TinySubagentResult {
|
|
227
|
+
\tdocuments: TinySubagentDocument[];
|
|
228
|
+
\tstatus: "running" | "failed";
|
|
229
|
+
}`,
|
|
230
|
+
newText: `export interface HerdrSubagentsDocument {
|
|
231
|
+
\tpath: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface HerdrSubagentsReport {
|
|
235
|
+
\tdocuments: HerdrSubagentsDocument[];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface HerdrSubagentsOptions {
|
|
239
|
+
\tonUpdate?: (result: HerdrSubagentsResult) => void;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface HerdrSubagentsResult {
|
|
243
|
+
\tdocuments: HerdrSubagentsDocument[];
|
|
244
|
+
\tstatus: "running" | "failed";
|
|
245
|
+
}`,
|
|
246
|
+
}, {
|
|
247
|
+
oldText: `function compactText(report: TinySubagentReport): string {
|
|
248
|
+
\treturn report.documents.map((document) => document.path).join("\\n");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<TinySubagentResult> = {}): TinySubagentResult {
|
|
252
|
+
\treturn { documents: [], status: "failed", ...fields };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function validReport(value: unknown): value is TinySubagentReport {
|
|
256
|
+
\treturn Boolean(value);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function readReport(path: string): Promise<TinySubagentReport | undefined> {
|
|
260
|
+
\treturn undefined;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function runTinySubagent(prompt: string, options: TinySubagentOptions = {}): Promise<TinySubagentResult> {
|
|
264
|
+
\tlet result: TinySubagentResult | undefined;
|
|
265
|
+
\tconst common = (): Partial<TinySubagentResult> => ({});
|
|
266
|
+
\tconst update = (status: TinySubagentResult["status"], summary: string) => {};`,
|
|
267
|
+
newText: `function compactText(report: HerdrSubagentsReport): string {
|
|
268
|
+
\treturn report.documents.map((document) => document.path).join("\\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function failure(cwd: string, startedAt: number, errorMessage: string, fields: Partial<HerdrSubagentsResult> = {}): HerdrSubagentsResult {
|
|
272
|
+
\treturn { documents: [], status: "failed", ...fields };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function validReport(value: unknown): value is HerdrSubagentsReport {
|
|
276
|
+
\treturn Boolean(value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function readReport(path: string): Promise<HerdrSubagentsReport | undefined> {
|
|
280
|
+
\treturn undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function runHerdrSubagents(prompt: string, options: HerdrSubagentsOptions = {}): Promise<HerdrSubagentsResult> {
|
|
284
|
+
\tlet result: HerdrSubagentsResult | undefined;
|
|
285
|
+
\tconst common = (): Partial<HerdrSubagentsResult> => ({});
|
|
286
|
+
\tconst update = (status: HerdrSubagentsResult["status"], summary: string) => {};`,
|
|
287
|
+
}],
|
|
288
|
+
}],
|
|
289
|
+
outcomes: { native: "pass", released: "pass", local: "pass" },
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "multi-file-add-delete-move",
|
|
293
|
+
kind: "valid-edit",
|
|
294
|
+
input: { "old.ts": "export const value = 1;\n", "remove.txt": "obsolete\n" },
|
|
295
|
+
expected: { "new.ts": "export const value = 2;\n", "added.txt": "created\n" },
|
|
296
|
+
patch: `*** Begin Patch
|
|
297
|
+
*** Update File: old.ts
|
|
298
|
+
*** Move to: new.ts
|
|
299
|
+
@@
|
|
300
|
+
-export const value = 1;
|
|
301
|
+
+export const value = 2;
|
|
302
|
+
*** Delete File: remove.txt
|
|
303
|
+
*** Add File: added.txt
|
|
304
|
+
+created
|
|
305
|
+
*** End Patch`,
|
|
306
|
+
nativeCalls: null,
|
|
307
|
+
outcomes: { native: "unsupported", released: "pass", local: "pass" },
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "stale-expected-lines",
|
|
311
|
+
kind: "safety",
|
|
312
|
+
input: { "app.ts": "const actual = true;\n" },
|
|
313
|
+
expected: null,
|
|
314
|
+
patch: `*** Begin Patch
|
|
315
|
+
*** Update File: app.ts
|
|
316
|
+
@@
|
|
317
|
+
-const stale = false;
|
|
318
|
+
+const stale = true;
|
|
319
|
+
*** End Patch`,
|
|
320
|
+
nativeCalls: [{ path: "app.ts", edits: [{ oldText: "const stale = false;", newText: "const stale = true;" }] }],
|
|
321
|
+
outcomes: { native: "fail", released: "fail", local: "fail" },
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: "conflicting-targets",
|
|
325
|
+
kind: "safety",
|
|
326
|
+
input: { "app.ts": "old\n" },
|
|
327
|
+
expected: null,
|
|
328
|
+
patch: `*** Begin Patch
|
|
329
|
+
*** Update File: app.ts
|
|
330
|
+
@@
|
|
331
|
+
-old
|
|
332
|
+
+new
|
|
333
|
+
*** Delete File: app.ts
|
|
334
|
+
*** End Patch`,
|
|
335
|
+
nativeCalls: null,
|
|
336
|
+
outcomes: { native: "unsupported", released: "fail", local: "fail" },
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "empty-update-chunk",
|
|
340
|
+
kind: "safety",
|
|
341
|
+
input: { "app.ts": "unchanged\n" },
|
|
342
|
+
expected: null,
|
|
343
|
+
patch: `*** Begin Patch
|
|
344
|
+
*** Update File: app.ts
|
|
345
|
+
@@
|
|
346
|
+
*** End Patch`,
|
|
347
|
+
nativeCalls: null,
|
|
348
|
+
outcomes: { native: "unsupported", released: "fail", local: "fail" },
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "resolved-path-alias-conflict",
|
|
352
|
+
kind: "safety",
|
|
353
|
+
input: { "app.ts": "const value = 1;\n" },
|
|
354
|
+
expected: null,
|
|
355
|
+
patch: `*** Begin Patch
|
|
356
|
+
*** Update File: app.ts
|
|
357
|
+
@@
|
|
358
|
+
-const value = 1;
|
|
359
|
+
+const value = 2;
|
|
360
|
+
*** Update File: ./app.ts
|
|
361
|
+
@@
|
|
362
|
+
-const value = 2;
|
|
363
|
+
+const value = 3;
|
|
364
|
+
*** End Patch`,
|
|
365
|
+
nativeCalls: null,
|
|
366
|
+
outcomes: { native: "unsupported", released: "fail", local: "fail" },
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "outside-workspace-absolute-path",
|
|
370
|
+
kind: "safety",
|
|
371
|
+
input: { "app.ts": "const value = 1;\n" },
|
|
372
|
+
expected: null,
|
|
373
|
+
patch: `*** Begin Patch
|
|
374
|
+
*** Update File: /tmp/outside-workspace.ts
|
|
375
|
+
@@
|
|
376
|
+
-const value = 1;
|
|
377
|
+
+const value = 2;
|
|
378
|
+
*** End Patch`,
|
|
379
|
+
nativeCalls: null,
|
|
380
|
+
outcomes: { native: "unsupported", released: "fail", local: "fail" },
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: "bom-and-crlf",
|
|
384
|
+
kind: "valid-edit",
|
|
385
|
+
input: { "app.ts": "\ufeffconst old = 1;\r\nconst keep = 2;\r\n" },
|
|
386
|
+
expected: { "app.ts": "\ufeffconst next = 1;\r\nconst keep = 2;\r\n" },
|
|
387
|
+
patch: `*** Begin Patch
|
|
388
|
+
*** Update File: app.ts
|
|
389
|
+
@@
|
|
390
|
+
-const old = 1;
|
|
391
|
+
+const next = 1;
|
|
392
|
+
*** End Patch`,
|
|
393
|
+
nativeCalls: [{ path: "app.ts", edits: [{ oldText: "const old = 1;", newText: "const next = 1;" }] }],
|
|
394
|
+
outcomes: { native: "pass", released: "pass", local: "pass" },
|
|
395
|
+
},
|
|
396
|
+
];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
|
|
3
|
+
export function applyOperations(input, operations, applyUpdate) {
|
|
4
|
+
const files = new Map(Object.entries(input));
|
|
5
|
+
for (const operation of operations) {
|
|
6
|
+
if (operation.action === "add") {
|
|
7
|
+
if (files.has(operation.path)) throw new Error(`Cannot add existing file ${operation.path}`);
|
|
8
|
+
files.set(operation.path, operation.content);
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (!files.has(operation.path)) throw new Error(`Missing source file ${operation.path}`);
|
|
12
|
+
if (operation.action === "delete") {
|
|
13
|
+
files.delete(operation.path);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const groups = operation.chunkGroups ?? [operation.chunks];
|
|
17
|
+
const after = groups.reduce(
|
|
18
|
+
(content, chunks) => applyUpdate(content, chunks, operation.path),
|
|
19
|
+
files.get(operation.path),
|
|
20
|
+
);
|
|
21
|
+
if (operation.moveTo) {
|
|
22
|
+
if (files.has(operation.moveTo)) throw new Error(`Cannot move over existing file ${operation.moveTo}`);
|
|
23
|
+
files.delete(operation.path);
|
|
24
|
+
files.set(operation.moveTo, after);
|
|
25
|
+
} else {
|
|
26
|
+
files.set(operation.path, after);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Object.fromEntries([...files].sort(([left], [right]) => left.localeCompare(right)));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function runPatchCase(testCase, parse, applyUpdate, expectedOutcome) {
|
|
33
|
+
try {
|
|
34
|
+
const actual = applyOperations(testCase.input, parse(testCase.patch), applyUpdate);
|
|
35
|
+
if (expectedOutcome !== "pass") {
|
|
36
|
+
assert.fail(`${testCase.id}: expected ${expectedOutcome}, but patch succeeded`);
|
|
37
|
+
}
|
|
38
|
+
assert.deepEqual(actual, testCase.expected, `${testCase.id}: final filesystem differs`);
|
|
39
|
+
return "pass";
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (expectedOutcome === "pass") throw error;
|
|
42
|
+
return "fail";
|
|
43
|
+
}
|
|
44
|
+
}
|