@shanepadgett/tau-agent 0.33.1 → 0.34.0
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/docs/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +79 -0
- package/extensions/checkpoint/checkpoint.ts +277 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +169 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/panel.ts +28 -16
- package/extensions/review/index.ts +63 -51
- package/extensions/review/panel.ts +5 -34
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +304 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +14 -6
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +20 -33
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +71 -29
- package/shared/model-effort.ts +9 -19
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -230,19 +230,32 @@ async function stageUpdate(cwd: string, op: Extract<PatchOperation, { type: "upd
|
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
function operationPaths(cwd: string, op: PatchOperation): string[] {
|
|
234
|
+
const paths = [resolvePath(cwd, op.path)];
|
|
235
|
+
if (op.type === "update" && op.movePath) paths.push(resolvePath(cwd, op.movePath));
|
|
236
|
+
return paths;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function recordPathSection(
|
|
240
|
+
pathSections: Map<string, number[]>,
|
|
241
|
+
displayPaths: Map<string, string>,
|
|
242
|
+
path: string,
|
|
243
|
+
sectionIndex: number,
|
|
244
|
+
displayPath: string,
|
|
245
|
+
): void {
|
|
246
|
+
const existing = pathSections.get(path) ?? [];
|
|
247
|
+
existing.push(sectionIndex);
|
|
248
|
+
pathSections.set(path, existing);
|
|
249
|
+
if (!displayPaths.has(path)) displayPaths.set(path, displayPath);
|
|
250
|
+
}
|
|
251
|
+
|
|
233
252
|
function collectDupPathFailures(cwd: string, operations: PatchOperation[]): PatchFailure[] {
|
|
234
253
|
const pathSections = new Map<string, number[]>();
|
|
235
254
|
const displayPaths = new Map<string, string>();
|
|
236
255
|
|
|
237
256
|
for (const op of operations) {
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
for (const p of paths) {
|
|
242
|
-
const existing = pathSections.get(p) ?? [];
|
|
243
|
-
existing.push(op.sectionIndex);
|
|
244
|
-
pathSections.set(p, existing);
|
|
245
|
-
if (!displayPaths.has(p)) displayPaths.set(p, op.path);
|
|
257
|
+
for (const path of operationPaths(cwd, op)) {
|
|
258
|
+
recordPathSection(pathSections, displayPaths, path, op.sectionIndex, op.path);
|
|
246
259
|
}
|
|
247
260
|
}
|
|
248
261
|
|
|
@@ -272,12 +285,77 @@ function withMutationQueuePaths<T>(paths: string[], fn: () => Promise<T>): Promi
|
|
|
272
285
|
return current();
|
|
273
286
|
}
|
|
274
287
|
|
|
275
|
-
|
|
288
|
+
function displayPathFor(cwd: string, absolutePath: string): string {
|
|
289
|
+
const displayPath = relative(resolve(cwd), absolutePath);
|
|
290
|
+
return displayPath.startsWith("..") || isAbsolute(displayPath) ? absolutePath : displayPath;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function stageExactSourceMutation(
|
|
276
294
|
cwd: string,
|
|
277
|
-
|
|
295
|
+
mutation: ExactSourceMutation,
|
|
296
|
+
sectionIndex: number,
|
|
297
|
+
signal?: AbortSignal,
|
|
298
|
+
): Promise<{ mutation: ExactSourceMutation; change: ApplyPatchChange }> {
|
|
299
|
+
throwIfAborted(signal);
|
|
300
|
+
await assertExistingFile(mutation.path, mutation.path);
|
|
301
|
+
const canonical = await realpath(mutation.path);
|
|
302
|
+
if (canonical !== mutation.path) {
|
|
303
|
+
throw new Error(`Exact-source mutation path is not canonical: ${mutation.path}`);
|
|
304
|
+
}
|
|
305
|
+
const current = await readUtf8(mutation.path, mutation.path);
|
|
306
|
+
const currentFingerprint = fingerprint(current);
|
|
307
|
+
if (currentFingerprint !== mutation.expectedFingerprint) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Source changed before exact-source mutation for ${mutation.path}; expected ${mutation.expectedFingerprint}, found ${currentFingerprint}.`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const lineCount = countLogicalLines(mutation.source);
|
|
313
|
+
return {
|
|
314
|
+
mutation,
|
|
315
|
+
change: {
|
|
316
|
+
sectionIndex,
|
|
317
|
+
kind: "update",
|
|
318
|
+
path: displayPathFor(cwd, mutation.path),
|
|
319
|
+
linesAdded: lineCount,
|
|
320
|
+
linesRemoved: countLogicalLines(current),
|
|
321
|
+
resultingFingerprint: fingerprint(mutation.source),
|
|
322
|
+
snapshotRanges: lineCount > 0 ? [{ startLine: 1, endLine: Math.min(lineCount, 120) }] : undefined,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async function commitExactSourceMutations(
|
|
328
|
+
staged: Array<{ mutation: ExactSourceMutation; change: ApplyPatchChange }>,
|
|
278
329
|
signal?: AbortSignal,
|
|
279
330
|
): Promise<ApplyPatchSummary> {
|
|
280
331
|
throwIfAborted(signal);
|
|
332
|
+
const changes: ApplyPatchChange[] = [];
|
|
333
|
+
for (const { mutation, change } of staged) {
|
|
334
|
+
try {
|
|
335
|
+
throwIfAborted(signal);
|
|
336
|
+
await writeFile(mutation.path, mutation.source, "utf8");
|
|
337
|
+
changes.push(change);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
return {
|
|
340
|
+
status: changes.length === 0 ? "failed" : "partial",
|
|
341
|
+
changes,
|
|
342
|
+
failures: [
|
|
343
|
+
{
|
|
344
|
+
phase: "apply",
|
|
345
|
+
sectionIndex: change.sectionIndex,
|
|
346
|
+
path: change.path,
|
|
347
|
+
kind: "update",
|
|
348
|
+
message: error instanceof Error ? error.message : String(error),
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
totalSections: staged.length,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return { status: "completed", changes, failures: [], totalSections: staged.length };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function orderExactSourceMutations(cwd: string, mutations: readonly ExactSourceMutation[]): ExactSourceMutation[] {
|
|
281
359
|
if (mutations.length === 0) throw new Error("Exact-source mutation requires at least one file.");
|
|
282
360
|
const byPath = new Map<string, ExactSourceMutation>();
|
|
283
361
|
for (const mutation of mutations) {
|
|
@@ -285,71 +363,115 @@ export async function applyExactSourceMutations(
|
|
|
285
363
|
if (byPath.has(path)) throw new Error(`Conflicting exact-source mutations for path: ${mutation.path}`);
|
|
286
364
|
byPath.set(path, { ...mutation, path });
|
|
287
365
|
}
|
|
288
|
-
|
|
366
|
+
return [...byPath.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
367
|
+
}
|
|
289
368
|
|
|
369
|
+
export async function applyExactSourceMutations(
|
|
370
|
+
cwd: string,
|
|
371
|
+
mutations: readonly ExactSourceMutation[],
|
|
372
|
+
signal?: AbortSignal,
|
|
373
|
+
): Promise<ApplyPatchSummary> {
|
|
374
|
+
throwIfAborted(signal);
|
|
375
|
+
const ordered = orderExactSourceMutations(cwd, mutations);
|
|
290
376
|
return withMutationQueuePaths(
|
|
291
377
|
ordered.map((mutation) => mutation.path),
|
|
292
378
|
async () => {
|
|
293
379
|
const staged: Array<{ mutation: ExactSourceMutation; change: ApplyPatchChange }> = [];
|
|
294
380
|
for (const [sectionIndex, mutation] of ordered.entries()) {
|
|
295
|
-
|
|
296
|
-
await assertExistingFile(mutation.path, mutation.path);
|
|
297
|
-
const canonical = await realpath(mutation.path);
|
|
298
|
-
if (canonical !== mutation.path) {
|
|
299
|
-
throw new Error(`Exact-source mutation path is not canonical: ${mutation.path}`);
|
|
300
|
-
}
|
|
301
|
-
const current = await readUtf8(mutation.path, mutation.path);
|
|
302
|
-
const currentFingerprint = fingerprint(current);
|
|
303
|
-
if (currentFingerprint !== mutation.expectedFingerprint) {
|
|
304
|
-
throw new Error(
|
|
305
|
-
`Source changed before exact-source mutation for ${mutation.path}; expected ${mutation.expectedFingerprint}, found ${currentFingerprint}.`,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
const displayPath = relative(resolve(cwd), mutation.path);
|
|
309
|
-
const lineCount = countLogicalLines(mutation.source);
|
|
310
|
-
staged.push({
|
|
311
|
-
mutation,
|
|
312
|
-
change: {
|
|
313
|
-
sectionIndex,
|
|
314
|
-
kind: "update",
|
|
315
|
-
path: displayPath.startsWith("..") || isAbsolute(displayPath) ? mutation.path : displayPath,
|
|
316
|
-
linesAdded: lineCount,
|
|
317
|
-
linesRemoved: countLogicalLines(current),
|
|
318
|
-
resultingFingerprint: fingerprint(mutation.source),
|
|
319
|
-
snapshotRanges: lineCount > 0 ? [{ startLine: 1, endLine: Math.min(lineCount, 120) }] : undefined,
|
|
320
|
-
},
|
|
321
|
-
});
|
|
381
|
+
staged.push(await stageExactSourceMutation(cwd, mutation, sectionIndex, signal));
|
|
322
382
|
}
|
|
323
|
-
|
|
324
|
-
throwIfAborted(signal);
|
|
325
|
-
const changes: ApplyPatchChange[] = [];
|
|
326
|
-
for (const { mutation, change } of staged) {
|
|
327
|
-
try {
|
|
328
|
-
throwIfAborted(signal);
|
|
329
|
-
await writeFile(mutation.path, mutation.source, "utf8");
|
|
330
|
-
changes.push(change);
|
|
331
|
-
} catch (error) {
|
|
332
|
-
return {
|
|
333
|
-
status: changes.length === 0 ? "failed" : "partial",
|
|
334
|
-
changes,
|
|
335
|
-
failures: [
|
|
336
|
-
{
|
|
337
|
-
phase: "apply",
|
|
338
|
-
sectionIndex: change.sectionIndex,
|
|
339
|
-
path: change.path,
|
|
340
|
-
kind: "update",
|
|
341
|
-
message: error instanceof Error ? error.message : String(error),
|
|
342
|
-
},
|
|
343
|
-
],
|
|
344
|
-
totalSections: staged.length,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return { status: "completed", changes, failures: [], totalSections: staged.length };
|
|
383
|
+
return commitExactSourceMutations(staged, signal);
|
|
349
384
|
},
|
|
350
385
|
);
|
|
351
386
|
}
|
|
352
387
|
|
|
388
|
+
function toApplyFailure(op: PatchOperation, error: unknown): PatchFailure {
|
|
389
|
+
const failure: PatchFailure = {
|
|
390
|
+
phase: "apply",
|
|
391
|
+
sectionIndex: op.sectionIndex,
|
|
392
|
+
path: op.path,
|
|
393
|
+
kind: op.type,
|
|
394
|
+
message: error instanceof Error ? error.message : String(error),
|
|
395
|
+
};
|
|
396
|
+
if (error instanceof UpdateChunkApplyError) {
|
|
397
|
+
failure.chunkIndex = error.chunkIndex;
|
|
398
|
+
failure.totalChunks = error.totalChunks;
|
|
399
|
+
failure.contextHint = error.contextHint;
|
|
400
|
+
}
|
|
401
|
+
return failure;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function stageOperation(cwd: string, op: PatchOperation): Promise<StagedMutation> {
|
|
405
|
+
if (op.type === "add" || op.type === "replace") return stageWholeFile(cwd, op);
|
|
406
|
+
if (op.type === "delete") return stageDelete(cwd, op);
|
|
407
|
+
return stageUpdate(cwd, op);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function stageRunnableOperations(
|
|
411
|
+
cwd: string,
|
|
412
|
+
operations: PatchOperation[],
|
|
413
|
+
initialFailures: PatchFailure[],
|
|
414
|
+
): Promise<{ staged: StagedMutation[]; failures: PatchFailure[] }> {
|
|
415
|
+
const staged: StagedMutation[] = [];
|
|
416
|
+
const failures: PatchFailure[] = [...initialFailures];
|
|
417
|
+
for (const op of operations) {
|
|
418
|
+
try {
|
|
419
|
+
staged.push(await stageOperation(cwd, op));
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (error instanceof Error && error.message === "Operation aborted") throw error;
|
|
422
|
+
failures.push(toApplyFailure(op, error));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return { staged, failures };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function commitStagedMutations(
|
|
429
|
+
staged: StagedMutation[],
|
|
430
|
+
failures: PatchFailure[],
|
|
431
|
+
totalSections: number,
|
|
432
|
+
signal: AbortSignal | undefined,
|
|
433
|
+
onProgress?: (summary: ApplyPatchSummary) => void | Promise<void>,
|
|
434
|
+
): Promise<ApplyPatchSummary> {
|
|
435
|
+
if (staged.length === 0) {
|
|
436
|
+
const summary: ApplyPatchSummary = { status: "failed", changes: [], failures, totalSections };
|
|
437
|
+
await onProgress?.(summary);
|
|
438
|
+
return summary;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
throwIfAborted(signal);
|
|
442
|
+
const changes: ApplyPatchChange[] = [];
|
|
443
|
+
for (const mutation of staged) {
|
|
444
|
+
try {
|
|
445
|
+
await mutation.commit();
|
|
446
|
+
changes.push(mutation.change);
|
|
447
|
+
await onProgress?.({ status: "partial", changes: [...changes], failures: [...failures], totalSections });
|
|
448
|
+
} catch (error) {
|
|
449
|
+
failures.push({
|
|
450
|
+
phase: "apply",
|
|
451
|
+
sectionIndex: mutation.change.sectionIndex,
|
|
452
|
+
path: mutation.change.path,
|
|
453
|
+
kind: mutation.change.kind,
|
|
454
|
+
message: error instanceof Error ? error.message : String(error),
|
|
455
|
+
});
|
|
456
|
+
const summary: ApplyPatchSummary = {
|
|
457
|
+
status: changes.length === 0 ? "failed" : "partial",
|
|
458
|
+
changes: [...changes],
|
|
459
|
+
failures: [...failures],
|
|
460
|
+
totalSections,
|
|
461
|
+
};
|
|
462
|
+
await onProgress?.(summary);
|
|
463
|
+
return summary;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
status: failures.length > 0 ? "partial" : "completed",
|
|
469
|
+
changes,
|
|
470
|
+
failures,
|
|
471
|
+
totalSections,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
353
475
|
export async function applyPatch(
|
|
354
476
|
cwd: string,
|
|
355
477
|
input: string,
|
|
@@ -370,80 +492,13 @@ export async function applyPatch(
|
|
|
370
492
|
return summary;
|
|
371
493
|
}
|
|
372
494
|
|
|
373
|
-
const
|
|
374
|
-
const initialFailures = [...parseFailures, ...dupFailures];
|
|
495
|
+
const initialFailures = [...parseFailures, ...collectDupPathFailures(cwd, operations)];
|
|
375
496
|
const blockedSections = new Set(initialFailures.map((failure) => failure.sectionIndex));
|
|
376
497
|
const runnableOperations = operations.filter((op) => !blockedSections.has(op.sectionIndex));
|
|
377
|
-
|
|
378
|
-
const queuePaths = runnableOperations.flatMap((op) => {
|
|
379
|
-
const paths = [resolvePath(cwd, op.path)];
|
|
380
|
-
if (op.type === "update" && op.movePath) paths.push(resolvePath(cwd, op.movePath));
|
|
381
|
-
return paths;
|
|
382
|
-
});
|
|
498
|
+
const queuePaths = runnableOperations.flatMap((op) => operationPaths(cwd, op));
|
|
383
499
|
|
|
384
500
|
return withMutationQueuePaths(queuePaths, async () => {
|
|
385
|
-
const staged
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
for (const op of runnableOperations) {
|
|
389
|
-
try {
|
|
390
|
-
if (op.type === "add" || op.type === "replace") staged.push(await stageWholeFile(cwd, op));
|
|
391
|
-
else if (op.type === "delete") staged.push(await stageDelete(cwd, op));
|
|
392
|
-
else staged.push(await stageUpdate(cwd, op));
|
|
393
|
-
} catch (error) {
|
|
394
|
-
if (error instanceof Error && error.message === "Operation aborted") throw error;
|
|
395
|
-
const failure: PatchFailure = {
|
|
396
|
-
phase: "apply",
|
|
397
|
-
sectionIndex: op.sectionIndex,
|
|
398
|
-
path: op.path,
|
|
399
|
-
kind: op.type,
|
|
400
|
-
message: error instanceof Error ? error.message : String(error),
|
|
401
|
-
};
|
|
402
|
-
if (error instanceof UpdateChunkApplyError) {
|
|
403
|
-
failure.chunkIndex = error.chunkIndex;
|
|
404
|
-
failure.totalChunks = error.totalChunks;
|
|
405
|
-
failure.contextHint = error.contextHint;
|
|
406
|
-
}
|
|
407
|
-
failures.push(failure);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (staged.length === 0) {
|
|
412
|
-
const summary: ApplyPatchSummary = { status: "failed", changes: [], failures, totalSections };
|
|
413
|
-
await onProgress?.(summary);
|
|
414
|
-
return summary;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
throwIfAborted(signal);
|
|
418
|
-
|
|
419
|
-
const changes: ApplyPatchChange[] = [];
|
|
420
|
-
for (const mutation of staged) {
|
|
421
|
-
try {
|
|
422
|
-
await mutation.commit();
|
|
423
|
-
changes.push(mutation.change);
|
|
424
|
-
await onProgress?.({ status: "partial", changes: [...changes], failures: [...failures], totalSections });
|
|
425
|
-
} catch (error) {
|
|
426
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
427
|
-
failures.push({
|
|
428
|
-
phase: "apply",
|
|
429
|
-
sectionIndex: mutation.change.sectionIndex,
|
|
430
|
-
path: mutation.change.path,
|
|
431
|
-
kind: mutation.change.kind,
|
|
432
|
-
message,
|
|
433
|
-
});
|
|
434
|
-
const status = changes.length === 0 ? "failed" : "partial";
|
|
435
|
-
const summary: ApplyPatchSummary = {
|
|
436
|
-
status,
|
|
437
|
-
changes: [...changes],
|
|
438
|
-
failures: [...failures],
|
|
439
|
-
totalSections,
|
|
440
|
-
};
|
|
441
|
-
await onProgress?.(summary);
|
|
442
|
-
return summary;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const status: ApplyPatchSummary["status"] = failures.length > 0 ? "partial" : "completed";
|
|
447
|
-
return { status, changes, failures, totalSections };
|
|
501
|
+
const { staged, failures } = await stageRunnableOperations(cwd, runnableOperations, initialFailures);
|
|
502
|
+
return commitStagedMutations(staged, failures, totalSections, signal, onProgress);
|
|
448
503
|
});
|
|
449
504
|
}
|
|
@@ -125,10 +125,86 @@ function buildReplacement(chunk: UpdateFileChunk, fileLines: string[], matchInde
|
|
|
125
125
|
return result;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
interface ChunkReplacement {
|
|
129
|
+
index: number;
|
|
130
|
+
deleteCount: number;
|
|
131
|
+
insert: string[];
|
|
132
|
+
order: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function seekChunkContext(
|
|
136
|
+
lines: string[],
|
|
137
|
+
chunk: UpdateFileChunk,
|
|
138
|
+
lineIndex: number,
|
|
139
|
+
chunkNumber: number,
|
|
140
|
+
total: number,
|
|
141
|
+
): number {
|
|
142
|
+
if (!chunk.changeContext) return lineIndex;
|
|
143
|
+
const contextIndex = seekSequence(lines, [chunk.changeContext], lineIndex);
|
|
144
|
+
if (contextIndex < 0) {
|
|
145
|
+
throw new UpdateChunkApplyError(chunkNumber, total, formatContextHint(chunk), "could not find update context");
|
|
146
|
+
}
|
|
147
|
+
return contextIndex + 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function matchChunkIndex(
|
|
151
|
+
lines: string[],
|
|
152
|
+
chunk: UpdateFileChunk,
|
|
153
|
+
oldLines: string[],
|
|
154
|
+
lineIndex: number,
|
|
155
|
+
chunkNumber: number,
|
|
156
|
+
total: number,
|
|
157
|
+
): number {
|
|
158
|
+
if (oldLines.length === 0) return lines.length;
|
|
159
|
+
if (chunk.isEndOfFile) {
|
|
160
|
+
const eofIndex = lines.length - oldLines.length;
|
|
161
|
+
if (eofIndex >= lineIndex && seekSequence(lines, oldLines, eofIndex) === eofIndex) return eofIndex;
|
|
162
|
+
}
|
|
163
|
+
const matchIndex = seekSequence(lines, oldLines, lineIndex);
|
|
164
|
+
if (matchIndex < 0) {
|
|
165
|
+
throw new UpdateChunkApplyError(chunkNumber, total, formatContextHint(chunk), "could not match");
|
|
166
|
+
}
|
|
167
|
+
return matchIndex;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function planChunkReplacement(
|
|
171
|
+
lines: string[],
|
|
172
|
+
chunk: UpdateFileChunk,
|
|
173
|
+
lineIndex: number,
|
|
174
|
+
order: number,
|
|
175
|
+
chunkNumber: number,
|
|
176
|
+
total: number,
|
|
177
|
+
): {
|
|
178
|
+
replacement: ChunkReplacement;
|
|
179
|
+
nextLineIndex: number;
|
|
180
|
+
rawRange: { index: number; deleteCount: number; insertCount: number };
|
|
181
|
+
} {
|
|
182
|
+
const searchFrom = seekChunkContext(lines, chunk, lineIndex, chunkNumber, total);
|
|
183
|
+
const oldLines = chunk.lines.filter((l) => l.prefix !== "+").map((l) => l.text);
|
|
184
|
+
const matchIndex = matchChunkIndex(lines, chunk, oldLines, searchFrom, chunkNumber, total);
|
|
185
|
+
const insert = oldLines.length === 0 ? chunk.lines.map((l) => l.text) : buildReplacement(chunk, lines, matchIndex);
|
|
186
|
+
const deleteCount = oldLines.length;
|
|
187
|
+
return {
|
|
188
|
+
replacement: { index: matchIndex, deleteCount, insert, order },
|
|
189
|
+
nextLineIndex: oldLines.length === 0 ? searchFrom : matchIndex + oldLines.length,
|
|
190
|
+
rawRange: { index: matchIndex, deleteCount, insertCount: insert.length },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function applyPlannedReplacements(lines: string[], replacements: ChunkReplacement[]): string[] {
|
|
195
|
+
const output = [...lines];
|
|
196
|
+
replacements
|
|
197
|
+
.sort((a, b) => b.index - a.index || b.order - a.order)
|
|
198
|
+
.forEach((r) => {
|
|
199
|
+
output.splice(r.index, r.deleteCount, ...r.insert);
|
|
200
|
+
});
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
203
|
+
|
|
128
204
|
export function applyChunksWithRanges(currentContent: string, chunks: UpdateFileChunk[]): ApplyChunksResult {
|
|
129
205
|
const parts = splitText(currentContent);
|
|
130
206
|
const lines = splitLogicalLines(parts.text);
|
|
131
|
-
const replacements:
|
|
207
|
+
const replacements: ChunkReplacement[] = [];
|
|
132
208
|
const rawRanges: Array<{ index: number; deleteCount: number; insertCount: number }> = [];
|
|
133
209
|
let lineIndex = 0;
|
|
134
210
|
let order = 0;
|
|
@@ -136,56 +212,14 @@ export function applyChunksWithRanges(currentContent: string, chunks: UpdateFile
|
|
|
136
212
|
for (let ci = 0; ci < chunks.length; ci += 1) {
|
|
137
213
|
const chunk = chunks[ci];
|
|
138
214
|
if (chunk === undefined) continue;
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (contextIndex < 0) {
|
|
144
|
-
throw new UpdateChunkApplyError(
|
|
145
|
-
ci + 1,
|
|
146
|
-
chunks.length,
|
|
147
|
-
formatContextHint(chunk),
|
|
148
|
-
"could not find update context",
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
lineIndex = contextIndex + 1;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (oldLines.length === 0) {
|
|
155
|
-
const insertIndex = lines.length;
|
|
156
|
-
const insert = chunk.lines.map((l) => l.text);
|
|
157
|
-
replacements.push({ index: insertIndex, deleteCount: 0, insert, order });
|
|
158
|
-
order += 1;
|
|
159
|
-
rawRanges.push({ index: insertIndex, deleteCount: 0, insertCount: insert.length });
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
let matchIndex = -1;
|
|
164
|
-
if (chunk.isEndOfFile) {
|
|
165
|
-
const eofIndex = lines.length - oldLines.length;
|
|
166
|
-
if (eofIndex >= lineIndex && seekSequence(lines, oldLines, eofIndex) === eofIndex) {
|
|
167
|
-
matchIndex = eofIndex;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (matchIndex < 0) matchIndex = seekSequence(lines, oldLines, lineIndex);
|
|
171
|
-
if (matchIndex < 0) {
|
|
172
|
-
throw new UpdateChunkApplyError(ci + 1, chunks.length, formatContextHint(chunk), "could not match");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const replacement = buildReplacement(chunk, lines, matchIndex);
|
|
176
|
-
replacements.push({ index: matchIndex, deleteCount: oldLines.length, insert: replacement, order });
|
|
215
|
+
const planned = planChunkReplacement(lines, chunk, lineIndex, order, ci + 1, chunks.length);
|
|
216
|
+
replacements.push(planned.replacement);
|
|
217
|
+
rawRanges.push(planned.rawRange);
|
|
218
|
+
lineIndex = planned.nextLineIndex;
|
|
177
219
|
order += 1;
|
|
178
|
-
rawRanges.push({ index: matchIndex, deleteCount: oldLines.length, insertCount: replacement.length });
|
|
179
|
-
lineIndex = matchIndex + oldLines.length;
|
|
180
220
|
}
|
|
181
221
|
|
|
182
|
-
const output =
|
|
183
|
-
replacements
|
|
184
|
-
.sort((a, b) => b.index - a.index || b.order - a.order)
|
|
185
|
-
.forEach((r) => {
|
|
186
|
-
output.splice(r.index, r.deleteCount, ...r.insert);
|
|
187
|
-
});
|
|
188
|
-
|
|
222
|
+
const output = applyPlannedReplacements(lines, replacements);
|
|
189
223
|
const joined = output.join("\n");
|
|
190
224
|
const normalized = joined === "" ? "" : `${joined}\n`;
|
|
191
225
|
const next = parts.bom + restoreLineEndings(normalized, parts.lineEnding);
|