@agentproto/worktree 0.9.0 → 0.12.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/dist/bin/worktree-agent.mjs +3 -3
- package/dist/{chunk-6EV7WP2U.mjs → chunk-5SDH2V6M.mjs} +4 -4
- package/dist/{chunk-6EV7WP2U.mjs.map → chunk-5SDH2V6M.mjs.map} +1 -1
- package/dist/{chunk-IKCBL2UN.mjs → chunk-EK5BH2YL.mjs} +5 -3
- package/dist/chunk-EK5BH2YL.mjs.map +1 -0
- package/dist/{chunk-KRQFVJPK.mjs → chunk-EVHLUKC7.mjs} +155 -22
- package/dist/chunk-EVHLUKC7.mjs.map +1 -0
- package/dist/index.d.ts +177 -9
- package/dist/index.mjs +89 -21
- package/dist/index.mjs.map +1 -1
- package/dist/provider/index.mjs +2 -2
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.mjs +1 -1
- package/package.json +3 -3
- package/dist/chunk-IKCBL2UN.mjs.map +0 -1
- package/dist/chunk-KRQFVJPK.mjs.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -245,6 +245,86 @@ declare function execArgv(command: string, args: readonly string[], cwd: string,
|
|
|
245
245
|
declare function execShell(cmd: string, cwd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
246
246
|
declare function execGit(repoRoot: string, args: readonly string[]): Promise<ExecResult>;
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Fast worktree removal: `rename` to a same-volume trash dir + `git worktree
|
|
250
|
+
* prune`, with the bytes deleted by a detached background child.
|
|
251
|
+
*
|
|
252
|
+
* WHY THIS EXISTS: `git worktree remove` deletes the worktree tree in
|
|
253
|
+
* process, one file at a time — on a pnpm worktree (its own `node_modules` /
|
|
254
|
+
* `.pnpm-vstores`, hundreds of thousands of files) on an external SSD that
|
|
255
|
+
* measured ~10 minutes PER WORKTREE. Every part of the removal that callers
|
|
256
|
+
* actually wait on is instant:
|
|
257
|
+
*
|
|
258
|
+
* 1. `rename` the worktree directory to `<dirname(path)>/.trash/<base>-
|
|
259
|
+
* <timestamp>` — same volume by construction, so it's a metadata
|
|
260
|
+
* operation, not a copy. `git worktree remove`'s per-file refusals have
|
|
261
|
+
* already run (see `removeWorktreeFast`), so at this point the bytes
|
|
262
|
+
* are authorized to die; only the cost is being moved.
|
|
263
|
+
* 2. `git worktree prune` — git forgets the (now missing) registration.
|
|
264
|
+
* The worktree disappears from `git worktree list` immediately.
|
|
265
|
+
* 3. `rm -rf` on the trash dir runs in a DETACHED child
|
|
266
|
+
* (`spawn("rm", ["-rf", trashDir], { detached: true, stdio: "ignore" })`
|
|
267
|
+
* + `.unref()`) — the parent never waits for the hundreds of thousands
|
|
268
|
+
* of unlinks, and survives its own exit. The next `gc` apply also
|
|
269
|
+
* sweeps stale `.trash` dirs (`sweepWorktreeTrash`) so nothing leaks
|
|
270
|
+
* even if a child is killed mid-delete.
|
|
271
|
+
*
|
|
272
|
+
* SAFETY — the plain/forced `git worktree remove` semantics this must not
|
|
273
|
+
* weaken, verified empirically against git (see the brief):
|
|
274
|
+
* - A non-force removal must refuse a dirty tree: the caller passes
|
|
275
|
+
* `force: false` only for a tree already checked clean, and
|
|
276
|
+
* `removeWorktreeFast` re-checks `git status --porcelain` itself and
|
|
277
|
+
* throws (leaving the worktree in place) rather than renaming dirt away.
|
|
278
|
+
* - A forced removal is only ever passed after a salvage snapshot or an
|
|
279
|
+
* explicit user-granted discard flag upstream — this module doesn't
|
|
280
|
+
* loosen that; it just performs the same destruction instantly.
|
|
281
|
+
*
|
|
282
|
+
* Fallback: if the `rename` fails for any reason (cross-device, permissions,
|
|
283
|
+
* the trash parent unmountable), the function falls back to today's plain
|
|
284
|
+
* `git worktree remove [--force]` — behavior identical to before, just slow.
|
|
285
|
+
*/
|
|
286
|
+
/** Directory (inside the worktrees root's repo bucket) background-delete leftovers land in. */
|
|
287
|
+
declare const WORKTREE_TRASH_DIRNAME = ".trash";
|
|
288
|
+
interface RemoveWorktreeFastOptions {
|
|
289
|
+
/**
|
|
290
|
+
* `false` (default): re-verify the tree is clean via `git status
|
|
291
|
+
* --porcelain` and refuse (throw) on any dirt — the same refusal a plain
|
|
292
|
+
* `git worktree remove` performs. `true`: skip the cleanliness gate,
|
|
293
|
+
* exactly like `git worktree remove --force` — only pass this when dirt
|
|
294
|
+
* has been authorized (salvage snapshot durable / discard flags granted)
|
|
295
|
+
* upstream.
|
|
296
|
+
*/
|
|
297
|
+
force?: boolean;
|
|
298
|
+
/**
|
|
299
|
+
* Injectable clock for the trash dir's timestamp suffix — tests freeze it
|
|
300
|
+
* so the dir name is deterministic. Defaults to `Date.now`.
|
|
301
|
+
*/
|
|
302
|
+
now?: () => number;
|
|
303
|
+
/**
|
|
304
|
+
* Test seam — overrides the detached `rm -rf` child (e.g. an in-process
|
|
305
|
+
* spy). Receives the trash dir path. Default: a detached
|
|
306
|
+
* `spawn("rm", ["-rf", …])` the parent never waits on.
|
|
307
|
+
*/
|
|
308
|
+
spawnRemoval?: (trashDir: string) => void;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Remove a linked git worktree instantly: validate → rename to trash →
|
|
312
|
+
* prune → delete the trash in the background. Throws the same *shape* of
|
|
313
|
+
* error `execGit` would on refusal (message carries git's own stderr where
|
|
314
|
+
* git produced one) so callers' existing error handling keeps working.
|
|
315
|
+
*/
|
|
316
|
+
declare function removeWorktreeFast(repoRoot: string, path: string, options?: RemoveWorktreeFastOptions): Promise<void>;
|
|
317
|
+
/**
|
|
318
|
+
* Delete every leftover `.trash/*` dir under `root` in the background — the
|
|
319
|
+
* sweep that keeps rename-removal self-cleaning when a detached `rm` child
|
|
320
|
+
* was killed before finishing (or a host rebooted mid-delete). Called at the
|
|
321
|
+
* start of a `gc` apply, where seconds of stale bytes are harmless. Missing
|
|
322
|
+
* root is a no-op, not an error.
|
|
323
|
+
*/
|
|
324
|
+
declare function sweepWorktreeTrash(root: string, options?: {
|
|
325
|
+
spawnRemoval?: (dir: string) => void;
|
|
326
|
+
}): void;
|
|
327
|
+
|
|
248
328
|
/** Compile a `/`-separated glob (supporting `*`, `?`, `**`) into an anchored regex. */
|
|
249
329
|
declare function globToRegExp(pattern: string): RegExp;
|
|
250
330
|
/** Expand a glob against `root`, returning root-relative posix paths of matching files. */
|
|
@@ -413,11 +493,29 @@ interface HookRun {
|
|
|
413
493
|
command: string;
|
|
414
494
|
result: ExecResult;
|
|
415
495
|
}
|
|
416
|
-
/** Raised when a `setup` hook exits non-zero — carries the captured output
|
|
496
|
+
/** Raised when a `setup` hook exits non-zero — carries the captured output
|
|
497
|
+
* (see {@link result}) plus a short, noise-filtered tail in `message` for
|
|
498
|
+
* callers that only surface a single string (e.g.
|
|
499
|
+
* `SessionDescriptor.lastError`).
|
|
500
|
+
*
|
|
501
|
+
* Incident (2026-09-27): three back-to-back `agent_start` worktree
|
|
502
|
+
* provisions all failed at `pnpm build`, but every `lastError` showed only
|
|
503
|
+
* the `.npmrc`/`NODE_AUTH_TOKEN` warning + turbo's one-line run summary —
|
|
504
|
+
* never the actual tsup/tsc error. Root cause: the OLD message used
|
|
505
|
+
* `(run.result.stderr || run.result.stdout).trim()` — since that WARN line
|
|
506
|
+
* (and turbo's failure banner) land on stderr, ANY non-empty stderr threw
|
|
507
|
+
* away stdout WHOLESALE, and stdout is exactly where a failing package's
|
|
508
|
+
* own build error prints. Fixed by combining both streams, filtering only
|
|
509
|
+
* the known-noise lines (not a whole stream), and keeping a real tail of
|
|
510
|
+
* the combined output instead of picking one stream whole. */
|
|
417
511
|
declare class HookError extends Error {
|
|
418
512
|
readonly command: string;
|
|
419
513
|
readonly result: ExecResult;
|
|
420
|
-
|
|
514
|
+
/** Path to the full, unfiltered stdout+stderr log for this and every
|
|
515
|
+
* other hook command in the same run, when the caller asked for one to
|
|
516
|
+
* be persisted (see {@link runSetup}'s `opts.logPath`). */
|
|
517
|
+
readonly logPath?: string;
|
|
518
|
+
constructor(phase: "setup" | "teardown", run: HookRun, logPath?: string);
|
|
421
519
|
}
|
|
422
520
|
/**
|
|
423
521
|
* Run the `worktree.setup` hooks sequentially in the worktree, with the
|
|
@@ -429,14 +527,41 @@ declare class HookError extends Error {
|
|
|
429
527
|
* setup hook that doesn't use turbo simply never reads the var. The first
|
|
430
528
|
* non-zero exit throws {@link HookError} (setup failure must fail
|
|
431
529
|
* provisioning). No-op when the repo declares no setup.
|
|
530
|
+
*
|
|
531
|
+
* `opts.logPath`, when given, gets every command's FULL stdout+stderr
|
|
532
|
+
* appended (success or failure) — see {@link appendHookLog}. Callers that
|
|
533
|
+
* know the failure will surface through a single short string (the daemon's
|
|
534
|
+
* `agent_start` path, via `SessionDescriptor.lastError`) should pass a path
|
|
535
|
+
* under that session's own directory so the full log outlives a
|
|
536
|
+
* subsequently-reclaimed worktree.
|
|
537
|
+
*
|
|
538
|
+
* `opts.retryOnFailure` (default `false`) re-runs a failing command exactly
|
|
539
|
+
* ONCE before giving up — a COMPLEMENT to, never a substitute for, an actual
|
|
540
|
+
* root-cause fix. It exists for the unattended `agent_start` path: a caller
|
|
541
|
+
* with no human watching has no way to retry a transient failure itself
|
|
542
|
+
* short of minting an entirely new worktree (a fresh `git worktree add`,
|
|
543
|
+
* paying the full setup cost again) — see `session-spawn.ts`'s
|
|
544
|
+
* `worktreeSetupLogPath` call sites, which both opt in. Left `false` for the
|
|
545
|
+
* default (`agentproto worktree new`, and this function's own default): an
|
|
546
|
+
* interactive human watching a genuine, deterministic build failure gets it
|
|
547
|
+
* on the first try instead of waiting through an identical, doomed rerun.
|
|
548
|
+
* Both attempts are logged (when `logPath` is given); the thrown
|
|
549
|
+
* `HookError` carries the RETRY's own result, since that's the one the
|
|
550
|
+
* caller is actually giving up on.
|
|
432
551
|
*/
|
|
433
|
-
declare function runSetup(config: AgentprotoConfig, ctx: WorktreeEnvContext
|
|
552
|
+
declare function runSetup(config: AgentprotoConfig, ctx: WorktreeEnvContext, opts?: {
|
|
553
|
+
logPath?: string;
|
|
554
|
+
retryOnFailure?: boolean;
|
|
555
|
+
}): Promise<HookRun[]>;
|
|
434
556
|
/**
|
|
435
557
|
* Run the `worktree.teardown` hooks sequentially. Unlike setup, a failing
|
|
436
558
|
* teardown hook does NOT throw — cleanup must proceed — so failures are
|
|
437
559
|
* returned for the caller to log. No-op when no teardown is declared.
|
|
560
|
+
* `opts.logPath` mirrors {@link runSetup}'s — best-effort, full output.
|
|
438
561
|
*/
|
|
439
|
-
declare function runTeardown(config: AgentprotoConfig, ctx: WorktreeEnvContext
|
|
562
|
+
declare function runTeardown(config: AgentprotoConfig, ctx: WorktreeEnvContext, opts?: {
|
|
563
|
+
logPath?: string;
|
|
564
|
+
}): Promise<HookRun[]>;
|
|
440
565
|
|
|
441
566
|
/**
|
|
442
567
|
* The proxy routing table: `<hostname>` → local service port.
|
|
@@ -849,6 +974,15 @@ type TreeState = {
|
|
|
849
974
|
* a liveness-independent "someone touched this a moment ago" signal.
|
|
850
975
|
*/
|
|
851
976
|
newestMtimeMs: number | null;
|
|
977
|
+
/**
|
|
978
|
+
* Set to `.plans/` when every non-noise dirty path starts with it —
|
|
979
|
+
* i.e. the ENTIRE tree's dirt lives under `.plans/`. `undefined`
|
|
980
|
+
* otherwise (mixed dirt, or dirt outside `.plans/` entirely). Feeds
|
|
981
|
+
* gc's plans-only salvage rule (`classifyForGc`, gc.ts): `.plans/`
|
|
982
|
+
* holds the user's plan files, never to be discarded, but also never a
|
|
983
|
+
* reason to hold a no-commit worktree forever.
|
|
984
|
+
*/
|
|
985
|
+
onlyUnder?: string;
|
|
852
986
|
};
|
|
853
987
|
interface ComputeTreeStateOptions {
|
|
854
988
|
/**
|
|
@@ -1282,10 +1416,21 @@ interface ClassifyForGcOptions {
|
|
|
1282
1416
|
}
|
|
1283
1417
|
/**
|
|
1284
1418
|
* gc's own classifier, layered on top of `classify` (status.ts) rather than
|
|
1285
|
-
* reimplementing it — reuse first (AGENTS.md, PLAN.md §0.6).
|
|
1286
|
-
* `
|
|
1287
|
-
*
|
|
1288
|
-
*
|
|
1419
|
+
* reimplementing it — reuse first (AGENTS.md, PLAN.md §0.6). `classify`
|
|
1420
|
+
* itself never lets a `fresh` worktree salvage, clean or not — that
|
|
1421
|
+
* invariant is load-bearing for `ls --status` and every other caller (see
|
|
1422
|
+
* its 2026-07-15 note) and stays untouched here. What `classify` cannot know
|
|
1423
|
+
* is gc-specific archival policy; two overrides live here instead:
|
|
1424
|
+
* - `--include-detached`: a clean, idle DETACHED worktree moves `hold` →
|
|
1425
|
+
* `reclaim`.
|
|
1426
|
+
* - the "plans-only" rule below: a no-commit worktree (`fresh`, or
|
|
1427
|
+
* `detached` when `includeDetached` is set — same gate, since a detached
|
|
1428
|
+
* HEAD's commit safety is exactly as unproven either way) whose ONLY
|
|
1429
|
+
* dirt is untracked files under `.plans/` moves `hold` → `salvage`, so
|
|
1430
|
+
* `--salvage-dirty` archives the plan files (never discards them) instead
|
|
1431
|
+
* of holding the worktree forever. Narrow on purpose: any OTHER dirt
|
|
1432
|
+
* (even one non-`.plans/` file, or a tracked/staged change) leaves this
|
|
1433
|
+
* untouched and `classify`'s ordinary `hold` stands.
|
|
1289
1434
|
*/
|
|
1290
1435
|
declare function classifyForGc(tree: TreeState, integration: IntegrationState, liveness: LivenessState, options?: ClassifyForGcOptions): GcClass;
|
|
1291
1436
|
/**
|
|
@@ -1453,6 +1598,16 @@ interface ApplyGcOptions {
|
|
|
1453
1598
|
protectedPaths?: string[];
|
|
1454
1599
|
/** See `PlanGcInput.noisePaths`. */
|
|
1455
1600
|
noisePaths?: readonly string[];
|
|
1601
|
+
/**
|
|
1602
|
+
* The bucket directory this repo's worktrees are provisioned under — the
|
|
1603
|
+
* same value `planGc` takes. When passed, apply first fires
|
|
1604
|
+
* `sweepWorktreeTrash(worktreesRoot)` (fast-remove.ts): every leftover
|
|
1605
|
+
* `.trash/*` dir left behind by an interrupted background delete is
|
|
1606
|
+
* handed to a fresh detached `rm -rf` child, so a killed mid-delete
|
|
1607
|
+
* removal (crash, reboot) still converges — seconds of stale bytes at
|
|
1608
|
+
* apply time are harmless, and the sweep never blocks on the deletes.
|
|
1609
|
+
*/
|
|
1610
|
+
worktreesRoot?: string;
|
|
1456
1611
|
}
|
|
1457
1612
|
type GcApplyOutcome = {
|
|
1458
1613
|
path: string;
|
|
@@ -1694,6 +1849,8 @@ interface BranchGcPlan {
|
|
|
1694
1849
|
generatedAt: string;
|
|
1695
1850
|
/** Refs of OTHER configured remotes: never classified, never touched. */
|
|
1696
1851
|
otherRemoteRefs: number;
|
|
1852
|
+
/** true = `git fetch --prune` of the base remote succeeded; false = it failed; null = skipped (opt-out or no remote). */
|
|
1853
|
+
fetched: boolean | null;
|
|
1697
1854
|
entries: BranchGcPlanEntry[];
|
|
1698
1855
|
}
|
|
1699
1856
|
/** Base content index, built once per base: path→blob, every blob, every basename. */
|
|
@@ -1713,6 +1870,13 @@ interface LadderContext {
|
|
|
1713
1870
|
* plan's anchor detection asks the same question of every tip) — saves
|
|
1714
1871
|
* the ladder's first `merge-base` per tip. */
|
|
1715
1872
|
related?: ReadonlyMap<string, boolean>;
|
|
1873
|
+
/**
|
|
1874
|
+
* Memoized set of every commit reachable from `cmp` (at most two distinct
|
|
1875
|
+
* values: `baseSha` and the anchor) — ONE `git rev-list <cmp>` replaces one
|
|
1876
|
+
* `merge-base --is-ancestor` per tip. `null` above 500 000 commits (or on a
|
|
1877
|
+
* git failure) = fall back to the per-tip call.
|
|
1878
|
+
*/
|
|
1879
|
+
ancestorsOf?: (cmp: string) => Promise<Set<string> | null>;
|
|
1716
1880
|
}
|
|
1717
1881
|
declare function createLadderContext(repoRoot: string, baseRef: string, anchor: string | null, related?: ReadonlyMap<string, boolean>): Promise<LadderContext>;
|
|
1718
1882
|
/**
|
|
@@ -1760,6 +1924,8 @@ interface PlanBranchGcInput {
|
|
|
1760
1924
|
nowMs?: number;
|
|
1761
1925
|
/** Parallel ladder runs. Default 8. */
|
|
1762
1926
|
concurrency?: number;
|
|
1927
|
+
/** Run `git fetch --prune <baseRemote>` before classifying. Default true. */
|
|
1928
|
+
fetch?: boolean;
|
|
1763
1929
|
}
|
|
1764
1930
|
/** The dry-run plan: classify every ref in `scopes`, mutate nothing. */
|
|
1765
1931
|
declare function planBranchGc(input: PlanBranchGcInput): Promise<BranchGcPlan>;
|
|
@@ -1873,6 +2039,8 @@ interface ApplyBranchGcOptions {
|
|
|
1873
2039
|
nowMs?: number;
|
|
1874
2040
|
/** Remote refs per `git push --delete`. Default 50. */
|
|
1875
2041
|
remoteBatchSize?: number;
|
|
2042
|
+
/** Run `git fetch --prune <baseRemote>` before re-deriving entries. Default true. */
|
|
2043
|
+
fetch?: boolean;
|
|
1876
2044
|
}
|
|
1877
2045
|
interface BranchGcApplyResult {
|
|
1878
2046
|
outcomes: BranchGcApplyOutcome[];
|
|
@@ -1989,4 +2157,4 @@ declare function recordBranchVerdict(input: {
|
|
|
1989
2157
|
now?: () => string;
|
|
1990
2158
|
}): Promise<BranchVerdictRecord>;
|
|
1991
2159
|
|
|
1992
|
-
export { type AgentprotoConfig, type ApplyBranchGcOptions, type ApplyGcOptions, BRANCH_GC_SCOPES, BRANCH_GC_STATE_DIR, BRANCH_TRIAGE_VERDICTS, BRANCH_VERDICTS_PATH, type BaseDivergence, type BranchCoverage, type BranchGcApplyOutcome, type BranchGcApplyResult, type BranchGcClass, type BranchGcHoldReason, type BranchGcPlan, type BranchGcPlanEntry, type BranchGcReclaimReason, type BranchGcRestoreEntry, type BranchGcRestoreLog, type BranchGcSummary, type BranchHistory, type BranchPushState, type BranchRef, type BranchRefKind, type BranchReviewCandidate, type BranchReviewQueue, type BranchStatus, type BranchTriageVerdict, type BranchVerdict, BranchVerdictError, type BranchVerdictRecord, type BranchVerdictStore, type BranchVerdictSummary, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeTreeStateOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_BRANCH_GC_MIN_AGE_DAYS, DEFAULT_GC_NOISE_PATHS, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileBranchVerdictStore, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcHoldReason, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, HookError, type HookRun, type HostnameParts, type InBaseReclaimReason, InMemoryBranchVerdictStore, InMemoryVerdictMemoStore, type IntegrationState, type LadderContext, type ListWorktreeStatusesInput, type LivenessState, type NamedScript, type PeerService, type PlanBranchGcInput, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TipClassification, type TreeIndex, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyBranchGc, applyGc, branchReviewQueue, branchVerdictSchema, classify, classifyForGc, classifyTip, classifyTipAgainstBase, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createLadderContext, createProxyServer, detectAnchor, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, peerEnv, peerEnvKey, planBranchGc, planGc, readBranchGcRestoreLog, readSessionsRegistry, readWorktreeMarker, reclaimOneWorktree, reconcileIntegration, recordBranchVerdict, repoLabel, resolveGcClass, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, summarizeBranchGcPlan, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
|
2160
|
+
export { type AgentprotoConfig, type ApplyBranchGcOptions, type ApplyGcOptions, BRANCH_GC_SCOPES, BRANCH_GC_STATE_DIR, BRANCH_TRIAGE_VERDICTS, BRANCH_VERDICTS_PATH, type BaseDivergence, type BranchCoverage, type BranchGcApplyOutcome, type BranchGcApplyResult, type BranchGcClass, type BranchGcHoldReason, type BranchGcPlan, type BranchGcPlanEntry, type BranchGcReclaimReason, type BranchGcRestoreEntry, type BranchGcRestoreLog, type BranchGcSummary, type BranchHistory, type BranchPushState, type BranchRef, type BranchRefKind, type BranchReviewCandidate, type BranchReviewQueue, type BranchStatus, type BranchTriageVerdict, type BranchVerdict, BranchVerdictError, type BranchVerdictRecord, type BranchVerdictStore, type BranchVerdictSummary, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeTreeStateOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_BRANCH_GC_MIN_AGE_DAYS, DEFAULT_GC_NOISE_PATHS, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileBranchVerdictStore, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcHoldReason, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, HookError, type HookRun, type HostnameParts, type InBaseReclaimReason, InMemoryBranchVerdictStore, InMemoryVerdictMemoStore, type IntegrationState, type LadderContext, type ListWorktreeStatusesInput, type LivenessState, type NamedScript, type PeerService, type PlanBranchGcInput, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type RemoveWorktreeFastOptions, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TipClassification, type TreeIndex, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyBranchGc, applyGc, branchReviewQueue, branchVerdictSchema, classify, classifyForGc, classifyTip, classifyTipAgainstBase, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createLadderContext, createProxyServer, detectAnchor, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, peerEnv, peerEnvKey, planBranchGc, planGc, readBranchGcRestoreLog, readSessionsRegistry, readWorktreeMarker, reclaimOneWorktree, reconcileIntegration, recordBranchVerdict, removeWorktreeFast, repoLabel, resolveGcClass, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, summarizeBranchGcPlan, sweepWorktreeTrash, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-
|
|
1
|
+
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-5SDH2V6M.mjs';
|
|
2
2
|
import './chunk-E3EM24BS.mjs';
|
|
3
|
-
import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, execGit, worktreeProvider } from './chunk-
|
|
4
|
-
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WorktreeNotRemovableError, allocatePort, classify, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, worktreeProvider, writeWorktreeMarker } from './chunk-
|
|
5
|
-
import { cleanupWorktreeTool } from './chunk-
|
|
6
|
-
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-
|
|
3
|
+
import { computeProvenance, execArgv, classify, computeWorktreeStatus, sweepWorktreeTrash, listGitWorktrees, execGit, worktreeProvider } from './chunk-EVHLUKC7.mjs';
|
|
4
|
+
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WORKTREE_TRASH_DIRNAME, WorktreeNotRemovableError, allocatePort, classify, computeBaseDivergence, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, removeWorktreeFast, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, sweepWorktreeTrash, worktreeProvider, writeWorktreeMarker } from './chunk-EVHLUKC7.mjs';
|
|
5
|
+
import { cleanupWorktreeTool } from './chunk-EK5BH2YL.mjs';
|
|
6
|
+
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-EK5BH2YL.mjs';
|
|
7
7
|
import { mkdir, copyFile, readFile, writeFile, open, rename, readdir, realpath, rm, stat } from 'fs/promises';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { resolve, join, dirname, basename, sep, relative, isAbsolute } from 'path';
|
|
@@ -507,12 +507,28 @@ async function createLadderContext(repoRoot, baseRef, anchor, related) {
|
|
|
507
507
|
const baseSha = (await gitOk(repoRoot, ["rev-parse", "--verify", `${baseRef}^{commit}`])).trim();
|
|
508
508
|
const baseTree = (await gitOk(repoRoot, ["rev-parse", `${baseSha}^{tree}`])).trim();
|
|
509
509
|
let idx = null;
|
|
510
|
+
const ancestors = /* @__PURE__ */ new Map();
|
|
511
|
+
const ancestorsOf = (cmp) => {
|
|
512
|
+
let p = ancestors.get(cmp);
|
|
513
|
+
if (!p) {
|
|
514
|
+
p = (async () => {
|
|
515
|
+
const res = await git(repoRoot, ["rev-list", cmp]);
|
|
516
|
+
if (res.exitCode !== 0) return null;
|
|
517
|
+
const shas = res.stdout.split("\n");
|
|
518
|
+
if (shas.length > 5e5) return null;
|
|
519
|
+
return new Set(shas.filter(Boolean));
|
|
520
|
+
})();
|
|
521
|
+
ancestors.set(cmp, p);
|
|
522
|
+
}
|
|
523
|
+
return p;
|
|
524
|
+
};
|
|
510
525
|
return {
|
|
511
526
|
repoRoot,
|
|
512
527
|
baseSha,
|
|
513
528
|
baseTree,
|
|
514
529
|
anchor,
|
|
515
530
|
index: () => idx ??= indexTree(repoRoot, baseSha),
|
|
531
|
+
ancestorsOf,
|
|
516
532
|
...related ? { related } : {}
|
|
517
533
|
};
|
|
518
534
|
}
|
|
@@ -580,7 +596,9 @@ async function classifyTip(ctx, tip) {
|
|
|
580
596
|
if (!related && !anchor) return { status: "unmerged", history: "unrelated", conflicts: true, ahead: null, behind: null };
|
|
581
597
|
const history = related ? "current" : "pre-rewrite";
|
|
582
598
|
const cmp = related ? baseSha : anchor;
|
|
583
|
-
|
|
599
|
+
const ancestors = ctx.ancestorsOf ? await ctx.ancestorsOf(cmp) : null;
|
|
600
|
+
const isAncestor = ancestors ? ancestors.has(tip) : (await git(repoRoot, ["merge-base", "--is-ancestor", tip, cmp])).exitCode === 0;
|
|
601
|
+
if (isAncestor) {
|
|
584
602
|
return { status: "merged", history, ahead: 0, behind: null };
|
|
585
603
|
}
|
|
586
604
|
const counts = (await gitOk(repoRoot, ["rev-list", "--left-right", "--count", `${cmp}...${tip}`])).trim().split(/\s+/);
|
|
@@ -736,12 +754,19 @@ async function pushStateOf(b, env) {
|
|
|
736
754
|
if (b.kind === "local") {
|
|
737
755
|
if (env.remoteShas.has(b.sha)) return "same-tip-on-remote";
|
|
738
756
|
if (env.remote) {
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
757
|
+
const reachable = env.remoteReachable ? await env.remoteReachable() : null;
|
|
758
|
+
if (reachable) {
|
|
759
|
+
if (reachable.has(b.sha)) return "contained-in-remote";
|
|
760
|
+
} else {
|
|
761
|
+
const contained = await git(repoRoot, ["for-each-ref", "--contains", b.sha, "--format=x", `refs/remotes/${env.remote}`]);
|
|
762
|
+
if (contained.stdout.trim()) return "contained-in-remote";
|
|
763
|
+
}
|
|
741
764
|
}
|
|
742
765
|
return env.remoteNames.has(b.name) ? "diverged-from-remote" : "local-only";
|
|
743
766
|
}
|
|
744
767
|
if (b.kind === "orphan") {
|
|
768
|
+
const reachable = env.localOrRemoteReachable ? await env.localOrRemoteReachable() : null;
|
|
769
|
+
if (reachable) return reachable.has(b.sha) ? "contained-elsewhere" : "only-copy";
|
|
745
770
|
const scopes = ["refs/heads", ...env.remote ? [`refs/remotes/${env.remote}`] : []];
|
|
746
771
|
const contained = await git(repoRoot, ["for-each-ref", "--contains", b.sha, "--format=x", ...scopes]);
|
|
747
772
|
return contained.stdout.trim() ? "contained-elsewhere" : "only-copy";
|
|
@@ -797,6 +822,10 @@ async function classifyRef(b, env) {
|
|
|
797
822
|
async function snapshot(input) {
|
|
798
823
|
const remotes = await configuredRemotes(input.repoRoot);
|
|
799
824
|
const remote = baseRemoteOf(input.base, remotes);
|
|
825
|
+
let fetched = null;
|
|
826
|
+
if (remote && input.fetch !== false) {
|
|
827
|
+
fetched = (await git(input.repoRoot, ["fetch", "--prune", "--quiet", remote])).exitCode === 0;
|
|
828
|
+
}
|
|
800
829
|
const [{ refs, otherRemoteRefs }, worktrees, prs, mergedPrs] = await Promise.all([
|
|
801
830
|
listBranchRefs(input.repoRoot, remote, remotes),
|
|
802
831
|
worktreeBranches(input.repoRoot),
|
|
@@ -811,9 +840,20 @@ async function snapshot(input) {
|
|
|
811
840
|
else anchor = null;
|
|
812
841
|
const ctx = await createLadderContext(input.repoRoot, baseSha, anchor, related);
|
|
813
842
|
const remoteRefs = refs.filter((r) => r.kind === "remote");
|
|
843
|
+
const MAX_REACHABLE = 5e5;
|
|
844
|
+
const revListSet = async (args) => {
|
|
845
|
+
const res = await git(input.repoRoot, ["rev-list", ...args]);
|
|
846
|
+
if (res.exitCode !== 0) return null;
|
|
847
|
+
const shas = res.stdout.split("\n");
|
|
848
|
+
if (shas.length > MAX_REACHABLE) return null;
|
|
849
|
+
return new Set(shas.filter(Boolean));
|
|
850
|
+
};
|
|
851
|
+
let remoteReachableP = null;
|
|
852
|
+
let localOrRemoteP = null;
|
|
814
853
|
return {
|
|
815
854
|
refs,
|
|
816
855
|
otherRemoteRefs,
|
|
856
|
+
fetched,
|
|
817
857
|
env: {
|
|
818
858
|
ctx,
|
|
819
859
|
tips: /* @__PURE__ */ new Map(),
|
|
@@ -824,6 +864,10 @@ async function snapshot(input) {
|
|
|
824
864
|
mergedPrShas: mergedPrs,
|
|
825
865
|
remoteShas: new Set(remoteRefs.map((r) => r.sha)),
|
|
826
866
|
remoteNames: new Set(remoteRefs.map((r) => r.name)),
|
|
867
|
+
...remote ? {
|
|
868
|
+
remoteReachable: () => remoteReachableP ??= revListSet([`--glob=refs/remotes/${remote}/*`]),
|
|
869
|
+
localOrRemoteReachable: () => localOrRemoteP ??= revListSet(["--branches", `--glob=refs/remotes/${remote}/*`])
|
|
870
|
+
} : {},
|
|
827
871
|
minAgeDays: input.minAgeDays,
|
|
828
872
|
includeReviewed: input.includeReviewed,
|
|
829
873
|
...input.verdicts ? { verdicts: input.verdicts } : {},
|
|
@@ -857,7 +901,8 @@ async function planBranchGc(input) {
|
|
|
857
901
|
...input.verdicts ? { verdicts: input.verdicts } : {},
|
|
858
902
|
minAgeDays,
|
|
859
903
|
includeReviewed,
|
|
860
|
-
nowMs
|
|
904
|
+
nowMs,
|
|
905
|
+
...input.fetch === false ? { fetch: false } : {}
|
|
861
906
|
});
|
|
862
907
|
const inScope = snap.refs.filter((r) => scopes.includes(r.kind));
|
|
863
908
|
const entries = await pool(inScope, input.concurrency ?? 8, (r) => classifyRef(r, snap.env));
|
|
@@ -875,6 +920,7 @@ async function planBranchGc(input) {
|
|
|
875
920
|
includeReviewed,
|
|
876
921
|
generatedAt: new Date(nowMs).toISOString(),
|
|
877
922
|
otherRemoteRefs: snap.otherRemoteRefs,
|
|
923
|
+
fetched: snap.fetched,
|
|
878
924
|
entries
|
|
879
925
|
};
|
|
880
926
|
}
|
|
@@ -963,7 +1009,8 @@ async function applyBranchGc(plan, options) {
|
|
|
963
1009
|
...options.verdicts ? { verdicts: options.verdicts } : {},
|
|
964
1010
|
minAgeDays: plan.minAgeDays,
|
|
965
1011
|
includeReviewed: plan.includeReviewed,
|
|
966
|
-
nowMs: options.nowMs ?? Date.now()
|
|
1012
|
+
nowMs: options.nowMs ?? Date.now(),
|
|
1013
|
+
...options.fetch === false ? { fetch: false } : {}
|
|
967
1014
|
});
|
|
968
1015
|
const current = new Map(snap.refs.map((r) => [r.ref, r]));
|
|
969
1016
|
const outcomes = [];
|
|
@@ -1025,23 +1072,34 @@ async function applyBranchGc(plan, options) {
|
|
|
1025
1072
|
for (const [remote, list] of byRemote) {
|
|
1026
1073
|
const failedBatches = [];
|
|
1027
1074
|
for (const batch of chunk(list, options.remoteBatchSize ?? 50)) {
|
|
1028
|
-
const res = await git(repoRoot, ["push", remote, "--delete", ...batch.map((e) => e.name)]);
|
|
1075
|
+
const res = await git(repoRoot, ["push", "--no-verify", remote, "--delete", ...batch.map((e) => e.name)]);
|
|
1029
1076
|
if (res.exitCode === 0) for (const e of batch) results.set(e.ref, { ok: true });
|
|
1030
1077
|
else failedBatches.push({ batch, res });
|
|
1031
1078
|
}
|
|
1032
1079
|
await git(repoRoot, ["fetch", "--prune", "--quiet", remote]);
|
|
1033
|
-
|
|
1080
|
+
const stillThere = [];
|
|
1034
1081
|
for (const { batch } of failedBatches) {
|
|
1035
1082
|
for (const e of batch) {
|
|
1036
|
-
if ((await git(repoRoot, ["rev-parse", "--verify", "--quiet", e.ref])).exitCode
|
|
1037
|
-
|
|
1038
|
-
continue;
|
|
1039
|
-
}
|
|
1040
|
-
const one = await git(repoRoot, ["push", remote, "--delete", e.name]);
|
|
1041
|
-
results.set(e.ref, one.exitCode === 0 ? { ok: true } : fail(one));
|
|
1042
|
-
retried = true;
|
|
1083
|
+
if ((await git(repoRoot, ["rev-parse", "--verify", "--quiet", e.ref])).exitCode === 0) stillThere.push(e);
|
|
1084
|
+
else results.set(e.ref, { ok: true });
|
|
1043
1085
|
}
|
|
1044
1086
|
}
|
|
1087
|
+
const failedAgain = [];
|
|
1088
|
+
for (const batch of chunk(stillThere, options.remoteBatchSize ?? 50)) {
|
|
1089
|
+
const retry = await git(repoRoot, ["push", "--no-verify", remote, "--delete", ...batch.map((e) => e.name)]);
|
|
1090
|
+
if (retry.exitCode === 0) for (const e of batch) results.set(e.ref, { ok: true });
|
|
1091
|
+
else failedAgain.push(...batch);
|
|
1092
|
+
}
|
|
1093
|
+
const retried = failedAgain.length > 0;
|
|
1094
|
+
if (retried) await git(repoRoot, ["fetch", "--prune", "--quiet", remote]);
|
|
1095
|
+
for (const e of failedAgain) {
|
|
1096
|
+
if ((await git(repoRoot, ["rev-parse", "--verify", "--quiet", e.ref])).exitCode !== 0) {
|
|
1097
|
+
results.set(e.ref, { ok: true });
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const one = await git(repoRoot, ["push", "--no-verify", remote, "--delete", e.name]);
|
|
1101
|
+
results.set(e.ref, one.exitCode === 0 ? { ok: true } : fail(one));
|
|
1102
|
+
}
|
|
1045
1103
|
if (retried) await git(repoRoot, ["fetch", "--prune", "--quiet", remote]);
|
|
1046
1104
|
}
|
|
1047
1105
|
for (const e of confirmed.filter((c) => c.kind === "orphan")) {
|
|
@@ -1157,13 +1215,22 @@ async function recordBranchVerdict(input) {
|
|
|
1157
1215
|
// src/gc.ts
|
|
1158
1216
|
var candidates = [worktreeProvider];
|
|
1159
1217
|
function classifyForGc(tree, integration, liveness, options = {}) {
|
|
1218
|
+
const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
|
|
1160
1219
|
if (options.includeDetached && integration.state === "detached") {
|
|
1161
|
-
const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
|
|
1162
1220
|
if (tree.state === "clean" && idleOrUnreachable) return "reclaim";
|
|
1163
1221
|
}
|
|
1222
|
+
const noCommitsOfItsOwn = integration.state === "fresh" || Boolean(options.includeDetached) && integration.state === "detached";
|
|
1223
|
+
if (noCommitsOfItsOwn && idleOrUnreachable && tree.state === "dirty" && tree.modified === 0 && tree.staged === 0 && tree.untracked > 0 && tree.onlyUnder === ".plans/") {
|
|
1224
|
+
return "salvage";
|
|
1225
|
+
}
|
|
1164
1226
|
return classify(tree, integration, liveness, options.nowMs).class;
|
|
1165
1227
|
}
|
|
1166
|
-
var DEFAULT_GC_NOISE_PATHS = [
|
|
1228
|
+
var DEFAULT_GC_NOISE_PATHS = [
|
|
1229
|
+
"pnpm-lock.yaml",
|
|
1230
|
+
"package-lock.json",
|
|
1231
|
+
".opencode/package-lock.json",
|
|
1232
|
+
".claude/launch.json"
|
|
1233
|
+
];
|
|
1167
1234
|
function isProtectedPath(worktreePath, protectedPaths) {
|
|
1168
1235
|
if (!protectedPaths || protectedPaths.length === 0) return false;
|
|
1169
1236
|
return protectedPaths.some((p) => p === worktreePath || p.startsWith(worktreePath + sep));
|
|
@@ -1588,6 +1655,7 @@ async function applyOne(entry, options) {
|
|
|
1588
1655
|
return salvageOne(options, fresh);
|
|
1589
1656
|
}
|
|
1590
1657
|
async function applyGc(plan, options) {
|
|
1658
|
+
if (options.worktreesRoot) sweepWorktreeTrash(options.worktreesRoot);
|
|
1591
1659
|
const outcomes = [];
|
|
1592
1660
|
for (const entry of plan) {
|
|
1593
1661
|
outcomes.push(await applyOne(entry, options));
|