@agentproto/worktree 0.6.2 → 0.7.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
@@ -956,6 +956,15 @@ interface GitWorktreeRef {
956
956
  /** `null` for a detached HEAD. */
957
957
  branch: string | null;
958
958
  head: string;
959
+ /**
960
+ * Set to git's own reason string (e.g. `"gitdir file points to
961
+ * non-existent location"`) when `git worktree list --porcelain` itself
962
+ * marks this entry prunable — its working directory is gone, so no git
963
+ * command can be run with it as `-C` target. `undefined` for every
964
+ * ordinary, live entry. This is git's own signal, not re-derived via
965
+ * `existsSync` or any other filesystem check of our own.
966
+ */
967
+ prunable?: string;
959
968
  }
960
969
  /** Parses `git worktree list --porcelain` — every linked worktree of `repoRoot`, wherever it lives. */
961
970
  declare function listGitWorktrees(repoRoot: string): Promise<GitWorktreeRef[]>;
@@ -1205,14 +1214,22 @@ interface ClassifyForGcOptions {
1205
1214
  declare function classifyForGc(tree: TreeState, integration: IntegrationState, liveness: LivenessState, options?: ClassifyForGcOptions): GcClass;
1206
1215
  /**
1207
1216
  * The reclaim reasons this module can attach. `dep-bump` promotes a `hold`
1208
- * to `reclaim` (see above). `orphan` is different in kind — it never starts
1209
- * from a `hold` verdict at all, because an orphan was never classified in
1210
- * the first place (see the "orphan reclaim" section below): a directory
1211
- * physically present under the repo's worktree pool that `git worktree
1212
- * list` no longer knows about, so none of `classify`'s three axes can be
1213
- * computed for it.
1217
+ * to `reclaim` (see above). `orphan` and `prunable` are both different in
1218
+ * kind — neither ever starts from a `hold` verdict, because neither was
1219
+ * classified via `classify`'s three axes in the first place:
1220
+ * - `orphan`: a directory physically present under the repo's worktree
1221
+ * pool that `git worktree list` no longer knows about at all (see the
1222
+ * "orphan reclaim" section below).
1223
+ * - `prunable`: the mirror image — `git worktree list` still has a
1224
+ * registration, but git itself already reports it `prunable` because
1225
+ * the working directory is gone. Distinct from `orphan` on purpose: an
1226
+ * orphan's directory is still there to lose (it just failed removal),
1227
+ * so `reclaimOrphan` does `rm -rf`; a prunable entry's directory is
1228
+ * already gone, so there's nothing to inspect and nothing to `rm` — the
1229
+ * only irreversible thing history nearly did to it (deleting the branch)
1230
+ * must not happen either. See "prunable reclaim" below.
1214
1231
  */
1215
- type GcReclaimReason = "dep-bump" | "orphan";
1232
+ type GcReclaimReason = "dep-bump" | "orphan" | "prunable";
1216
1233
  /** The hold reasons this module can attach, distinct from `classify`'s snapshot-based holds. */
1217
1234
  type GcHoldReason = "live-session-cwd";
1218
1235
  /**
@@ -1255,17 +1272,19 @@ interface GcPlanEntry {
1255
1272
  branch: string | null;
1256
1273
  head: string;
1257
1274
  /**
1258
- * Absent only for an orphan entry (`orphan: true`) — git itself cannot
1259
- * answer the tree/integration/liveness questions for a directory it no
1260
- * longer recognizes as a worktree, so this module never fabricates a
1261
- * value for them rather than reporting a fact it can't actually check.
1275
+ * Absent for an orphan entry (`orphan: true`) or a prunable entry
1276
+ * (`prunable: true`) — git itself cannot answer the tree/integration/
1277
+ * liveness questions for a directory it no longer recognizes as a
1278
+ * worktree (orphan) or whose working directory is simply gone (prunable),
1279
+ * so this module never fabricates a value for them rather than reporting
1280
+ * a fact it can't actually check.
1262
1281
  */
1263
1282
  tree?: TreeState;
1264
1283
  integration?: IntegrationState;
1265
1284
  liveness?: LivenessState;
1266
1285
  /** Classification per PLAN.md §5.1, after `--include-detached` (if set). */
1267
1286
  class: GcClass;
1268
- /** Set only when `class === "reclaim"` via the dep-bump exemption or the orphan reclaim path — see `resolveGcClass` / `scanOrphanWorktreePaths`. */
1287
+ /** Set only when `class === "reclaim"` via the dep-bump exemption, the orphan reclaim path, or the prunable reclaim path — see `resolveGcClass` / `scanOrphanWorktreePaths` / `toPlanEntry`. */
1269
1288
  reclaimReason?: GcReclaimReason;
1270
1289
  /** Set only when `class === "hold"` via `protectedPaths` (`isProtectedPath`) — distinguishes this from an ordinary snapshot-based hold. */
1271
1290
  holdReason?: GcHoldReason;
@@ -1278,6 +1297,14 @@ interface GcPlanEntry {
1278
1297
  * it classifies.
1279
1298
  */
1280
1299
  orphan?: boolean;
1300
+ /**
1301
+ * `true` only for a linked worktree `git worktree list --porcelain` itself
1302
+ * already marked `prunable` — its working directory is gone, so none of
1303
+ * `classify`'s three axes were computed (no git command was ever run
1304
+ * against the dead path). Always paired with `class: "reclaim"` and
1305
+ * `reclaimReason: "prunable"` — see "prunable reclaim" below `toPlanEntry`.
1306
+ */
1307
+ prunable?: boolean;
1281
1308
  }
1282
1309
  interface PlanGcInput {
1283
1310
  repoRoot: string;
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-5TEU6UGY.mjs';
1
+ export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-MKZ7BDGG.mjs';
2
2
  import './chunk-E3EM24BS.mjs';
3
- import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, worktreeProvider } from './chunk-Y337JFBG.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, 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-Y337JFBG.mjs';
3
+ import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, execGit, worktreeProvider } from './chunk-RQUJW75P.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, 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-RQUJW75P.mjs';
5
5
  import { cleanupWorktreeTool } from './chunk-BBDKRTJK.mjs';
