@webpieces/repo-workflow-core 0.4.976 → 0.4.977

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.
@@ -1 +1 @@
1
- {"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from '@webpieces/tooling-common';\nimport { DotWebpieces, dotWebpieces } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { Worktree, WorktreeService } from './worktrees';\nimport { CommandTimeouts } from './command-timeouts';\nimport { LockDecision, WorktreeLockVerdicts } from './worktree-lock-verdicts';\nimport {\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_SUPERSEDED,\n DeletableBranch,\n DeletableWorktree,\n MergedBranch,\n MergedBranchesCache,\n} from './merged-branch-verdicts';\n\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n ADJUDICATED_CLASSIFICATIONS,\n} from './merged-branch-verdicts';\n\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n/**\n * Internal: the bulk PR lookup, indexed.\n *\n * `merged` drives the DELETE verdicts and comes from the `--state merged` call, unchanged — the\n * reaping logic must not get looser or tighter here. `state` is display-only, from a second\n * `--state all` call, and answers the question a human needs in order to say \"yes, delete it\":\n * was there a PR at all, and did it close without merging? It fails soft to an empty map.\n */\nclass PrRef {\n number: number;\n state: string;\n\n constructor(prNumber: number, state: string) {\n this.number = prNumber;\n this.state = state;\n }\n}\n\nclass PrLookup {\n merged: Map<string, number>;\n state: Map<string, PrRef>;\n /**\n * Highest merged PR number seen. A branch whose own PR CLOSED UNMERGED below this number means work\n * kept landing after it was abandoned — the \"superseded by a later PR\" signal, obtained without\n * having to guess WHICH PR superseded it.\n */\n latestMergedPr: number;\n\n constructor(merged: Map<string, number>, state: Map<string, PrRef>, latestMergedPr: number = 0) {\n this.merged = merged;\n this.state = state;\n this.latestMergedPr = latestMergedPr;\n }\n}\n\n// Internal: a classification result. `deletable` is the decision; `entry` carries the branch + reason\n// either way (a spared branch still needs its reason recorded, and it has no PR to key off).\nclass Verdict {\n deletable: boolean;\n entry: DeletableBranch;\n\n constructor(deletable: boolean, entry: DeletableBranch) {\n this.deletable = deletable;\n this.entry = entry;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawDeletable {\n branch?: string;\n reason?: string;\n pr?: number;\n // Absent in caches written before the \"ask the human which of these to delete\" remedy existed.\n sha?: string;\n commits?: number;\n prState?: string;\n // Absent before classification existed — revives to the conservative 'never-proposed'.\n classification?: string;\n}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n // Absent before worktree verdicts carried a classification — revives to 'never-proposed'.\n classification?: string;\n // Absent before stale agent locks were recognised — revives to false, i.e. \"there is no lock to\n // clear\", which is the fail-safe direction: the removal fails loudly instead of overriding a lock\n // this release cannot see a reason for.\n unlockBeforeRemove?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n // Only requested by the display-only fetchPrStates call.\n state?: string;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly locks: WorktreeLockVerdicts = new WorktreeLockVerdicts(),\n private readonly timeouts: CommandTimeouts = new CommandTimeouts(),\n ) {}\n\n /**\n * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents\n * describe every branch AND every worktree in the repo, so a per-worktree copy was a repo-wide fact\n * stored N times and therefore N times wrong: branch-creation-guard reported \"8 parked local\n * branches\" against an actual 1, reading a copy that predated deletions made from another worktree.\n */\n mergedBranchesPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * How old is this cache, and may its numbers be quoted as fact?\n *\n * `now` is a parameter purely so the specs can pin it; every caller passes nothing.\n */\n freshness(cache: MergedBranchesCache, now: number = Date.now()): CacheFreshness {\n const written = Date.parse(cache.timestamp);\n if (Number.isNaN(written)) return new CacheFreshness(true, -1, cache.timestamp);\n const ageMs = Math.max(0, now - written);\n const ageMinutes = Math.floor(ageMs / 60000);\n return new CacheFreshness(ageMs > CACHE_STALE_AFTER_MS, ageMinutes, cache.timestamp);\n }\n\n /**\n * Drop every cached verdict whose subject no longer exists, using only LOCAL, instant git reads.\n *\n * The verdicts themselves are NOT re-derived here — that is the detached refresher's job and it\n * needs the network. This answers a different and much cheaper question: does this branch / this\n * worktree still exist at all? A cache written before a `git branch -D` or a `git worktree remove`\n * keeps naming things that are gone, and a reader that counts those entries reports a repo that\n * does not exist (observed: 8 parked branches asserted against an actual 1) and then blocks on it.\n *\n * Fails toward the cache: if git cannot answer (empty branch list), the cache is returned untouched\n * rather than emptied, since \"git failed\" must never read as \"everything was deleted\".\n */\n reconcile(repoRoot: string, cache: MergedBranchesCache): MergedBranchesCache {\n const live = new Set(this.localBranches(repoRoot));\n const trees = this.worktrees.listWorktrees(repoRoot);\n const livePaths = new Set(trees.map((tree: Worktree): string => tree.path));\n\n const keepBranch = (entry: DeletableBranch): boolean => live.size === 0 || live.has(entry.branch);\n const keepTree = (entry: DeletableWorktree): boolean =>\n trees.length === 0 || livePaths.has(entry.path);\n\n return new MergedBranchesCache(\n cache.timestamp,\n cache.deletable.filter(keepBranch),\n cache.keep.filter(keepBranch),\n cache.worktrees.filter(keepTree),\n );\n }\n\n /**\n * ATOMIC write. Now that this file is shared by every worktree it has N concurrent writers (one\n * detached refresher per agent), and its readers are the guards' BLOCKING path. A plain\n * `writeFileSync` truncates before it writes, so a reader landing in that window gets a torn\n * document — the reader-side retry added in PR #526 cannot rescue a syntactically valid PREFIX.\n * Fixed where it belongs: temp file + `rename()`, which POSIX makes atomic. See AtomicFile.\n *\n * This buys ATOMIC READS, not lost-update protection: two read-modify-write cycles still end\n * last-writer-wins. That is acceptable and deliberate — this is a DERIVED cache, recomputed from\n * `gh` + git by the refresher, and wp-cleanup recomputes rather than trusting it before deleting\n * anything. Nothing here is transactional and no caller may assume it is.\n */\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n this.atomicFile.writeJsonAtomic(this.mergedBranchesPath(repoRoot), cache);\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n const result = this.capture(repoRoot, 'git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/']);\n if (!result.ok || result.out === '') return [];\n return result.out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n computeMergedBranches(repoRoot: string, ignoreStaleAgentLocks: boolean): MergedBranchesCache {\n const merged = this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n let latestMergedPr = 0;\n for (const entry of merged) {\n if (entry.pr > latestMergedPr) latestMergedPr = entry.pr;\n }\n const prs = new PrLookup(byBranch, this.fetchPrStates(repoRoot), latestMergedPr);\n\n const trees = this.worktrees.listWorktrees(repoRoot);\n const holder = new Map<string, string>();\n for (const tree of trees) {\n if (tree.branch !== '') holder.set(tree.branch, tree.path);\n }\n\n const deletable: DeletableBranch[] = [];\n const keep: DeletableBranch[] = [];\n\n for (const branch of this.localBranches(repoRoot)) {\n const verdict = this.classify(repoRoot, branch, prs);\n\n // A branch checked out in ANY worktree (including the branch we are standing on right here)\n // cannot be deleted — git refuses, and since the reap is ONE `git branch -D a b c`, a single\n // such branch would fail the entire command and strand the branches that would have deleted\n // fine. Spare it LOUDLY (into `keep`, with the reason) rather than dropping it silently: the\n // worktree list below is what actually reaps it, and a human should see the connection.\n const heldAt = holder.get(branch);\n if (heldAt !== undefined) {\n keep.push(new DeletableBranch(\n branch,\n `checked out in worktree '${heldAt}' — remove that worktree before deleting the branch`,\n verdict.entry.pr,\n verdict.entry.sha,\n verdict.entry.commits,\n verdict.entry.prState,\n CLASSIFICATION_IN_USE,\n ));\n continue;\n }\n\n if (verdict.deletable) deletable.push(verdict.entry);\n else keep.push(verdict.entry);\n }\n\n const worktrees = this.classifyWorktrees(repoRoot, trees, prs, ignoreStaleAgentLocks);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proof the branch cap uses — a MERGED PR, its own or that of the branch it\n * snapshots. Nothing else. It is spared when its LOCK still stands (see WorktreeLockVerdicts for\n * what makes one stand), when it is the worktree we are standing in right now (removing your own\n * cwd is not a thing to suggest to an agent), or when its branch is anything short of provably\n * merged — INCLUDING a branch with no commits yet, which is what every worktree looks like while\n * an agent is working in it. `deletable` means PROVABLY dead and nothing weaker; wp-cleanup reaps\n * the zero-commit ones on top of that, but only after checking on disk whether anybody is holding\n * them (see CLASSIFICATION_NO_COMMITS), which is a question these verdicts do not answer.\n *\n * THE BRANCH VERDICT IS COMPUTED BEFORE THE LOCK IS RULED ON, which is new and is the point: one\n * of the four things that can override an agent lock is the branch already being provably dead,\n * and that cannot be weighed until it is known. Two extra local git calls per locked worktree, on\n * a path that only ever runs in the detached refresher and in wp-cleanup itself.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\n ignoreStaleAgentLocks: boolean,\n ): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true,\n CLASSIFICATION_PRUNABLE));\n continue;\n }\n\n const verdict = tree.branch === '' ? null : this.classify(repoRoot, tree.branch, prs);\n // An OVERRIDDEN lock leaves the worktree judged on its branch below, exactly like an\n // unlocked one — with `unlockBeforeRemove` set by annotate() so the reap clears the lock.\n const decision = tree.locked ? this.lockDecision(tree, verdict, ignoreStaleAgentLocks) : null;\n if (decision !== null && decision.spare) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, decision.reason, 0, false, CLASSIFICATION_LOCKED));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false, CLASSIFICATION_CURRENT),\n decision));\n continue;\n }\n if (verdict === null) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false,\n CLASSIFICATION_DETACHED), decision));\n continue;\n }\n\n // The branch's OWN classification token rides along unchanged. That is what lets wp-cleanup\n // group probably-dead worktrees exactly the way it groups probably-dead branches, instead of\n // inventing a second, parallel notion of \"how dead is this\".\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable,\n verdict.entry.classification), decision));\n }\n\n return out;\n }\n\n // The lock verdict for one worktree. The evidence gathering (and what it deliberately declines to\n // gather) lives in WorktreeLockVerdicts — see `gather` there.\n private lockDecision(tree: Worktree, verdict: Verdict | null, ignoreStaleAgentLocks: boolean): LockDecision {\n return this.locks.decide(this.locks.gather(\n tree, verdict !== null && verdict.deletable, ignoreStaleAgentLocks));\n }\n\n /**\n * Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n *\n * The verdict itself is unchanged. What is new is that EVERY entry now carries its tip SHA, its\n * unique-commit count and its PR state, so a `keep` can be shown to a human as a delete candidate\n * rather than just counted. This runs only in the detached refresher, so the two extra local git\n * calls per branch cost the blocking hook path nothing.\n */\n private classify(repoRoot: string, branch: string, prs: PrLookup): Verdict {\n const sha = this.tipSha(repoRoot, branch);\n const commits = this.commitsAheadOfMain(repoRoot, branch);\n const known = prs.state.get(branch);\n const prState = known ? known.state : '';\n\n const own = prs.merged.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch, `PR #${String(own)} merged`, own, sha, commits, prState || 'MERGED',\n CLASSIFICATION_MERGED_PR));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = prs.merged.get(base);\n if (basePr !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch,\n `squash-merge backup of '${base}' (PR #${String(basePr)} merged) — its job is done`,\n basePr, sha, commits, prState, CLASSIFICATION_BACKUP_OF_MERGED,\n ));\n }\n // The base has not MERGED — but a snapshot is a copy of its base by construction, so as\n // long as the base still exists this branch is provably not the only copy of anything.\n // Falling through to classifySpared told the human the opposite (\"may be the only copy in\n // existence\"), which is unanswerable-by-design and is why these accumulated.\n //\n // DELETABLE, not merely promptable. Asking about it was still asking a question with only\n // one honest answer — the base branch is right there holding the same work — and every\n // ask lands on a human who is being asked to authorise a delete the tooling can already\n // PROVE is lossless. That is the pile: `wp-start-upsert-pr` mints another snapshot on\n // every run, so the branch cap fills with copies nobody can be bothered to adjudicate.\n // The proof here is as strong as a merged PR (a live sibling ref holds the content), and\n // the reap archives to an `archive/<date>/<branch>` tag and logs a `recover=` first.\n if (this.branchExists(repoRoot, base)) {\n const basePrState = prs.state.get(base);\n const liveness = basePrState\n ? `PR #${String(basePrState.number)} ${basePrState.state}`\n : 'no PR yet';\n return new Verdict(true, new DeletableBranch(\n branch,\n `pre-merge snapshot of '${base}' (${liveness}) — '${base}' still exists and holds ` +\n `this work, so this is a spare copy, not the only one`,\n basePrState ? basePrState.number : 0, sha, commits, prState,\n CLASSIFICATION_BACKUP_OF_LIVE,\n ));\n }\n }\n\n // Zero commits of its own. NOT deletable BY THIS VERDICT — the verdicts answer \"is this\n // provably dead?\", and a husk is not. wp-cleanup reaps it anyway, after asking the one\n // question this service cannot: is anybody HOLDING it (uncommitted files, a live lock, the\n // tree you are standing in)? That check needs the worktree on disk, so it lives there and the\n // verdict stays exactly what it is — see CLASSIFICATION_NO_COMMITS.\n if (commits === 0) {\n return new Verdict(false, new DeletableBranch(\n branch,\n 'no commits of its own — identical to origin/main, so deleting the ref loses a NAME, not a '\n + 'commit. wp-cleanup reaps this by default; a worktree holding it is spared when it has '\n + 'uncommitted files or a live lock, i.e. when somebody is working in it',\n 0, sha, 0, prState, CLASSIFICATION_NO_COMMITS));\n }\n\n return new Verdict(false, this.classifySpared(repoRoot, branch, prs, sha, commits, prState));\n }\n\n /**\n * The three genuinely different reasons a branch survives the deletable proofs, ordered by how\n * confidently a human can say yes to deleting it.\n *\n * The safety posture is UNCHANGED: all three are still SPARED, never auto-deleted. The only thing\n * that changed is that wp-cleanup can now ask a question whose answer is knowable.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private classifySpared(\n repoRoot: string, branch: string, prs: PrLookup, sha: string, commits: number, prState: string,\n ): DeletableBranch {\n const known = prs.state.get(branch);\n\n // A PR that CLOSED without merging, in a repo that has merged later PRs, is near-certainly the\n // abandoned first attempt at work that landed under a different number.\n if (known !== undefined && known.state === 'CLOSED' && prs.latestMergedPr > known.number) {\n return new DeletableBranch(\n branch,\n `PR #${String(known.number)} was CLOSED UNMERGED and later PRs (through ` +\n `#${String(prs.latestMergedPr)}) have merged — near-certainly superseded`,\n known.number, sha, commits, prState, CLASSIFICATION_SUPERSEDED,\n );\n }\n\n // `git cherry` compares by PATCH-ID, so it survives cherry-picks and rebases. It does NOT\n // survive a squash — which is exactly why this is a spared verdict and not a deletable proof.\n if (this.contentAlreadyInMain(repoRoot, branch)) {\n return new DeletableBranch(\n branch,\n `all ${String(commits)} commit(s) already have an equivalent in origin/main ` +\n `(git cherry) — content is not unique`,\n known ? known.number : 0, sha, commits, prState, CLASSIFICATION_CONTENT_IN_MAIN,\n );\n }\n\n const why = known\n ? `PR #${String(known.number)} is ${known.state} (not merged); holds ` +\n `${String(commits)} unique commit(s) — a human must decide`\n : `never had a PR; holds ${String(commits)} unique commit(s) that may be the only copy in existence`;\n return new DeletableBranch(\n branch, why, 0, sha, commits, prState, CLASSIFICATION_NEVER_PROPOSED);\n }\n\n /**\n * True when EVERY commit on the branch has a patch-equivalent already in origin/main.\n *\n * `git cherry origin/main <branch>` prints one line per commit: `+ <sha>` = not upstream,\n * `- <sha>` = an equivalent change IS upstream. All-minus means the content landed (typically by\n * cherry-pick or rebase-merge) even though the SHAs differ. Any failure returns false — \"cannot\n * prove the content is in main\" must never read as \"safe\".\n */\n private contentAlreadyInMain(repoRoot: string, branch: string): boolean {\n const result = this.capture(repoRoot, 'git', ['cherry', 'origin/main', branch]);\n if (!result.ok || result.out === '') return false;\n const lines = result.out.split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line !== '');\n if (lines.length === 0) return false;\n return lines.every((line: string): boolean => line.startsWith('-'));\n }\n\n /**\n * Does this local branch exist? Used to prove a snapshot is not the last copy of its work — the\n * base branch being present is that proof. Verifies it is a BRANCH (`refs/heads/`), not just any\n * resolvable rev, so a tag or a stray SHA sharing the name cannot stand in for the base.\n */\n private branchExists(repoRoot: string, branch: string): boolean {\n return this.capture(repoRoot, 'git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]).ok;\n }\n\n // Short tip SHA — the value that makes any delete of this branch reversible.\n private tipSha(repoRoot: string, branch: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--short', branch]);\n return result.ok ? result.out : '';\n }\n\n /**\n * Commits on `branch` that are not on origin/main. Returns -1 (\"assume it has work\") whenever the\n * count cannot be established — an unresolvable origin/main must never read as \"empty branch\".\n */\n private commitsAheadOfMain(repoRoot: string, branch: string): number {\n const result = this.capture(repoRoot, 'git', ['rev-list', '--count', `origin/main..${branch}`]);\n if (!result.ok) return -1;\n const count = Number(result.out);\n return Number.isInteger(count) ? count : -1;\n }\n\n /**\n * The ONE bulk network call. Every merged PR's head branch in a single round trip — no per-branch\n * lookups. Fails SOFT: if `gh` is missing, unauthenticated, or offline we return [], which makes\n * every branch a `keep`. The guard then deletes nothing rather than guessing.\n */\n private fetchMergedPrs(repoRoot: string): MergedBranch[] {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'merged',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName',\n ]);\n if (!result.ok || result.out === '') return [];\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n const out: MergedBranch[] = [];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n if (branch !== '' && pr > 0) out.push(new MergedBranch(branch, pr));\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n /**\n * The DISPLAY-only second lookup: every PR (any state) keyed by head branch, so a spared branch can\n * be shown to a human as \"PR #12 CLOSED (not merged)\" or \"no PR\" instead of an opaque name. Kept\n * SEPARATE from fetchMergedPrs on purpose — folding both into one `--state all` call would let a\n * flood of open/closed PRs push older MERGED ones out of the limit and silently stop reaping real\n * dead branches. Fails soft to an empty map: the verdicts do not depend on it.\n */\n private fetchPrStates(repoRoot: string): Map<string, PrRef> {\n const out = new Map<string, PrRef>();\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'all',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName,state',\n ]);\n if (!result.ok || result.out === '') return out;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n // `gh` lists newest-first, so the FIRST entry for a branch is its latest PR — keep it.\n if (branch !== '' && pr > 0 && !out.has(branch)) {\n out.set(branch, new PrRef(pr, entry.state ?? ''));\n }\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return out;\n }\n }\n\n private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch => new DeletableBranch(\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.sha ?? '',\n entry.commits ?? -1,\n entry.prState ?? '',\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n ));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => {\n const verdict = new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n );\n verdict.unlockBeforeRemove = entry.unlockBeforeRemove ?? false;\n return verdict;\n });\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8', timeout: this.timeouts.forCommand(cmd) });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
1
+ {"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from '@webpieces/tooling-common';\nimport { DotWebpieces, dotWebpieces } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { Worktree, WorktreeService } from './worktrees';\nimport { CommandTimeouts } from './command-timeouts';\nimport { LockDecision, WorktreeLockVerdicts } from './worktree-lock-verdicts';\nimport {\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_SUPERSEDED,\n DeletableBranch,\n DeletableWorktree,\n MergedBranch,\n MergedBranchesCache,\n} from './merged-branch-verdicts';\n\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n ADJUDICATED_CLASSIFICATIONS,\n} from './merged-branch-verdicts';\n\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n/**\n * Internal: the bulk PR lookup, indexed.\n *\n * `merged` drives the DELETE verdicts and comes from the `--state merged` call, unchanged — the\n * reaping logic must not get looser or tighter here. `state` is display-only, from a second\n * `--state all` call, and answers the question a human needs in order to say \"yes, delete it\":\n * was there a PR at all, and did it close without merging? It fails soft to an empty map.\n */\nclass PrRef {\n number: number;\n state: string;\n\n constructor(prNumber: number, state: string) {\n this.number = prNumber;\n this.state = state;\n }\n}\n\nclass PrLookup {\n merged: Map<string, number>;\n state: Map<string, PrRef>;\n /**\n * Highest merged PR number seen. A branch whose own PR CLOSED UNMERGED below this number means work\n * kept landing after it was abandoned — the \"superseded by a later PR\" signal, obtained without\n * having to guess WHICH PR superseded it.\n */\n latestMergedPr: number;\n\n constructor(merged: Map<string, number>, state: Map<string, PrRef>, latestMergedPr: number = 0) {\n this.merged = merged;\n this.state = state;\n this.latestMergedPr = latestMergedPr;\n }\n}\n\n// Internal: a classification result. `deletable` is the decision; `entry` carries the branch + reason\n// either way (a spared branch still needs its reason recorded, and it has no PR to key off).\nclass Verdict {\n deletable: boolean;\n entry: DeletableBranch;\n\n constructor(deletable: boolean, entry: DeletableBranch) {\n this.deletable = deletable;\n this.entry = entry;\n }\n}\n\n// Internal: one bounded command's verdict and trimmed stdout. Data only.\nclass CapturedOutput {\n constructor(readonly ok: boolean, readonly out: string) {}\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawDeletable {\n branch?: string;\n reason?: string;\n pr?: number;\n // Absent in caches written before the \"ask the human which of these to delete\" remedy existed.\n sha?: string;\n commits?: number;\n prState?: string;\n // Absent before classification existed — revives to the conservative 'never-proposed'.\n classification?: string;\n}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n // Absent before worktree verdicts carried a classification — revives to 'never-proposed'.\n classification?: string;\n // Absent before stale agent locks were recognised — revives to false, i.e. \"there is no lock to\n // clear\", which is the fail-safe direction: the removal fails loudly instead of overriding a lock\n // this release cannot see a reason for.\n unlockBeforeRemove?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n // Only requested by the display-only fetchPrStates call.\n state?: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly locks: WorktreeLockVerdicts = new WorktreeLockVerdicts(),\n private readonly commands: CommandTimeouts = new CommandTimeouts(),\n ) {}\n\n /**\n * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents\n * describe every branch AND every worktree in the repo, so a per-worktree copy was a repo-wide fact\n * stored N times and therefore N times wrong: branch-creation-guard reported \"8 parked local\n * branches\" against an actual 1, reading a copy that predated deletions made from another worktree.\n */\n mergedBranchesPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * How old is this cache, and may its numbers be quoted as fact?\n *\n * `now` is a parameter purely so the specs can pin it; every caller passes nothing.\n */\n freshness(cache: MergedBranchesCache, now: number = Date.now()): CacheFreshness {\n const written = Date.parse(cache.timestamp);\n if (Number.isNaN(written)) return new CacheFreshness(true, -1, cache.timestamp);\n const ageMs = Math.max(0, now - written);\n const ageMinutes = Math.floor(ageMs / 60000);\n return new CacheFreshness(ageMs > CACHE_STALE_AFTER_MS, ageMinutes, cache.timestamp);\n }\n\n /**\n * Drop every cached verdict whose subject no longer exists, using only LOCAL, instant git reads.\n *\n * The verdicts themselves are NOT re-derived here — that is the detached refresher's job and it\n * needs the network. This answers a different and much cheaper question: does this branch / this\n * worktree still exist at all? A cache written before a `git branch -D` or a `git worktree remove`\n * keeps naming things that are gone, and a reader that counts those entries reports a repo that\n * does not exist (observed: 8 parked branches asserted against an actual 1) and then blocks on it.\n *\n * Fails toward the cache: if git cannot answer (empty branch list), the cache is returned untouched\n * rather than emptied, since \"git failed\" must never read as \"everything was deleted\".\n */\n reconcile(repoRoot: string, cache: MergedBranchesCache): MergedBranchesCache {\n const live = new Set(this.localBranches(repoRoot));\n const trees = this.worktrees.listWorktrees(repoRoot);\n const livePaths = new Set(trees.map((tree: Worktree): string => tree.path));\n\n const keepBranch = (entry: DeletableBranch): boolean => live.size === 0 || live.has(entry.branch);\n const keepTree = (entry: DeletableWorktree): boolean =>\n trees.length === 0 || livePaths.has(entry.path);\n\n return new MergedBranchesCache(\n cache.timestamp,\n cache.deletable.filter(keepBranch),\n cache.keep.filter(keepBranch),\n cache.worktrees.filter(keepTree),\n );\n }\n\n /**\n * ATOMIC write. Now that this file is shared by every worktree it has N concurrent writers (one\n * detached refresher per agent), and its readers are the guards' BLOCKING path. A plain\n * `writeFileSync` truncates before it writes, so a reader landing in that window gets a torn\n * document — the reader-side retry added in PR #526 cannot rescue a syntactically valid PREFIX.\n * Fixed where it belongs: temp file + `rename()`, which POSIX makes atomic. See AtomicFile.\n *\n * This buys ATOMIC READS, not lost-update protection: two read-modify-write cycles still end\n * last-writer-wins. That is acceptable and deliberate — this is a DERIVED cache, recomputed from\n * `gh` + git by the refresher, and wp-cleanup recomputes rather than trusting it before deleting\n * anything. Nothing here is transactional and no caller may assume it is.\n */\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n this.atomicFile.writeJsonAtomic(this.mergedBranchesPath(repoRoot), cache);\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n // SYNCHRONOUS: the branch-creation guard counts parked branches on its blocking path.\n const result = this.commands.localGitNow(['for-each-ref', '--format=%(refname:short)', 'refs/heads/'], repoRoot);\n const out = result.stdout.trim();\n if (!result.ok || out === '') return [];\n return out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n async computeMergedBranches(repoRoot: string, ignoreStaleAgentLocks: boolean): Promise<MergedBranchesCache> {\n const merged = await this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n let latestMergedPr = 0;\n for (const entry of merged) {\n if (entry.pr > latestMergedPr) latestMergedPr = entry.pr;\n }\n const prs = new PrLookup(byBranch, await this.fetchPrStates(repoRoot), latestMergedPr);\n\n const trees = this.worktrees.listWorktrees(repoRoot);\n const holder = new Map<string, string>();\n for (const tree of trees) {\n if (tree.branch !== '') holder.set(tree.branch, tree.path);\n }\n\n const deletable: DeletableBranch[] = [];\n const keep: DeletableBranch[] = [];\n\n for (const branch of this.localBranches(repoRoot)) {\n const verdict = await this.classify(repoRoot, branch, prs);\n\n // A branch checked out in ANY worktree (including the branch we are standing on right here)\n // cannot be deleted — git refuses, and since the reap is ONE `git branch -D a b c`, a single\n // such branch would fail the entire command and strand the branches that would have deleted\n // fine. Spare it LOUDLY (into `keep`, with the reason) rather than dropping it silently: the\n // worktree list below is what actually reaps it, and a human should see the connection.\n const heldAt = holder.get(branch);\n if (heldAt !== undefined) {\n keep.push(new DeletableBranch(\n branch,\n `checked out in worktree '${heldAt}' — remove that worktree before deleting the branch`,\n verdict.entry.pr,\n verdict.entry.sha,\n verdict.entry.commits,\n verdict.entry.prState,\n CLASSIFICATION_IN_USE,\n ));\n continue;\n }\n\n if (verdict.deletable) deletable.push(verdict.entry);\n else keep.push(verdict.entry);\n }\n\n const worktrees = await this.classifyWorktrees(repoRoot, trees, prs, ignoreStaleAgentLocks);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proof the branch cap uses — a MERGED PR, its own or that of the branch it\n * snapshots. Nothing else. It is spared when its LOCK still stands (see WorktreeLockVerdicts for\n * what makes one stand), when it is the worktree we are standing in right now (removing your own\n * cwd is not a thing to suggest to an agent), or when its branch is anything short of provably\n * merged — INCLUDING a branch with no commits yet, which is what every worktree looks like while\n * an agent is working in it. `deletable` means PROVABLY dead and nothing weaker; wp-cleanup reaps\n * the zero-commit ones on top of that, but only after checking on disk whether anybody is holding\n * them (see CLASSIFICATION_NO_COMMITS), which is a question these verdicts do not answer.\n *\n * THE BRANCH VERDICT IS COMPUTED BEFORE THE LOCK IS RULED ON, which is new and is the point: one\n * of the four things that can override an agent lock is the branch already being provably dead,\n * and that cannot be weighed until it is known. Two extra local git calls per locked worktree, on\n * a path that only ever runs in the detached refresher and in wp-cleanup itself.\n */\n private async classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\n ignoreStaleAgentLocks: boolean,\n ): Promise<DeletableWorktree[]> {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true,\n CLASSIFICATION_PRUNABLE));\n continue;\n }\n\n const verdict = tree.branch === '' ? null : await this.classify(repoRoot, tree.branch, prs);\n // An OVERRIDDEN lock leaves the worktree judged on its branch below, exactly like an\n // unlocked one — with `unlockBeforeRemove` set by annotate() so the reap clears the lock.\n const decision = tree.locked ? await this.lockDecision(tree, verdict, ignoreStaleAgentLocks) : null;\n if (decision !== null && decision.spare) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, decision.reason, 0, false, CLASSIFICATION_LOCKED));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false, CLASSIFICATION_CURRENT),\n decision));\n continue;\n }\n if (verdict === null) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false,\n CLASSIFICATION_DETACHED), decision));\n continue;\n }\n\n // The branch's OWN classification token rides along unchanged. That is what lets wp-cleanup\n // group probably-dead worktrees exactly the way it groups probably-dead branches, instead of\n // inventing a second, parallel notion of \"how dead is this\".\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable,\n verdict.entry.classification), decision));\n }\n\n return out;\n }\n\n // The lock verdict for one worktree. The evidence gathering (and what it deliberately declines to\n // gather) lives in WorktreeLockVerdicts — see `gather` there.\n private async lockDecision(tree: Worktree, verdict: Verdict | null, ignoreStaleAgentLocks: boolean): Promise<LockDecision> {\n return this.locks.decide(await this.locks.gather(\n tree, verdict !== null && verdict.deletable, ignoreStaleAgentLocks));\n }\n\n /**\n * Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n *\n * The verdict itself is unchanged. What is new is that EVERY entry now carries its tip SHA, its\n * unique-commit count and its PR state, so a `keep` can be shown to a human as a delete candidate\n * rather than just counted. This runs only in the detached refresher, so the two extra local git\n * calls per branch cost the blocking hook path nothing.\n */\n private async classify(repoRoot: string, branch: string, prs: PrLookup): Promise<Verdict> {\n const sha = await this.tipSha(repoRoot, branch);\n const commits = await this.commitsAheadOfMain(repoRoot, branch);\n const known = prs.state.get(branch);\n const prState = known ? known.state : '';\n\n const own = prs.merged.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch, `PR #${String(own)} merged`, own, sha, commits, prState || 'MERGED',\n CLASSIFICATION_MERGED_PR));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = prs.merged.get(base);\n if (basePr !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch,\n `squash-merge backup of '${base}' (PR #${String(basePr)} merged) — its job is done`,\n basePr, sha, commits, prState, CLASSIFICATION_BACKUP_OF_MERGED,\n ));\n }\n // The base has not MERGED — but a snapshot is a copy of its base by construction, so as\n // long as the base still exists this branch is provably not the only copy of anything.\n // Falling through to classifySpared told the human the opposite (\"may be the only copy in\n // existence\"), which is unanswerable-by-design and is why these accumulated.\n //\n // DELETABLE, not merely promptable. Asking about it was still asking a question with only\n // one honest answer — the base branch is right there holding the same work — and every\n // ask lands on a human who is being asked to authorise a delete the tooling can already\n // PROVE is lossless. That is the pile: `wp-start-upsert-pr` mints another snapshot on\n // every run, so the branch cap fills with copies nobody can be bothered to adjudicate.\n // The proof here is as strong as a merged PR (a live sibling ref holds the content), and\n // the reap archives to an `archive/<date>/<branch>` tag and logs a `recover=` first.\n if (await this.branchExists(repoRoot, base)) {\n const basePrState = prs.state.get(base);\n const liveness = basePrState\n ? `PR #${String(basePrState.number)} ${basePrState.state}`\n : 'no PR yet';\n return new Verdict(true, new DeletableBranch(\n branch,\n `pre-merge snapshot of '${base}' (${liveness}) — '${base}' still exists and holds ` +\n `this work, so this is a spare copy, not the only one`,\n basePrState ? basePrState.number : 0, sha, commits, prState,\n CLASSIFICATION_BACKUP_OF_LIVE,\n ));\n }\n }\n\n // Zero commits of its own. NOT deletable BY THIS VERDICT — the verdicts answer \"is this\n // provably dead?\", and a husk is not. wp-cleanup reaps it anyway, after asking the one\n // question this service cannot: is anybody HOLDING it (uncommitted files, a live lock, the\n // tree you are standing in)? That check needs the worktree on disk, so it lives there and the\n // verdict stays exactly what it is — see CLASSIFICATION_NO_COMMITS.\n if (commits === 0) {\n return new Verdict(false, new DeletableBranch(\n branch,\n 'no commits of its own — identical to origin/main, so deleting the ref loses a NAME, not a '\n + 'commit. wp-cleanup reaps this by default; a worktree holding it is spared when it has '\n + 'uncommitted files or a live lock, i.e. when somebody is working in it',\n 0, sha, 0, prState, CLASSIFICATION_NO_COMMITS));\n }\n\n return new Verdict(false, await this.classifySpared(repoRoot, branch, prs, sha, commits, prState));\n }\n\n /**\n * The three genuinely different reasons a branch survives the deletable proofs, ordered by how\n * confidently a human can say yes to deleting it.\n *\n * The safety posture is UNCHANGED: all three are still SPARED, never auto-deleted. The only thing\n * that changed is that wp-cleanup can now ask a question whose answer is knowable.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private async classifySpared(\n repoRoot: string, branch: string, prs: PrLookup, sha: string, commits: number, prState: string,\n ): Promise<DeletableBranch> {\n const known = prs.state.get(branch);\n\n // A PR that CLOSED without merging, in a repo that has merged later PRs, is near-certainly the\n // abandoned first attempt at work that landed under a different number.\n if (known !== undefined && known.state === 'CLOSED' && prs.latestMergedPr > known.number) {\n return new DeletableBranch(\n branch,\n `PR #${String(known.number)} was CLOSED UNMERGED and later PRs (through ` +\n `#${String(prs.latestMergedPr)}) have merged — near-certainly superseded`,\n known.number, sha, commits, prState, CLASSIFICATION_SUPERSEDED,\n );\n }\n\n // `git cherry` compares by PATCH-ID, so it survives cherry-picks and rebases. It does NOT\n // survive a squash — which is exactly why this is a spared verdict and not a deletable proof.\n if (await this.contentAlreadyInMain(repoRoot, branch)) {\n return new DeletableBranch(\n branch,\n `all ${String(commits)} commit(s) already have an equivalent in origin/main ` +\n `(git cherry) — content is not unique`,\n known ? known.number : 0, sha, commits, prState, CLASSIFICATION_CONTENT_IN_MAIN,\n );\n }\n\n const why = known\n ? `PR #${String(known.number)} is ${known.state} (not merged); holds ` +\n `${String(commits)} unique commit(s) — a human must decide`\n : `never had a PR; holds ${String(commits)} unique commit(s) that may be the only copy in existence`;\n return new DeletableBranch(\n branch, why, 0, sha, commits, prState, CLASSIFICATION_NEVER_PROPOSED);\n }\n\n /**\n * True when EVERY commit on the branch has a patch-equivalent already in origin/main.\n *\n * `git cherry origin/main <branch>` prints one line per commit: `+ <sha>` = not upstream,\n * `- <sha>` = an equivalent change IS upstream. All-minus means the content landed (typically by\n * cherry-pick or rebase-merge) even though the SHAs differ. Any failure returns false — \"cannot\n * prove the content is in main\" must never read as \"safe\".\n */\n private async contentAlreadyInMain(repoRoot: string, branch: string): Promise<boolean> {\n const result = await this.git(repoRoot, ['cherry', 'origin/main', branch]);\n if (!result.ok || result.out === '') return false;\n const lines = result.out.split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line !== '');\n if (lines.length === 0) return false;\n return lines.every((line: string): boolean => line.startsWith('-'));\n }\n\n /**\n * Does this local branch exist? Used to prove a snapshot is not the last copy of its work — the\n * base branch being present is that proof. Verifies it is a BRANCH (`refs/heads/`), not just any\n * resolvable rev, so a tag or a stray SHA sharing the name cannot stand in for the base.\n */\n private async branchExists(repoRoot: string, branch: string): Promise<boolean> {\n return (await this.git(repoRoot, ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`])).ok;\n }\n\n // Short tip SHA — the value that makes any delete of this branch reversible.\n private async tipSha(repoRoot: string, branch: string): Promise<string> {\n const result = await this.git(repoRoot, ['rev-parse', '--short', branch]);\n return result.ok ? result.out : '';\n }\n\n /**\n * Commits on `branch` that are not on origin/main. Returns -1 (\"assume it has work\") whenever the\n * count cannot be established — an unresolvable origin/main must never read as \"empty branch\".\n */\n private async commitsAheadOfMain(repoRoot: string, branch: string): Promise<number> {\n const result = await this.git(repoRoot, ['rev-list', '--count', `origin/main..${branch}`]);\n if (!result.ok) return -1;\n const count = Number(result.out);\n return Number.isInteger(count) ? count : -1;\n }\n\n /**\n * The ONE bulk network call. Every merged PR's head branch in a single round trip — no per-branch\n * lookups. Fails SOFT: if `gh` is missing, unauthenticated, or offline we return [], which makes\n * every branch a `keep`. The guard then deletes nothing rather than guessing.\n */\n private async fetchMergedPrs(repoRoot: string): Promise<MergedBranch[]> {\n const result = await this.gh(repoRoot, [\n 'pr', 'list',\n '--state', 'merged',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName',\n ]);\n if (!result.ok || result.out === '') return [];\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n const out: MergedBranch[] = [];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n if (branch !== '' && pr > 0) out.push(new MergedBranch(branch, pr));\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n /**\n * The DISPLAY-only second lookup: every PR (any state) keyed by head branch, so a spared branch can\n * be shown to a human as \"PR #12 CLOSED (not merged)\" or \"no PR\" instead of an opaque name. Kept\n * SEPARATE from fetchMergedPrs on purpose — folding both into one `--state all` call would let a\n * flood of open/closed PRs push older MERGED ones out of the limit and silently stop reaping real\n * dead branches. Fails soft to an empty map: the verdicts do not depend on it.\n */\n private async fetchPrStates(repoRoot: string): Promise<Map<string, PrRef>> {\n const out = new Map<string, PrRef>();\n const result = await this.gh(repoRoot, [\n 'pr', 'list',\n '--state', 'all',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName,state',\n ]);\n if (!result.ok || result.out === '') return out;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n // `gh` lists newest-first, so the FIRST entry for a branch is its latest PR — keep it.\n if (branch !== '' && pr > 0 && !out.has(branch)) {\n out.set(branch, new PrRef(pr, entry.state ?? ''));\n }\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return out;\n }\n }\n\n private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch => new DeletableBranch(\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.sha ?? '',\n entry.commits ?? -1,\n entry.prState ?? '',\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n ));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => {\n const verdict = new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n );\n verdict.unlockBeforeRemove = entry.unlockBeforeRemove ?? false;\n return verdict;\n });\n }\n\n // Local git, bounded (CommandTimeouts), stdout trimmed; ok=false on a non-zero exit, a kill or a failed start.\n private async git(repoRoot: string, args: string[]): Promise<CapturedOutput> {\n const result = await this.commands.localGit(args, repoRoot);\n return new CapturedOutput(result.ok, result.ok ? result.stdout.trim() : '');\n }\n\n // `gh`, bounded by the network ceiling, stdout trimmed.\n private async gh(repoRoot: string, args: string[]): Promise<CapturedOutput> {\n const result = await this.commands.gh(args, repoRoot);\n return new CapturedOutput(result.ok, result.ok ? result.stdout.trim() : '');\n }\n}\n"]}
@@ -110,7 +110,7 @@ export declare class WorktreeLockVerdicts {
110
110
  * a path with no reap to veto there is nothing to ask, and asking would invite a later reader to
111
111
  * let the answer decide something.
112
112
  */
113
- gather(tree: Worktree, branchDeletable: boolean, ignoreStaleAgentLocks: boolean): LockEvidence;
113
+ gather(tree: Worktree, branchDeletable: boolean, ignoreStaleAgentLocks: boolean): Promise<LockEvidence>;
114
114
  /**
115
115
  * Spare this locked worktree, or judge it on its branch like any unlocked one — with the sentence
116
116
  * that says which piece of evidence decided it.
@@ -135,12 +135,12 @@ let WorktreeLockVerdicts = class WorktreeLockVerdicts {
135
135
  * a path with no reap to veto there is nothing to ask, and asking would invite a later reader to
136
136
  * let the answer decide something.
137
137
  */
138
- gather(tree, branchDeletable, ignoreStaleAgentLocks) {
138
+ async gather(tree, branchDeletable, ignoreStaleAgentLocks) {
139
139
  const lock = this.agentLocks.parse(tree.lockReason);
140
140
  if (lock === null || (!branchDeletable && !ignoreStaleAgentLocks)) {
141
141
  return new LockEvidence(tree.lockReason, exports.HARNESS_NOT_CONSULTED, false, '', branchDeletable, ignoreStaleAgentLocks);
142
142
  }
143
- const held = this.worktrees.workInFlight(tree.path);
143
+ const held = await this.worktrees.workInFlight(tree.path);
144
144
  return new LockEvidence(tree.lockReason, this.harness.activityOf(lock.agent, tree.path), held.held, held.reason, branchDeletable, ignoreStaleAgentLocks);
145
145
  }
146
146
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"worktree-lock-verdicts.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/worktree-lock-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\n\nimport { AgentWorktreeLock, AgentWorktreeLockReader } from './agent-worktree-lock';\nimport {\n AGENT_ACTIVITY_LIVE,\n AGENT_ACTIVITY_UNKNOWN,\n AgentActivity,\n HarnessAgentActivityReader,\n} from './harness-agent-activity';\nimport { DeletableWorktree } from './merged-branch-verdicts';\nimport { Worktree, WorktreeService } from './worktrees';\n\n/**\n * What a `git worktree lock` DOES to a worktree's cleanup verdict.\n *\n * Separate from agent-worktree-lock.ts (which only parses a lock reason) and from\n * harness-agent-activity.ts (which only asks the harness about an agent): this is the POLICY built on\n * top of that evidence — what stops a reap, what a spared lock is allowed to CLAIM about who took it,\n * and what an overridden one has to leave on the verdict so the reaper knows to clear it.\n *\n * ─── WHY THE POLICY CHANGED ──────────────────────────────────────────────────────────────────────\n * It used to be one question, asked of the kernel: is the pid in the lock reason still running? That\n * pid is the Claude Code SESSION process, shared by every subagent, so the answer was yes for the\n * whole life of a session and every agent worktree was spared forever — nine against a cap of five,\n * with one agent running, each carrying the sentence \"that agent is working in here\" about agents\n * whose PRs had already merged. Both halves were defects: the reap never happened, and the message\n * asserted something nobody knew.\n *\n * ─── THE DECISION TABLE, AND THE ONE RULE BEHIND IT ──────────────────────────────────────────────\n *\n * HARNESS STATE MAY ONLY VETO A REAP. IT MAY NEVER LICENSE ONE.\n *\n * The licence comes solely from the branch/commit evidence wp-cleanup already computes and already\n * gets right. So, for a lock this repo can read as the harness's own:\n *\n * branch evidence does NOT say deletable → SPARE. The harness is not even consulted; it\n * cannot promote a spare into a reap.\n * deletable, but the worktree is DIRTY → SPARE, unconditionally. Uncommitted or\n * untracked files are work no archive tag can\n * bring back. This one overrides everything,\n * including the explicit flag.\n * deletable, clean, harness says the agent is\n * mid-tool-call and writing → SPARE. The veto.\n * deletable, clean, harness says returned /\n * long-stale / cannot tell → judge it on its branch like any unlocked tree,\n * clearing the lock on the way out.\n *\n * `--ignore-stale-locks` moves ONE line of that table: it lets a locked worktree whose branch is not\n * provably dead be classified on its real branch and commit state anyway (so a zero-commit husk gets\n * reaped and a worktree holding unique commits reaches the numbered block a human can answer). It\n * does not touch the dirty rail or the live-agent veto, because neither of those is what the caller\n * is complaining about when they pass it.\n *\n * ─── SCOPE: AGENT LOCKS ONLY ─────────────────────────────────────────────────────────────────────\n * Every override applies ONLY to a lock this file can read as the harness's own. A lock reason naming\n * anything else — a human's \"dean is debugging\", or nothing at all — is still an instruction from\n * somebody, and wp-cleanup still obeys it.\n */\n\n/**\n * The exact phrase a spared \"we cannot tell\" verdict carries, exported so wp-cleanup can spot those\n * worktrees and offer the flag that judges them on their branch instead.\n *\n * ONE spelling, shared: the alternative is the caller re-deriving the same condition from a second\n * copy of the sentence, which is how a message and the code that keys off it drift apart.\n */\nexport const LOCK_LIVENESS_UNVERIFIABLE = 'whether that agent is still running cannot be verified';\n\n/**\n * The stand-in for \"the harness was never asked\", used on the paths where asking could not change the\n * answer. It is UNKNOWN, so a caller that consults it anyway still fails safe.\n */\nexport const HARNESS_NOT_CONSULTED = new AgentActivity(\n AGENT_ACTIVITY_UNKNOWN, 'the harness was not consulted');\n\n// Data-only (per CLAUDE.md, classes for data). Everything `decide` rules on, as one value — see\n// `gather`, which is the only thing that builds one outside a test.\nexport class LockEvidence {\n /** The lock reason verbatim, straight out of `git worktree list --porcelain`. */\n lockReason: string;\n /** What the harness says about the agent named in that reason, or HARNESS_NOT_CONSULTED. */\n activity: AgentActivity;\n /** Does `git status --porcelain` report anything, or could it not answer? Either way: held. */\n workInFlight: boolean;\n /** Why it is held, in git's terms — printed verbatim, because the two reasons differ for a human. */\n workInFlightReason: string;\n /** Is the branch this worktree holds ALREADY provably dead by the ordinary verdicts? */\n branchDeletable: boolean;\n /** The caller said to treat a standing agent lock as no evidence at all. */\n ignoreStaleAgentLocks: boolean;\n\n constructor(\n lockReason: string,\n activity: AgentActivity,\n workInFlight: boolean,\n workInFlightReason: string,\n branchDeletable: boolean,\n ignoreStaleAgentLocks: boolean,\n ) {\n this.lockReason = lockReason;\n this.activity = activity;\n this.workInFlight = workInFlight;\n this.workInFlightReason = workInFlightReason;\n this.branchDeletable = branchDeletable;\n this.ignoreStaleAgentLocks = ignoreStaleAgentLocks;\n }\n}\n\n/**\n * Data-only. The one verdict this file produces: spare or override, the sentence that says WHY, and\n * the agent lock the reaper will have to clear if the worktree goes.\n *\n * ONE class rather than a spare-path method and an override-path method, because the two answers are\n * mutually exclusive readings of the same evidence, and two methods is how they drift apart — which\n * is precisely what happened before: the old spared message and the old stale-lock test were computed\n * from the same pid by different code, and only one of them was ever corrected.\n */\nexport class LockDecision {\n /** True: leave this worktree alone, and `reason` is the whole story. */\n spare: boolean;\n /** Spared: why it was left. Overridden: the clause appended to the branch's own verdict. */\n reason: string;\n /** The agent lock to clear on the way out — null when nothing may be cleared. */\n lock: AgentWorktreeLock | null;\n\n constructor(spare: boolean, reason: string, lock: AgentWorktreeLock | null) {\n this.spare = spare;\n this.reason = reason;\n this.lock = lock;\n }\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeLockVerdicts {\n // Defaulted like BranchReaper's collaborators, so the non-DI call sites can just\n // `new WorktreeLockVerdicts()` while inversify still injects the singletons from a container.\n constructor(\n private readonly agentLocks: AgentWorktreeLockReader = new AgentWorktreeLockReader(),\n private readonly harness: HarnessAgentActivityReader = new HarnessAgentActivityReader(),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n ) {}\n\n /**\n * Collect what `decide` rules on — and, just as deliberately, DECLINE to collect the two\n * expensive facts when they cannot change the answer.\n *\n * `git status --porcelain` and a walk of the harness's state tree are gathered only when a reap\n * is otherwise on the table: the lock is one this repo can read as the harness's own, and either\n * the branch is already provably dead or the caller asked for locks to be ignored. That is not an\n * optimisation dressed up as a rule — harness state may only VETO a reap, never license one, so on\n * a path with no reap to veto there is nothing to ask, and asking would invite a later reader to\n * let the answer decide something.\n */\n gather(tree: Worktree, branchDeletable: boolean, ignoreStaleAgentLocks: boolean): LockEvidence {\n const lock = this.agentLocks.parse(tree.lockReason);\n if (lock === null || (!branchDeletable && !ignoreStaleAgentLocks)) {\n return new LockEvidence(tree.lockReason, HARNESS_NOT_CONSULTED, false, '',\n branchDeletable, ignoreStaleAgentLocks);\n }\n const held = this.worktrees.workInFlight(tree.path);\n return new LockEvidence(\n tree.lockReason, this.harness.activityOf(lock.agent, tree.path), held.held, held.reason,\n branchDeletable, ignoreStaleAgentLocks);\n }\n\n /**\n * Spare this locked worktree, or judge it on its branch like any unlocked one — with the sentence\n * that says which piece of evidence decided it.\n *\n * The order below IS the decision table in the class header, in the same order, for the same\n * reasons. Read it there.\n */\n decide(evidence: LockEvidence): LockDecision {\n const lock = this.agentLocks.parse(evidence.lockReason);\n if (lock === null) return new LockDecision(true, this.foreignLockReason(evidence.lockReason), null);\n\n if (!evidence.branchDeletable && !evidence.ignoreStaleAgentLocks) {\n return new LockDecision(true, this.unverifiableReason(lock), null);\n }\n if (evidence.workInFlight) {\n return new LockDecision(true,\n `locked by claude agent ${lock.agent}, and the worktree ${evidence.workInFlightReason} — `\n + 'nothing archives that, so it is left exactly where it is', null);\n }\n if (evidence.activity.state === AGENT_ACTIVITY_LIVE) {\n return new LockDecision(true,\n `locked by claude agent ${lock.agent}, and the Claude Code harness says `\n + `${evidence.activity.detail} — that agent is working in here`, null);\n }\n if (evidence.branchDeletable) {\n return new LockDecision(false,\n `the lock from claude agent ${lock.agent} did not stop this — the worktree is clean, its `\n + `branch is already dead, and ${evidence.activity.detail}`, lock);\n }\n return new LockDecision(false,\n `the lock from claude agent ${lock.agent} was treated as no evidence, as asked`, lock);\n }\n\n /**\n * WHY a locked worktree was left alone when the lock is NOT the harness's — reporting what the\n * reason SAYS, never who we imagine wrote it.\n *\n * This used to read `locked by a human — do not touch` for every lock in existence, which named an\n * actor nothing in the evidence identifies: the lock reason is the ONLY thing we have, a human may\n * have locked it, so may some other tool, and asserting either is the same defect whichever way it\n * lands.\n */\n private foreignLockReason(lockReason: string): string {\n if (lockReason === '') {\n return 'locked with no reason recorded — nothing says who locked it or why, so it is left alone';\n }\n return `locked, reason \"${lockReason}\" — that is not a claude agent lock, so who locked it `\n + 'is unknown; left alone';\n }\n\n /**\n * The sentence that replaced \"pid N still running — that agent is working in here\".\n *\n * It says only what is known: which agent the reason names, that the pid in it cannot answer the\n * question because it is the shared session process, and that nothing else made the worktree\n * provably dead either. No claim about anybody working — and no claim about the harness, which on\n * this path was deliberately never asked, because its answer could not have changed the outcome.\n */\n private unverifiableReason(lock: AgentWorktreeLock): string {\n return `locked by claude agent ${lock.agent}; ${LOCK_LIVENESS_UNVERIFIABLE} — the recorded pid `\n + `${String(lock.pid)} is the shared Claude Code session process, not the agent's. Its branch `\n + 'is not provably dead either, so it is left alone';\n }\n\n /**\n * Stamp an overridden verdict with the lock the reap must CLEAR: git refuses to remove a locked\n * worktree, and the reason has to say why a lock is being overridden at all.\n *\n * Applied to every verdict the fall-through can produce, not just the deletable one, because the\n * spared cases are exactly where a human reads the reason and asks \"wasn't that locked?\".\n */\n annotate(verdict: DeletableWorktree, decision: LockDecision | null): DeletableWorktree {\n if (decision === null || decision.spare || decision.lock === null) return verdict;\n verdict.unlockBeforeRemove = true;\n verdict.reason = `${verdict.reason}; ${decision.reason}`;\n return verdict;\n }\n}\n"]}
1
+ {"version":3,"file":"worktree-lock-verdicts.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/worktree-lock-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\n\nimport { AgentWorktreeLock, AgentWorktreeLockReader } from './agent-worktree-lock';\nimport {\n AGENT_ACTIVITY_LIVE,\n AGENT_ACTIVITY_UNKNOWN,\n AgentActivity,\n HarnessAgentActivityReader,\n} from './harness-agent-activity';\nimport { DeletableWorktree } from './merged-branch-verdicts';\nimport { Worktree, WorktreeService } from './worktrees';\n\n/**\n * What a `git worktree lock` DOES to a worktree's cleanup verdict.\n *\n * Separate from agent-worktree-lock.ts (which only parses a lock reason) and from\n * harness-agent-activity.ts (which only asks the harness about an agent): this is the POLICY built on\n * top of that evidence — what stops a reap, what a spared lock is allowed to CLAIM about who took it,\n * and what an overridden one has to leave on the verdict so the reaper knows to clear it.\n *\n * ─── WHY THE POLICY CHANGED ──────────────────────────────────────────────────────────────────────\n * It used to be one question, asked of the kernel: is the pid in the lock reason still running? That\n * pid is the Claude Code SESSION process, shared by every subagent, so the answer was yes for the\n * whole life of a session and every agent worktree was spared forever — nine against a cap of five,\n * with one agent running, each carrying the sentence \"that agent is working in here\" about agents\n * whose PRs had already merged. Both halves were defects: the reap never happened, and the message\n * asserted something nobody knew.\n *\n * ─── THE DECISION TABLE, AND THE ONE RULE BEHIND IT ──────────────────────────────────────────────\n *\n * HARNESS STATE MAY ONLY VETO A REAP. IT MAY NEVER LICENSE ONE.\n *\n * The licence comes solely from the branch/commit evidence wp-cleanup already computes and already\n * gets right. So, for a lock this repo can read as the harness's own:\n *\n * branch evidence does NOT say deletable → SPARE. The harness is not even consulted; it\n * cannot promote a spare into a reap.\n * deletable, but the worktree is DIRTY → SPARE, unconditionally. Uncommitted or\n * untracked files are work no archive tag can\n * bring back. This one overrides everything,\n * including the explicit flag.\n * deletable, clean, harness says the agent is\n * mid-tool-call and writing → SPARE. The veto.\n * deletable, clean, harness says returned /\n * long-stale / cannot tell → judge it on its branch like any unlocked tree,\n * clearing the lock on the way out.\n *\n * `--ignore-stale-locks` moves ONE line of that table: it lets a locked worktree whose branch is not\n * provably dead be classified on its real branch and commit state anyway (so a zero-commit husk gets\n * reaped and a worktree holding unique commits reaches the numbered block a human can answer). It\n * does not touch the dirty rail or the live-agent veto, because neither of those is what the caller\n * is complaining about when they pass it.\n *\n * ─── SCOPE: AGENT LOCKS ONLY ─────────────────────────────────────────────────────────────────────\n * Every override applies ONLY to a lock this file can read as the harness's own. A lock reason naming\n * anything else — a human's \"dean is debugging\", or nothing at all — is still an instruction from\n * somebody, and wp-cleanup still obeys it.\n */\n\n/**\n * The exact phrase a spared \"we cannot tell\" verdict carries, exported so wp-cleanup can spot those\n * worktrees and offer the flag that judges them on their branch instead.\n *\n * ONE spelling, shared: the alternative is the caller re-deriving the same condition from a second\n * copy of the sentence, which is how a message and the code that keys off it drift apart.\n */\nexport const LOCK_LIVENESS_UNVERIFIABLE = 'whether that agent is still running cannot be verified';\n\n/**\n * The stand-in for \"the harness was never asked\", used on the paths where asking could not change the\n * answer. It is UNKNOWN, so a caller that consults it anyway still fails safe.\n */\nexport const HARNESS_NOT_CONSULTED = new AgentActivity(\n AGENT_ACTIVITY_UNKNOWN, 'the harness was not consulted');\n\n// Data-only (per CLAUDE.md, classes for data). Everything `decide` rules on, as one value — see\n// `gather`, which is the only thing that builds one outside a test.\nexport class LockEvidence {\n /** The lock reason verbatim, straight out of `git worktree list --porcelain`. */\n lockReason: string;\n /** What the harness says about the agent named in that reason, or HARNESS_NOT_CONSULTED. */\n activity: AgentActivity;\n /** Does `git status --porcelain` report anything, or could it not answer? Either way: held. */\n workInFlight: boolean;\n /** Why it is held, in git's terms — printed verbatim, because the two reasons differ for a human. */\n workInFlightReason: string;\n /** Is the branch this worktree holds ALREADY provably dead by the ordinary verdicts? */\n branchDeletable: boolean;\n /** The caller said to treat a standing agent lock as no evidence at all. */\n ignoreStaleAgentLocks: boolean;\n\n constructor(\n lockReason: string,\n activity: AgentActivity,\n workInFlight: boolean,\n workInFlightReason: string,\n branchDeletable: boolean,\n ignoreStaleAgentLocks: boolean,\n ) {\n this.lockReason = lockReason;\n this.activity = activity;\n this.workInFlight = workInFlight;\n this.workInFlightReason = workInFlightReason;\n this.branchDeletable = branchDeletable;\n this.ignoreStaleAgentLocks = ignoreStaleAgentLocks;\n }\n}\n\n/**\n * Data-only. The one verdict this file produces: spare or override, the sentence that says WHY, and\n * the agent lock the reaper will have to clear if the worktree goes.\n *\n * ONE class rather than a spare-path method and an override-path method, because the two answers are\n * mutually exclusive readings of the same evidence, and two methods is how they drift apart — which\n * is precisely what happened before: the old spared message and the old stale-lock test were computed\n * from the same pid by different code, and only one of them was ever corrected.\n */\nexport class LockDecision {\n /** True: leave this worktree alone, and `reason` is the whole story. */\n spare: boolean;\n /** Spared: why it was left. Overridden: the clause appended to the branch's own verdict. */\n reason: string;\n /** The agent lock to clear on the way out — null when nothing may be cleared. */\n lock: AgentWorktreeLock | null;\n\n constructor(spare: boolean, reason: string, lock: AgentWorktreeLock | null) {\n this.spare = spare;\n this.reason = reason;\n this.lock = lock;\n }\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeLockVerdicts {\n // Defaulted like BranchReaper's collaborators, so the non-DI call sites can just\n // `new WorktreeLockVerdicts()` while inversify still injects the singletons from a container.\n constructor(\n private readonly agentLocks: AgentWorktreeLockReader = new AgentWorktreeLockReader(),\n private readonly harness: HarnessAgentActivityReader = new HarnessAgentActivityReader(),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n ) {}\n\n /**\n * Collect what `decide` rules on — and, just as deliberately, DECLINE to collect the two\n * expensive facts when they cannot change the answer.\n *\n * `git status --porcelain` and a walk of the harness's state tree are gathered only when a reap\n * is otherwise on the table: the lock is one this repo can read as the harness's own, and either\n * the branch is already provably dead or the caller asked for locks to be ignored. That is not an\n * optimisation dressed up as a rule — harness state may only VETO a reap, never license one, so on\n * a path with no reap to veto there is nothing to ask, and asking would invite a later reader to\n * let the answer decide something.\n */\n async gather(tree: Worktree, branchDeletable: boolean, ignoreStaleAgentLocks: boolean): Promise<LockEvidence> {\n const lock = this.agentLocks.parse(tree.lockReason);\n if (lock === null || (!branchDeletable && !ignoreStaleAgentLocks)) {\n return new LockEvidence(tree.lockReason, HARNESS_NOT_CONSULTED, false, '',\n branchDeletable, ignoreStaleAgentLocks);\n }\n const held = await this.worktrees.workInFlight(tree.path);\n return new LockEvidence(\n tree.lockReason, this.harness.activityOf(lock.agent, tree.path), held.held, held.reason,\n branchDeletable, ignoreStaleAgentLocks);\n }\n\n /**\n * Spare this locked worktree, or judge it on its branch like any unlocked one — with the sentence\n * that says which piece of evidence decided it.\n *\n * The order below IS the decision table in the class header, in the same order, for the same\n * reasons. Read it there.\n */\n decide(evidence: LockEvidence): LockDecision {\n const lock = this.agentLocks.parse(evidence.lockReason);\n if (lock === null) return new LockDecision(true, this.foreignLockReason(evidence.lockReason), null);\n\n if (!evidence.branchDeletable && !evidence.ignoreStaleAgentLocks) {\n return new LockDecision(true, this.unverifiableReason(lock), null);\n }\n if (evidence.workInFlight) {\n return new LockDecision(true,\n `locked by claude agent ${lock.agent}, and the worktree ${evidence.workInFlightReason} — `\n + 'nothing archives that, so it is left exactly where it is', null);\n }\n if (evidence.activity.state === AGENT_ACTIVITY_LIVE) {\n return new LockDecision(true,\n `locked by claude agent ${lock.agent}, and the Claude Code harness says `\n + `${evidence.activity.detail} — that agent is working in here`, null);\n }\n if (evidence.branchDeletable) {\n return new LockDecision(false,\n `the lock from claude agent ${lock.agent} did not stop this — the worktree is clean, its `\n + `branch is already dead, and ${evidence.activity.detail}`, lock);\n }\n return new LockDecision(false,\n `the lock from claude agent ${lock.agent} was treated as no evidence, as asked`, lock);\n }\n\n /**\n * WHY a locked worktree was left alone when the lock is NOT the harness's — reporting what the\n * reason SAYS, never who we imagine wrote it.\n *\n * This used to read `locked by a human — do not touch` for every lock in existence, which named an\n * actor nothing in the evidence identifies: the lock reason is the ONLY thing we have, a human may\n * have locked it, so may some other tool, and asserting either is the same defect whichever way it\n * lands.\n */\n private foreignLockReason(lockReason: string): string {\n if (lockReason === '') {\n return 'locked with no reason recorded — nothing says who locked it or why, so it is left alone';\n }\n return `locked, reason \"${lockReason}\" — that is not a claude agent lock, so who locked it `\n + 'is unknown; left alone';\n }\n\n /**\n * The sentence that replaced \"pid N still running — that agent is working in here\".\n *\n * It says only what is known: which agent the reason names, that the pid in it cannot answer the\n * question because it is the shared session process, and that nothing else made the worktree\n * provably dead either. No claim about anybody working — and no claim about the harness, which on\n * this path was deliberately never asked, because its answer could not have changed the outcome.\n */\n private unverifiableReason(lock: AgentWorktreeLock): string {\n return `locked by claude agent ${lock.agent}; ${LOCK_LIVENESS_UNVERIFIABLE} — the recorded pid `\n + `${String(lock.pid)} is the shared Claude Code session process, not the agent's. Its branch `\n + 'is not provably dead either, so it is left alone';\n }\n\n /**\n * Stamp an overridden verdict with the lock the reap must CLEAR: git refuses to remove a locked\n * worktree, and the reason has to say why a lock is being overridden at all.\n *\n * Applied to every verdict the fall-through can produce, not just the deletable one, because the\n * spared cases are exactly where a human reads the reason and asks \"wasn't that locked?\".\n */\n annotate(verdict: DeletableWorktree, decision: LockDecision | null): DeletableWorktree {\n if (decision === null || decision.spare || decision.lock === null) return verdict;\n verdict.unlockBeforeRemove = true;\n verdict.reason = `${verdict.reason}; ${decision.reason}`;\n return verdict;\n }\n}\n"]}
@@ -1,3 +1,4 @@
1
+ import { GitProcessApi } from '@webpieces/tooling-common';
1
2
  import { BranchArchiver } from './branch-archiver';
