@agentproto/worktree 0.10.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/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
- constructor(phase: "setup" | "teardown", run: HookRun);
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): Promise<HookRun[]>;
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): Promise<HookRun[]>;
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). The one thing
1286
- * `classify` cannot know is `--include-detached`, a gc-specific flag; that
1287
- * override lives here, not in the read-only status engine `ls --status`
1288
- * also depends on.
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;
@@ -2002,4 +2157,4 @@ declare function recordBranchVerdict(input: {
2002
2157
  now?: () => string;
2003
2158
  }): Promise<BranchVerdictRecord>;
2004
2159
 
2005
- 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-6EV7WP2U.mjs';
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-KRQFVJPK.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, 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-KRQFVJPK.mjs';
5
- import { cleanupWorktreeTool } from './chunk-IKCBL2UN.mjs';
6
- export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-IKCBL2UN.mjs';
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';
@@ -1215,13 +1215,22 @@ async function recordBranchVerdict(input) {
1215
1215
  // src/gc.ts
1216
1216
  var candidates = [worktreeProvider];
1217
1217
  function classifyForGc(tree, integration, liveness, options = {}) {
1218
+ const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
1218
1219
  if (options.includeDetached && integration.state === "detached") {
1219
- const idleOrUnreachable = liveness.state === "idle" || liveness.state === "daemon-unreachable";
1220
1220
  if (tree.state === "clean" && idleOrUnreachable) return "reclaim";
1221
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
+ }
1222
1226
  return classify(tree, integration, liveness, options.nowMs).class;
1223
1227
  }
1224
- var DEFAULT_GC_NOISE_PATHS = [".opencode/package-lock.json"];
1228
+ var DEFAULT_GC_NOISE_PATHS = [
1229
+ "pnpm-lock.yaml",
1230
+ "package-lock.json",
1231
+ ".opencode/package-lock.json",
1232
+ ".claude/launch.json"
1233
+ ];
1225
1234
  function isProtectedPath(worktreePath, protectedPaths) {
1226
1235
  if (!protectedPaths || protectedPaths.length === 0) return false;
1227
1236
  return protectedPaths.some((p) => p === worktreePath || p.startsWith(worktreePath + sep));
@@ -1646,6 +1655,7 @@ async function applyOne(entry, options) {
1646
1655
  return salvageOne(options, fresh);
1647
1656
  }
1648
1657
  async function applyGc(plan, options) {
1658
+ if (options.worktreesRoot) sweepWorktreeTrash(options.worktreesRoot);
1649
1659
  const outcomes = [];
1650
1660
  for (const entry of plan) {
1651
1661
  outcomes.push(await applyOne(entry, options));