6
6
  export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-BBDKRTJK.mjs';
7
7
  import { mkdir, copyFile, writeFile, open, readdir, realpath, rm, readFile, stat } from 'fs/promises';
@@ -563,6 +563,39 @@ async function reclaimOrphan(entry, options) {
563
563
  }
564
564
  return { path: entry.path, branch: null, result: "reclaimed", reclaimReason: "orphan" };
565
565
  }
566
+ function makePrunablePlanEntry(worktree, protectedPaths) {
567
+ if (isProtectedPath(worktree.path, protectedPaths)) {
568
+ return { path: worktree.path, branch: worktree.branch, head: worktree.head, class: "hold", holdReason: "live-session-cwd" };
569
+ }
570
+ return {
571
+ path: worktree.path,
572
+ branch: worktree.branch,
573
+ head: worktree.head,
574
+ class: "reclaim",
575
+ reclaimReason: "prunable",
576
+ prunable: true
577
+ };
578
+ }
579
+ async function reclaimPrunable(entry, options) {
580
+ const fresh = await listGitWorktrees(options.repoRoot);
581
+ const stillThere = fresh.find((w) => w.path === entry.path);
582
+ if (stillThere && !stillThere.prunable) {
583
+ return { path: entry.path, branch: stillThere.branch, result: "aborted-reclassified", from: "reclaim", to: "hold" };
584
+ }
585
+ if (stillThere) {
586
+ try {
587
+ await execGit(options.repoRoot, ["worktree", "prune"]);
588
+ } catch (err) {
589
+ return {
590
+ path: entry.path,
591
+ branch: entry.branch,
592
+ result: "failed",
593
+ message: err instanceof Error ? err.message : String(err)
594
+ };
595
+ }
596
+ }
597
+ return { path: entry.path, branch: entry.branch, result: "reclaimed", reclaimReason: "prunable" };
598
+ }
566
599
  async function toPlanEntry(repoRoot, defaultBranchRef, worktree, status, includeDetached, nowMs, protectedPaths) {
567
600
  if (isProtectedPath(worktree.path, protectedPaths)) {
568
601
  return {
@@ -600,6 +633,10 @@ async function planGc(input) {
600
633
  const worktrees = await linkedWorktreesOf(input.repoRoot);
601
634
  const entries = [];
602
635
  for (const worktree of worktrees) {
636
+ if (worktree.prunable) {
637
+ entries.push(makePrunablePlanEntry(worktree, input.protectedPaths));
638
+ continue;
639
+ }
603
640
  const status = await computeWorktreeStatus({
604
641
  repoRoot: input.repoRoot,
605
642
  repoName: input.repoName,
@@ -735,6 +772,9 @@ async function applyOne(entry, options) {
735
772
  if (entry.orphan) {
736
773
  return reclaimOrphan(entry, options);
737
774
  }
775
+ if (entry.prunable) {
776
+ return reclaimPrunable(entry, options);
777
+ }
738
778
  if (entry.class === "hold") {
739
779
  return { path: entry.path, branch: entry.branch, result: "held" };
740
780
  }
@@ -784,6 +824,9 @@ async function applyGc(plan, options) {
784
824
  async function reclaimOneWorktree(worktreePath, options) {
785
825
  const worktree = await findWorktree(options.repoRoot, worktreePath);
786
826
  if (!worktree) return null;
827
+ if (worktree.prunable) {
828
+ return applyOne(makePrunablePlanEntry(worktree, options.protectedPaths), options);
829
+ }
787
830
  const status = await computeWorktreeStatus({
788
831
  repoRoot: options.repoRoot,
789
832
  repoName: options.repoName,