pi-cohort 7.3.1 → 7.4.1
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 +13 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/runs/background/async-execution.ts +12 -6
- package/src/runs/background/subagent-runner.ts +91 -33
- package/src/runs/foreground/chain-execution.ts +515 -498
- package/src/runs/foreground/execution.ts +24 -5
- package/src/runs/foreground/subagent-executor.ts +10 -9
- package/src/runs/shared/acceptance.ts +4 -2
- package/src/runs/shared/chain-outputs.ts +73 -12
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/single-output.ts +10 -0
- package/src/runs/shared/worktree.ts +32 -0
- package/src/shared/types.ts +4 -0
|
@@ -46,7 +46,7 @@ import { PINNED_AGENT_DIR_OVERRIDE } from "../../shared/agent-dir.ts";
|
|
|
46
46
|
import { createJsonlWriter } from "../../shared/jsonl-writer.ts";
|
|
47
47
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
48
48
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
49
|
-
import { captureSingleOutputSnapshot, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
49
|
+
import { captureSingleOutputSnapshot, formatFileOnlyFailure, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
50
50
|
import {
|
|
51
51
|
buildModelCandidates,
|
|
52
52
|
formatModelAttemptNote,
|
|
@@ -669,7 +669,7 @@ export async function runSync(
|
|
|
669
669
|
dynamic: options.acceptanceContext?.dynamic,
|
|
670
670
|
dynamicGroup: options.acceptanceContext?.dynamicGroup,
|
|
671
671
|
});
|
|
672
|
-
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance);
|
|
672
|
+
const acceptancePrompt = formatAcceptancePrompt(effectiveAcceptance, { structuredOutput: Boolean(options.structuredOutput) });
|
|
673
673
|
const taskWithAcceptance = acceptancePrompt ? `${task}\n${acceptancePrompt}` : task;
|
|
674
674
|
const sessionEnabled = Boolean(options.sessionFile || options.sessionDir) || shareEnabled;
|
|
675
675
|
const skillNames = options.skills ?? agent.skills ?? [];
|
|
@@ -815,6 +815,8 @@ export async function runSync(
|
|
|
815
815
|
} satisfies SingleResult;
|
|
816
816
|
|
|
817
817
|
let postLoopError: unknown;
|
|
818
|
+
let preservedAt: string | undefined;
|
|
819
|
+
let notPreservedReason = "artifact output disabled";
|
|
818
820
|
try {
|
|
819
821
|
result.usage = aggregateUsage;
|
|
820
822
|
result.attemptedModels = attemptedModels.length > 0 ? attemptedModels : undefined;
|
|
@@ -834,7 +836,14 @@ export async function runSync(
|
|
|
834
836
|
if (artifactPathsResult && options.artifactConfig?.enabled !== false) {
|
|
835
837
|
result.artifactPaths = artifactPathsResult;
|
|
836
838
|
if (options.artifactConfig?.includeOutput !== false) {
|
|
837
|
-
|
|
839
|
+
try {
|
|
840
|
+
writeArtifact(artifactPathsResult.outputPath, artifactOutputByResult.get(result) ?? result.finalOutput ?? "");
|
|
841
|
+
preservedAt = artifactPathsResult.outputPath;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
844
|
+
notPreservedReason = `artifact write failed: ${message}`;
|
|
845
|
+
throw error;
|
|
846
|
+
}
|
|
838
847
|
}
|
|
839
848
|
if (options.artifactConfig?.includeMetadata !== false) {
|
|
840
849
|
writeMetadata(artifactPathsResult.metadataPath, {
|
|
@@ -912,9 +921,19 @@ export async function runSync(
|
|
|
912
921
|
: safeMessage;
|
|
913
922
|
result.executionSurface ??= { handle: externalOwner.surface, retained: true };
|
|
914
923
|
}
|
|
915
|
-
} else if (postLoopError) {
|
|
916
|
-
throw postLoopError;
|
|
917
924
|
}
|
|
918
925
|
|
|
926
|
+
if (options.outputMode === "file-only"
|
|
927
|
+
&& (result.exitCode !== 0 || result.outputSaveError)
|
|
928
|
+
&& !blockedLine(result.error ?? "")) {
|
|
929
|
+
result.finalOutput = formatFileOnlyFailure({
|
|
930
|
+
outputPath: options.outputPath,
|
|
931
|
+
saveError: result.outputSaveError,
|
|
932
|
+
...(preservedAt !== undefined ? { preservedAt } : { notPreservedReason }),
|
|
933
|
+
});
|
|
934
|
+
result.truncation = undefined;
|
|
935
|
+
}
|
|
936
|
+
if (!externalOwner && postLoopError) throw postLoopError;
|
|
937
|
+
|
|
919
938
|
return result;
|
|
920
939
|
}
|
|
@@ -39,6 +39,7 @@ import { createForkContextResolver } from "../../shared/fork-context.ts";
|
|
|
39
39
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
40
40
|
import { formatControlNoticeMessage, resolveControlConfig, shouldNotifyControlEvent } from "../shared/subagent-control.ts";
|
|
41
41
|
import { finalizeSingleOutput, injectSingleOutputInstruction, normalizeTopLevelOutput, resolveParallelTaskOutputPath, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
42
|
+
import { blockedLine } from "../shared/completion-guard.ts";
|
|
42
43
|
import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, readStatus, resolveChildCwd } from "../../shared/utils.ts";
|
|
43
44
|
import { resolveSubagentResultStatus } from "../shared/result-children.ts";
|
|
44
45
|
import { buildRevivedAsyncTask, resolveAsyncResumeTarget } from "../background/async-resume.ts";
|
|
@@ -2064,7 +2065,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2064
2065
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
2065
2066
|
|
|
2066
2067
|
const fullOutput = getSingleResultOutput(r);
|
|
2067
|
-
const
|
|
2068
|
+
const displayOutput = r.outputMode === "file-only" ? r.finalOutput : finalizeSingleOutput({
|
|
2068
2069
|
fullOutput,
|
|
2069
2070
|
truncatedOutput: r.truncation?.text,
|
|
2070
2071
|
outputPath,
|
|
@@ -2073,7 +2074,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2073
2074
|
savedPath: r.savedOutputPath,
|
|
2074
2075
|
outputReference: r.outputReference,
|
|
2075
2076
|
saveError: r.outputSaveError,
|
|
2076
|
-
});
|
|
2077
|
+
}).displayOutput;
|
|
2077
2078
|
const details = compactForegroundDetails({
|
|
2078
2079
|
mode: "single",
|
|
2079
2080
|
runId,
|
|
@@ -2096,14 +2097,14 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
2096
2097
|
};
|
|
2097
2098
|
}
|
|
2098
2099
|
|
|
2099
|
-
if (r.exitCode !== 0)
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2100
|
+
if (r.exitCode !== 0) {
|
|
2101
|
+
const text = r.outputMode === "file-only" && !blockedLine(r.error ?? "")
|
|
2102
|
+
? `FAILED (exit code ${r.exitCode})${r.error ? `: ${r.error}` : ""}\n\n${r.finalOutput ?? ""}`
|
|
2103
|
+
: r.error || "Failed";
|
|
2104
|
+
return { content: [{ type: "text", text }], details, isError: true };
|
|
2105
|
+
}
|
|
2105
2106
|
return {
|
|
2106
|
-
content: [{ type: "text", text:
|
|
2107
|
+
content: [{ type: "text", text: displayOutput || "(no output)" }],
|
|
2107
2108
|
details,
|
|
2108
2109
|
};
|
|
2109
2110
|
}
|
|
@@ -238,7 +238,7 @@ export function resolveEffectiveAcceptance(input: {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig): string {
|
|
241
|
+
export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig, options: { structuredOutput: boolean } = { structuredOutput: false }): string {
|
|
242
242
|
if (acceptance.level === "none") return "";
|
|
243
243
|
const lines = [
|
|
244
244
|
"",
|
|
@@ -264,7 +264,9 @@ export function formatAcceptancePrompt(acceptance: ResolvedAcceptanceConfig): st
|
|
|
264
264
|
}
|
|
265
265
|
lines.push(
|
|
266
266
|
"",
|
|
267
|
-
|
|
267
|
+
options.structuredOutput
|
|
268
|
+
? "Write a fenced JSON block tagged `acceptance-report` in your final text message, then call `structured_output` as your final action. Shape:"
|
|
269
|
+
: "Finish with a fenced JSON block tagged `acceptance-report` in this shape:",
|
|
268
270
|
"```acceptance-report",
|
|
269
271
|
JSON.stringify({
|
|
270
272
|
criteriaSatisfied: [{ id: "criterion-1", status: "satisfied", evidence: "specific proof" }],
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
1
2
|
import { isDynamicParallelStep, isParallelStep, type ChainStep, type SequentialStep } from "../../shared/settings.ts";
|
|
2
3
|
import type { ChainOutputMap, ChainOutputMapEntry, SingleResult } from "../../shared/types.ts";
|
|
3
4
|
import { getSingleResultOutput } from "../../shared/utils.ts";
|
|
4
5
|
import { DynamicFanoutError, hasDynamicFanoutFields, type DynamicFanoutConfig, validateDynamicStepShape } from "./dynamic-fanout.ts";
|
|
6
|
+
import { materializeTree } from "./worktree.ts";
|
|
5
7
|
import { validateStopConditions } from "./stop-conditions.ts";
|
|
6
8
|
|
|
7
9
|
const OUTPUT_REF_PATTERN = /\{outputs\.([^}]*)\}/g;
|
|
8
10
|
const SAFE_OUTPUT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
9
11
|
const PATCH_PATH_FIELD = "patchPath";
|
|
10
12
|
|
|
11
|
-
/**
|
|
12
|
-
function parseOutputReference(raw: string): { name: string; field?:
|
|
13
|
+
/** Only text and filesystem fields are valid output references. */
|
|
14
|
+
function parseOutputReference(raw: string): { name: string; field?: "patchPath" | "treePath" } | undefined {
|
|
13
15
|
const dot = raw.indexOf(".");
|
|
14
16
|
const name = dot === -1 ? raw : raw.slice(0, dot);
|
|
15
17
|
if (!SAFE_OUTPUT_NAME_PATTERN.test(name)) return undefined;
|
|
16
18
|
if (dot === -1) return { name };
|
|
17
|
-
|
|
19
|
+
const field = raw.slice(dot + 1);
|
|
20
|
+
return field === PATCH_PATH_FIELD || field === "treePath" ? { name, field } : undefined;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export class ChainOutputValidationError extends Error {}
|
|
@@ -26,10 +29,22 @@ function outputNamesForStep(step: ChainStep): string[] {
|
|
|
26
29
|
return name ? [name] : [];
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
function taskTemplatesForStep(step: ChainStep): string
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
function taskTemplatesForStep(step: ChainStep): Array<{ template: string; cwd: boolean }> {
|
|
33
|
+
const templates: Array<{ template: string; cwd: boolean }> = [];
|
|
34
|
+
const add = (value: { task?: string; label?: string; cwd?: string }) => {
|
|
35
|
+
for (const template of [value.task, value.label]) {
|
|
36
|
+
if (template) templates.push({ template, cwd: false });
|
|
37
|
+
}
|
|
38
|
+
if (value.cwd) templates.push({ template: value.cwd, cwd: true });
|
|
39
|
+
};
|
|
40
|
+
if (isParallelStep(step)) {
|
|
41
|
+
if (step.cwd) templates.push({ template: step.cwd, cwd: true });
|
|
42
|
+
for (const task of step.parallel) add(task);
|
|
43
|
+
} else if (isDynamicParallelStep(step)) {
|
|
44
|
+
add(step);
|
|
45
|
+
add(step.parallel);
|
|
46
|
+
} else add(step);
|
|
47
|
+
return templates;
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
export function validateChainOutputBindings(steps: ChainStep[], dynamicFanoutConfig: DynamicFanoutConfig = {}): void {
|
|
@@ -64,17 +79,23 @@ export function validateChainOutputBindings(steps: ChainStep[], dynamicFanoutCon
|
|
|
64
79
|
}
|
|
65
80
|
seen.add(name);
|
|
66
81
|
}
|
|
67
|
-
for (const template of taskTemplatesForStep(step)) {
|
|
82
|
+
for (const { template, cwd } of taskTemplatesForStep(step)) {
|
|
68
83
|
for (const match of template.matchAll(OUTPUT_REF_PATTERN)) {
|
|
84
|
+
if (cwd && isParallelStep(step) && step.worktree) {
|
|
85
|
+
throw new ChainOutputValidationError(`Chain step ${stepIndex + 1}: cwd output references cannot be combined with worktree: true.`);
|
|
86
|
+
}
|
|
69
87
|
const rawReference = match[0];
|
|
70
88
|
const parsed = parseOutputReference(match[1]!);
|
|
71
89
|
if (!parsed) {
|
|
72
90
|
throw new ChainOutputValidationError(`Invalid chain output reference '${rawReference}' at step ${stepIndex + 1}. Use {outputs.name} with /^[A-Za-z_][A-Za-z0-9_]*$/ names.`);
|
|
73
91
|
}
|
|
92
|
+
if (cwd && parsed.field !== "treePath") {
|
|
93
|
+
throw new ChainOutputValidationError(`Chain step ${stepIndex + 1}: only {outputs.<name>.treePath} may appear in cwd.`);
|
|
94
|
+
}
|
|
74
95
|
if (!available.has(parsed.name)) {
|
|
75
96
|
throw new ChainOutputValidationError(`Unknown chain output reference '${rawReference}' at step ${stepIndex + 1}. Named outputs are only available after producing step/group completes.`);
|
|
76
97
|
}
|
|
77
|
-
if (parsed.field
|
|
98
|
+
if (parsed.field && !worktreeProducers.has(parsed.name)) {
|
|
78
99
|
const producerStep = producerSteps.get(parsed.name)!;
|
|
79
100
|
throw new ChainOutputValidationError(`Chain step ${stepIndex + 1} references ${rawReference} but step ${producerStep + 1} is not a worktree step.`);
|
|
80
101
|
}
|
|
@@ -88,7 +109,7 @@ export function validateChainOutputBindings(steps: ChainStep[], dynamicFanoutCon
|
|
|
88
109
|
}
|
|
89
110
|
}
|
|
90
111
|
|
|
91
|
-
export function resolveOutputReferences(template: string, outputs: ChainOutputMap): string {
|
|
112
|
+
export function resolveOutputReferences(template: string, outputs: ChainOutputMap, trees?: TreeRegistry): string {
|
|
92
113
|
return template.replace(OUTPUT_REF_PATTERN, (rawReference, rawName: string) => {
|
|
93
114
|
const parsed = parseOutputReference(rawName);
|
|
94
115
|
if (!parsed) {
|
|
@@ -96,25 +117,65 @@ export function resolveOutputReferences(template: string, outputs: ChainOutputMa
|
|
|
96
117
|
}
|
|
97
118
|
const entry = outputs[parsed.name];
|
|
98
119
|
if (!entry) throw new ChainOutputValidationError(`Unknown chain output reference '${rawReference}'.`);
|
|
99
|
-
if (parsed.field
|
|
120
|
+
if (parsed.field === "treePath") {
|
|
121
|
+
if (!trees) throw new ChainOutputValidationError(`Output '${parsed.name}' tree registry is unavailable.`);
|
|
122
|
+
return trees.resolve(parsed.name, entry);
|
|
123
|
+
}
|
|
124
|
+
if (!parsed.field) return entry.text;
|
|
100
125
|
if (entry.patchPath) return entry.patchPath;
|
|
101
126
|
const reason = entry.patchError ? `capture failed: ${entry.patchError}` : "no changes";
|
|
102
127
|
throw new ChainOutputValidationError(`Output '${parsed.name}' has no patch (${reason}).`);
|
|
103
128
|
});
|
|
104
129
|
}
|
|
105
130
|
|
|
131
|
+
export class TreeRegistry {
|
|
132
|
+
readonly built = new Map<string, string>();
|
|
133
|
+
readonly created: Array<{ repoCwd: string; dir: string }> = [];
|
|
134
|
+
|
|
135
|
+
private readonly chainDir: string;
|
|
136
|
+
|
|
137
|
+
constructor(chainDir: string) {
|
|
138
|
+
this.chainDir = chainDir;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
resolve(name: string, entry: ChainOutputMapEntry): string {
|
|
142
|
+
if (this.built.has(name)) return this.built.get(name)!;
|
|
143
|
+
if (!entry.patchPath) {
|
|
144
|
+
const reason = entry.patchError ? `capture failed: ${entry.patchError}` : "no changes";
|
|
145
|
+
throw new ChainOutputValidationError(`Output '${name}' has no patch (${reason}).`);
|
|
146
|
+
}
|
|
147
|
+
const dir = path.join(this.chainDir, "worktree-trees", `step-${entry.stepIndex}`, `task-${entry.taskIndex}-${entry.agent}`);
|
|
148
|
+
this.created.push({ repoCwd: entry.repoCwd!, dir });
|
|
149
|
+
try {
|
|
150
|
+
materializeTree({ repoCwd: entry.repoCwd!, baseCommit: entry.baseCommit!, patchPath: entry.patchPath, dir });
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
throw new ChainOutputValidationError(`Output '${name}' tree build failed: ${message.split("\n")[0]}`);
|
|
154
|
+
}
|
|
155
|
+
this.built.set(name, dir);
|
|
156
|
+
entry.treePath = dir;
|
|
157
|
+
return dir;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
106
161
|
function compactStructuredText(value: unknown): string {
|
|
107
162
|
return JSON.stringify(value);
|
|
108
163
|
}
|
|
109
164
|
|
|
110
165
|
export interface OutputEntryPatchInfo {
|
|
166
|
+
taskIndex?: number;
|
|
167
|
+
baseCommit?: string;
|
|
168
|
+
repoCwd?: string;
|
|
111
169
|
patchPath?: string;
|
|
112
170
|
patchError?: string;
|
|
113
171
|
}
|
|
114
172
|
|
|
115
|
-
function patchFields(patch: OutputEntryPatchInfo | undefined):
|
|
173
|
+
function patchFields(patch: OutputEntryPatchInfo | undefined): OutputEntryPatchInfo {
|
|
116
174
|
if (!patch) return {};
|
|
117
175
|
return {
|
|
176
|
+
...(patch.taskIndex !== undefined ? { taskIndex: patch.taskIndex } : {}),
|
|
177
|
+
...(patch.baseCommit !== undefined ? { baseCommit: patch.baseCommit } : {}),
|
|
178
|
+
...(patch.repoCwd !== undefined ? { repoCwd: patch.repoCwd } : {}),
|
|
118
179
|
...(patch.patchPath ? { patchPath: patch.patchPath } : {}),
|
|
119
180
|
...(patch.patchError ? { patchError: patch.patchError } : {}),
|
|
120
181
|
};
|
|
@@ -106,6 +106,16 @@ export function formatSavedOutputReference(savedPath: string, fullOutput: string
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
export function formatFileOnlyFailure(params: {
|
|
110
|
+
outputPath?: string;
|
|
111
|
+
saveError?: string;
|
|
112
|
+
} & ({ preservedAt: string; notPreservedReason?: never } | { preservedAt?: never; notPreservedReason: string })): string {
|
|
113
|
+
const pointer = params.preservedAt !== undefined
|
|
114
|
+
? `Full reply preserved at ${params.preservedAt}`
|
|
115
|
+
: `Full reply not preserved (${params.notPreservedReason})`;
|
|
116
|
+
return params.saveError ? `Output file error: ${params.outputPath}\n${params.saveError}\n\n${pointer}` : pointer;
|
|
117
|
+
}
|
|
118
|
+
|
|
109
119
|
export function validateFileOnlyOutputMode(outputMode: OutputMode | undefined, outputPath: string | undefined, context: string): string | undefined {
|
|
110
120
|
if (outputMode === "file-only" && !outputPath) {
|
|
111
121
|
return `${context} sets outputMode: "file-only" but does not configure an output file. Set output to a path or use outputMode: "inline".`;
|
|
@@ -557,6 +557,38 @@ export function diffWorktrees(setup: WorktreeSetup, agents: string[], diffsDir:
|
|
|
557
557
|
return diffs;
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
export function materializeTree({ repoCwd, baseCommit, patchPath, dir }: {
|
|
561
|
+
repoCwd: string;
|
|
562
|
+
baseCommit: string;
|
|
563
|
+
patchPath: string;
|
|
564
|
+
dir: string;
|
|
565
|
+
}): string {
|
|
566
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
567
|
+
const add = runGit(repoCwd, ["worktree", "add", "--detach", dir, baseCommit]);
|
|
568
|
+
if (add.status !== 0) {
|
|
569
|
+
throw new Error(add.stderr.split("\n").map((line) => line.trim()).find(Boolean) || "git worktree add failed");
|
|
570
|
+
}
|
|
571
|
+
const apply = runGit(dir, ["apply", "--index", patchPath]);
|
|
572
|
+
if (apply.status !== 0) {
|
|
573
|
+
throw new Error(apply.stderr.split("\n").map((line) => line.trim()).find(Boolean) || "git apply failed");
|
|
574
|
+
}
|
|
575
|
+
return dir;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function removeTrees(entries: Array<{ repoCwd: string; dir: string }>): void {
|
|
579
|
+
for (const { repoCwd, dir } of entries) {
|
|
580
|
+
if (!fs.existsSync(dir)) continue;
|
|
581
|
+
try { runGitChecked(repoCwd, ["worktree", "remove", "--force", dir]); } catch (error) {
|
|
582
|
+
console.warn(`Failed to remove tree '${dir}': ${error instanceof Error ? error.message : String(error)}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
for (const repoCwd of new Set(entries.map((entry) => entry.repoCwd))) {
|
|
586
|
+
try { runGitChecked(repoCwd, ["worktree", "prune"]); } catch (error) {
|
|
587
|
+
console.warn(`Failed to prune worktrees in '${repoCwd}': ${error instanceof Error ? error.message : String(error)}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
560
592
|
export function cleanupWorktrees(setup: WorktreeSetup): void {
|
|
561
593
|
for (let index = setup.worktrees.length - 1; index >= 0; index--) {
|
|
562
594
|
cleanupSingleWorktree(setup.cwd, setup.worktrees[index]!);
|
package/src/shared/types.ts
CHANGED