2
3
  import { BranchMutationLog, MutationVerb } from './branch-mutation-log';
3
4
  import { DeletableWorktree } from './merged-branch-verdicts';
@@ -62,7 +63,8 @@ export declare class WorktreeReaper {
62
63
  private readonly worktrees;
63
64
  private readonly mutationLog;
64
65
  private readonly archiver;
65
- constructor(worktrees?: WorktreeService, mutationLog?: BranchMutationLog, archiver?: BranchArchiver);
66
+ private readonly git;
67
+ constructor(worktrees?: WorktreeService, mutationLog?: BranchMutationLog, archiver?: BranchArchiver, git?: GitProcessApi);
66
68
  /**
67
69
  * Reap every worktree in `targets`, skipping any the safety rails refuse.
68
70
  *
@@ -74,7 +76,7 @@ export declare class WorktreeReaper {
74
76
  * passes human-approved probably-dead ones on a second call. The safety rails below apply to both:
75
77
  * no answer at a prompt can authorise removing your own cwd or the primary clone.
76
78
  */
77
- reapWorktrees(repoRoot: string, cwd: string, verb: MutationVerb, targets: DeletableWorktree[], retention?: string): WorktreeReapResult;
79
+ reapWorktrees(repoRoot: string, cwd: string, verb: MutationVerb, targets: DeletableWorktree[], retention?: string): Promise<WorktreeReapResult>;
78
80
  /**
79
81
  * The two directories that must never be removed, resolved to absolute paths so a relative
80
82
  * `../foo` in a verdict can still be compared against them.
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const child_process_1 = require("child_process");
6
5
  const path = tslib_1.__importStar(require("path"));
7
6
  const inversify_1 = require("inversify");
7
+ const tooling_common_1 = require("@webpieces/tooling-common");
8
8
  const branch_archiver_1 = require("./branch-archiver");
9
9
  const branch_mutation_log_1 = require("./branch-mutation-log");
10
10
  const merged_branch_verdicts_1 = require("./merged-branch-verdicts");
@@ -95,16 +95,29 @@ class ProtectedPaths {
95
95
  this.current = current;
96
96
  }
97
97
  }
98
+ // Internal: one git call's verdict, trimmed stdout, and stderr so a refused removal can be reported. Data only.
99
+ class CapturedOutput {
100
+ ok;
101
+ out;
102
+ err;
103
+ constructor(ok, out, err) {
104
+ this.ok = ok;
105
+ this.out = out;
106
+ this.err = err;
107
+ }
108
+ }
98
109
  let WorktreeReaper = class WorktreeReaper {
99
110
  worktrees;
100
111
  mutationLog;
101
112
  archiver;
113
+ git;
102
114
  // Defaulted like BranchReaper's collaborators, so the non-DI call sites can just
103
115
  // `new WorktreeReaper()` while inversify still injects the singletons from a container.
104
- constructor(worktrees = new worktrees_1.WorktreeService(), mutationLog = new branch_mutation_log_1.BranchMutationLog(), archiver = new branch_archiver_1.BranchArchiver()) {
116
+ constructor(worktrees = new worktrees_1.WorktreeService(), mutationLog = new branch_mutation_log_1.BranchMutationLog(), archiver = new branch_archiver_1.BranchArchiver(), git = new tooling_common_1.GitProcessClient()) {
105
117
  this.worktrees = worktrees;
106
118
  this.mutationLog = mutationLog;
107
119
  this.archiver = archiver;
120
+ this.git = git;
108
121
  }
109
122
  /**
110
123
  * Reap every worktree in `targets`, skipping any the safety rails refuse.
@@ -118,7 +131,7 @@ let WorktreeReaper = class WorktreeReaper {
118
131
  * no answer at a prompt can authorise removing your own cwd or the primary clone.
119
132
  */
120
133
  // eslint-disable-next-line @typescript-eslint/max-params
121
- reapWorktrees(repoRoot, cwd, verb, targets, retention = branch_archiver_1.BRANCH_RETENTION_ARCHIVE_TAG) {
134
+ async reapWorktrees(repoRoot, cwd, verb, targets, retention = branch_archiver_1.BRANCH_RETENTION_ARCHIVE_TAG) {
122
135
  // 'keep' means "delete nothing, ever" — the reap degrades to a pure report, exactly as it does
123
136
  // for branches, so a repo that opted out of destructive cleanup still SEES what would have gone.
124
137
  if (retention === branch_archiver_1.BRANCH_RETENTION_KEEP)
@@ -133,7 +146,7 @@ let WorktreeReaper = class WorktreeReaper {
133
146
  spared.push(new merged_branch_verdicts_1.DeletableWorktree(target.path, target.branch, refusal, target.pr, false, target.classification));
134
147
  continue;
135
148
  }
136
- const outcome = this.removeOne(repoRoot, verb, target, retention);
149
+ const outcome = await this.removeOne(repoRoot, verb, target, retention);
137
150
  if (outcome.ok)
138
151
  reaped.push(outcome);
139
152
  else
@@ -208,11 +221,11 @@ let WorktreeReaper = class WorktreeReaper {
208
221
  * `git worktree prune`. There is also nothing to archive from a directory that no longer exists —
209
222
  * the branch itself is still archived, since it may well still hold the only copy of some work.
210
223
  */
211
- removeOne(repoRoot, verb, target, retention) {
224
+ async removeOne(repoRoot, verb, target, retention) {
212
225
  // Tip first: after the branch is gone there is nothing left to resolve, and the audit line's
213
226
  // whole job is to record what was destroyed while it still exists.
214
- const sha = target.branch !== '' ? this.revParse(repoRoot, target.branch) : '';
215
- const archive = this.archiveBranch(repoRoot, target, retention, sha);
227
+ const sha = target.branch !== '' ? await this.revParse(repoRoot, target.branch) : '';
228
+ const archive = await this.archiveBranch(repoRoot, target, retention, sha);
216
229
  if (!archive.ok)
217
230
  return this.archiveFailed(repoRoot, verb, target, sha, archive);
218
231
  // A worktree left LOCKED by an agent that has since died: git refuses `worktree remove` while
@@ -222,15 +235,15 @@ let WorktreeReaper = class WorktreeReaper {
222
235
  // verdict that reaches here. A failed unlock is not reported on its own: the removal that
223
236
  // follows fails with git's own words, which is the message a human can actually act on.
224
237
  const unlockedOk = target.unlockBeforeRemove && target.classification !== merged_branch_verdicts_1.CLASSIFICATION_PRUNABLE
225
- && this.capture(repoRoot, ['worktree', 'unlock', target.path]).ok;
238
+ && (await this.capture(repoRoot, ['worktree', 'unlock', target.path])).ok;
226
239
  const removed = target.classification === merged_branch_verdicts_1.CLASSIFICATION_PRUNABLE
227
- ? this.capture(repoRoot, ['worktree', 'prune'])
228
- : this.capture(repoRoot, ['worktree', 'remove', target.path]);
240
+ ? await this.capture(repoRoot, ['worktree', 'prune'])
241
+ : await this.capture(repoRoot, ['worktree', 'remove', target.path]);
229
242
  if (!removed.ok)
230
243
  return this.removalFailed(repoRoot, verb, target, sha, archive, removed.err);
231
244
  // Only NOW may the branch go: git refuses while a worktree still holds it.
232
245
  const branchDeleted = target.branch === ''
233
- || this.capture(repoRoot, ['branch', '-D', target.branch]).ok;
246
+ || (await this.capture(repoRoot, ['branch', '-D', target.branch])).ok;
234
247
  const result = new ReapedWorktree(target.path, target.branch, sha, target.reason, target.pr, true, '');
235
248
  result.archiveTag = archive.tag;
236
249
  result.branchDeleted = branchDeleted;
@@ -246,7 +259,7 @@ let WorktreeReaper = class WorktreeReaper {
246
259
  }
247
260
  // Archiving is skipped for a detached worktree (no branch to tag) and under retention 'delete'.
248
261
  // Both report ok=true with an empty tag: there is nothing to archive, which is not a failure.
249
- archiveBranch(repoRoot, target, retention, sha) {
262
+ async archiveBranch(repoRoot, target, retention, sha) {
250
263
  if (target.branch === '' || retention !== branch_archiver_1.BRANCH_RETENTION_ARCHIVE_TAG) {
251
264
  return new branch_archiver_1.ArchiveResult('', sha, true, '');
252
265
  }
@@ -294,18 +307,17 @@ let WorktreeReaper = class WorktreeReaper {
294
307
  return `git worktree add ${target.path} ${ref}`;
295
308
  return `git worktree add -b ${target.branch} ${target.path} ${ref}`;
296
309
  }
297
- revParse(repoRoot, ref) {
298
- const result = this.capture(repoRoot, ['rev-parse', ref]);
310
+ async revParse(repoRoot, ref) {
311
+ const result = await this.capture(repoRoot, ['rev-parse', ref]);
299
312
  return result.ok ? result.out : '';
300
313
  }
301
314
  // Run a git command capturing trimmed stdout/stderr; ok=false on spawn failure or non-zero exit.
302
- capture(repoRoot, args) {
303
- const result = (0, child_process_1.spawnSync)('git', args, { cwd: repoRoot, encoding: 'utf8' });
304
- const err = typeof result.stderr === 'string' ? result.stderr.trim() : '';
305
- if (result.status !== 0 || typeof result.stdout !== 'string') {
306
- return { ok: false, out: '', err: err !== '' ? err : 'git command failed' };
307
- }
308
- return { ok: true, out: result.stdout.trim(), err };
315
+ async capture(repoRoot, args) {
316
+ const result = await this.git.run(args, repoRoot);
317
+ const err = result.stderr.trim();
318
+ if (!result.ok)
319
+ return new CapturedOutput(false, '', err !== '' ? err : 'git command failed');
320
+ return new CapturedOutput(true, result.stdout.trim(), err);
309
321
  }
310
322
  };
311
323
  exports.WorktreeReaper = WorktreeReaper;
@@ -313,6 +325,7 @@ exports.WorktreeReaper = WorktreeReaper = tslib_1.__decorate([
313
325
  (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
314
326
  tslib_1.__metadata("design:paramtypes", [worktrees_1.WorktreeService,
315
327
  branch_mutation_log_1.BranchMutationLog,
316
- branch_archiver_1.BranchArchiver])
328
+ branch_archiver_1.BranchArchiver,
329
+ tooling_common_1.GitProcessApi])
317
330
  ], WorktreeReaper);
318
331
  //# sourceMappingURL=worktree-reaper.js.map