@webpieces/rules-config 0.4.496 → 0.4.498

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.4.496",
3
+ "version": "0.4.498",
4
4
  "description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -1,5 +1,5 @@
1
1
  export type MutationVerb = 'wp-start-update' | 'wp-finish-update' | 'wp-start-upsert-pr' | 'wp-finish-upsert-pr' | 'wp-cleanup' | 'auto-reap';
2
- export type MutationPhase = 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME' | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END' | 'REAP';
2
+ export type MutationPhase = 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME' | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END' | 'REAP' | 'REAP_WORKTREE';
3
3
  export declare class BranchMutationEvent {
4
4
  verb: MutationVerb;
5
5
  phase: MutationPhase;
@@ -13,6 +13,7 @@ export declare class BranchMutationEvent {
13
13
  artifacts: string[];
14
14
  sha: string;
15
15
  archiveTag: string;
16
+ worktreePath: string;
16
17
  constructor(verb: MutationVerb, phase: MutationPhase);
17
18
  }
18
19
  /** Appends branch-mutation audit lines. `@injectable(bindingScopeValues.Singleton)` so it's injectable + drawn in the design. */
@@ -25,6 +26,16 @@ export declare class BranchMutationLog {
25
26
  */
26
27
  logBranchMutation(root: string, event: BranchMutationEvent): void;
27
28
  private formatDetail;
29
+ /**
30
+ * The worktree flavour of the sha/recover token: path, tip, archive tag and the ONE command that
31
+ * puts the directory AND the branch back together.
32
+ *
33
+ * `git worktree add -b <branch> <path> <ref>` is verified by hand and in worktree-reaper.spec.ts —
34
+ * plain `git worktree add <path> <tag>` would restore the files at a DETACHED HEAD, silently losing
35
+ * the branch name the reap destroyed. Falls back to the sha when nothing was archived (retention
36
+ * 'delete'), and to a bare `git worktree add <path>` when there was no branch at all (detached).
37
+ */
38
+ private worktreeDetail;
28
39
  private oneLine;
29
40
  private rotateLogFile;
30
41
  }
@@ -40,6 +40,11 @@ class BranchMutationEvent {
40
40
  // survives `gc` and reflog expiry and can be pushed, whereas a bare sha is only recoverable while
41
41
  // this clone's reflog still holds it. Empty when nothing was tagged (retention policy 'delete').
42
42
  archiveTag = '';
43
+ // The directory a REAP_WORKTREE removed. When set, `recover=` becomes the WORKTREE form
44
+ // (`git worktree add -b <branch> <path> <ref>`) rather than the bare `git branch` form: putting the
45
+ // ref back does not put the directory back, and a recover line that restores half of what was
46
+ // destroyed is worse than none — it reads as done. Empty for every mutation that removes no directory.
47
+ worktreePath = '';
43
48
  constructor(verb, phase) {
44
49
  this.verb = verb;
45
50
  this.phase = phase;
@@ -98,7 +103,10 @@ let BranchMutationLog = class BranchMutationLog {
98
103
  parts.push(`outcome=${event.outcome}`);
99
104
  // Emitted as one unit so the hash is never separated from the command that undoes the delete.
100
105
  // Prefer the archive TAG as the recover ref when there is one — it does not expire.
101
- if (event.sha !== '' && event.archiveTag !== '') {
106
+ if (event.worktreePath !== '') {
107
+ parts.push(this.worktreeDetail(event));
108
+ }
109
+ else if (event.sha !== '' && event.archiveTag !== '') {
102
110
  parts.push(`sha=${event.sha} archiveTag=${event.archiveTag} ` +
103
111
  `recover=git checkout -b ${event.fromBranch || '?'} ${event.archiveTag}`);
104
112
  }
@@ -109,6 +117,30 @@ let BranchMutationLog = class BranchMutationLog {
109
117
  parts.push(`artifact=${artifact}`);
110
118
  return parts.join(' ');
111
119
  }
120
+ /**
121
+ * The worktree flavour of the sha/recover token: path, tip, archive tag and the ONE command that
122
+ * puts the directory AND the branch back together.
123
+ *
124
+ * `git worktree add -b <branch> <path> <ref>` is verified by hand and in worktree-reaper.spec.ts —
125
+ * plain `git worktree add <path> <tag>` would restore the files at a DETACHED HEAD, silently losing
126
+ * the branch name the reap destroyed. Falls back to the sha when nothing was archived (retention
127
+ * 'delete'), and to a bare `git worktree add <path>` when there was no branch at all (detached).
128
+ */
129
+ worktreeDetail(event) {
130
+ const ref = event.archiveTag !== '' ? event.archiveTag : event.sha;
131
+ const tokens = [`worktree=${event.worktreePath}`];
132
+ if (event.sha !== '')
133
+ tokens.push(`sha=${event.sha}`);
134
+ if (event.archiveTag !== '')
135
+ tokens.push(`archiveTag=${event.archiveTag}`);
136
+ if (ref === '')
137
+ return tokens.join(' ');
138
+ const recover = event.fromBranch !== ''
139
+ ? `git worktree add -b ${event.fromBranch} ${event.worktreePath} ${ref}`
140
+ : `git worktree add ${event.worktreePath} ${ref}`;
141
+ tokens.push(`recover=${recover}`);
142
+ return tokens.join(' ');
143
+ }
112
144
  // Collapse newlines/tabs and cap length so one event is always exactly one log line.
113
145
  oneLine(value) {
114
146
  const flat = value.replace(/[\t\r\n]+/g, ' ').trim();
@@ -1 +1 @@
1
- {"version":3,"file":"branch-mutation-log.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/branch-mutation-log.ts"],"names":[],"mappings":";;;AAoJA,sDAEC;AAGD,8CAEC;;AA3JD,+CAAyB;AACzB,mDAA6B;AAC7B,yCAA2D;AAE3D,2CAAgD;AAChD,yCAAqC;AAErC,mGAAmG;AACnG,4FAA4F;AAC5F,uGAAuG;AACvG,iGAAiG;AAEjG,MAAM,SAAS,GAAG,OAAO,CAAC;AAC1B,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AACxC,MAAM,aAAa,GAAG,wBAAwB,CAAC;AAC/C,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,mEAAmE;AACrG,MAAM,cAAc,GAAG,GAAG,CAAC;AAiB3B,0GAA0G;AAC1G,MAAa,mBAAmB;IAC5B,IAAI,CAAe;IACnB,KAAK,CAAgB;IACrB,UAAU,GAAW,EAAE,CAAC;IACxB,QAAQ,GAAW,EAAE,CAAC;IACtB,OAAO,GAAW,EAAE,CAAC;IACrB,OAAO,GAAW,EAAE,CAAC;IACrB,QAAQ,GAAY,KAAK,CAAC;IAC1B,aAAa,GAAa,EAAE,CAAC;IAC7B,OAAO,GAAW,EAAE,CAAC;IACrB,SAAS,GAAa,EAAE,CAAC;IACzB,+FAA+F;IAC/F,gGAAgG;IAChG,8FAA8F;IAC9F,yFAAyF;IACzF,GAAG,GAAW,EAAE,CAAC;IACjB,oGAAoG;IACpG,oGAAoG;IACpG,kGAAkG;IAClG,iGAAiG;IACjG,UAAU,GAAW,EAAE,CAAC;IAExB,YAAY,IAAkB,EAAE,KAAoB;QAChD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AA1BD,kDA0BC;AAED,iIAAiI;AAE1H,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC1B,qBAAqB,CAAC,IAAY;QAC9B,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,6BAAiB,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;IACnE,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,IAAY,EAAE,KAA0B;QACtD,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,6BAAiB,EAAE,SAAS,CAAC,CAAC;YAC/D,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC9C,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC;YAEhE,MAAM,IAAI,GAAG;gBACT,IAAI,SAAS,GAAG;gBAChB,KAAK,CAAC,IAAI;gBACV,KAAK,CAAC,KAAK;gBACX,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;aACzC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YACpB,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;QACf,CAAC;IACL,CAAC;IAED,iGAAiG;IACzF,YAAY,CAAC,KAA0B;QAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,4FAA4F;QAC5F,8EAA8E;QAC9E,IAAI,KAAK,CAAC,UAAU,KAAK,EAAE,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,UAAU,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;aAC7G,IAAI,KAAK,CAAC,UAAU,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;aACtE,IAAI,KAAK,CAAC,QAAQ,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC1E,IAAI,KAAK,CAAC,OAAO,KAAK,EAAE,IAAI,KAAK,CAAC,OAAO,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,OAAO,IAAI,GAAG,YAAY,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;QAChI,IAAI,KAAK,CAAC,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,aAAa,CAAC,MAAM,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChI,IAAI,KAAK,CAAC,OAAO,KAAK,EAAE;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjE,8FAA8F;QAC9F,oFAAoF;QACpF,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,KAAK,CAAC,UAAU,KAAK,EAAE,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CACN,OAAO,KAAK,CAAC,GAAG,eAAe,KAAK,CAAC,UAAU,GAAG;gBAClD,2BAA2B,KAAK,CAAC,UAAU,IAAI,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,CAC3E,CAAC;QACN,CAAC;aAAM,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,GAAG,uBAAuB,KAAK,CAAC,UAAU,IAAI,GAAG,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC,CAAC;QAC3E,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,qFAAqF;IAC7E,OAAO,CAAC,KAAa;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,OAAO,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,GAAG,CAAC;IACtF,CAAC;IAEO,aAAa,CAAC,OAAe,EAAE,QAAgB;QACnD,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,EAAE,CAAC;gBAC5B,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;oBAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;gBACrD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;QACf,CAAC;IACL,CAAC;CACJ,CAAA;AA9EY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,iBAAiB,CA8E7B;AAED,0FAA0F;AAC1F,MAAM,oBAAoB,GAAG,IAAI,iBAAiB,EAAE,CAAC;AAErD,wIAAwI;AACxI,SAAgB,qBAAqB,CAAC,IAAY;IAC9C,OAAO,oBAAoB,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;AAC5D,CAAC;AAED,wIAAwI;AACxI,SAAgB,iBAAiB,CAAC,IAAY,EAAE,KAA0B;IACtE,oBAAoB,CAAC,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACxD,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches.\n// Records START / each phase boundary / END-with-outcome so the next agent (or a human) can\n// reconstruct what the tooling did to the branches. Writes to `.webpieces/hooks/branch-mutations.log`.\n// Lives in rules-config (the shared dep of pr-gate) so the pr-gate scripts can call it directly.\n\nconst HOOKS_DIR = 'hooks';\nconst LOG_FILE = 'branch-mutations.log';\nconst LOG_FILE_PREV = 'branch-mutations.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors the other webpieces logs)\nconst MAX_DETAIL_LEN = 400;\n\n// The workflow verb whose branch mutation is being logged (the bin the AI/human invoked).\n// `auto-reap` is the odd one out: no human invoked it — it is the detached background refresher\n// (sync-main.ts) deleting dead branches on its own. It gets a verb precisely BECAUSE it is\n// unattended: a deletion nobody watched happen is the one that most needs an audit line.\nexport type MutationVerb =\n | 'wp-start-update' | 'wp-finish-update' | 'wp-start-upsert-pr' | 'wp-finish-upsert-pr'\n | 'wp-cleanup' | 'auto-reap';\n\n// A boundary within a verb's execution. START/END bracket the whole run; the middle phases mark each\n// irreversible git step so an interrupt leaves a breadcrumb at the last phase reached.\n// REAP is a whole mutation in one line (a branch delete has no phases) — see BranchReaper.\nexport type MutationPhase =\n | 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME'\n | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END' | 'REAP';\n\n// Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit construction).\nexport class BranchMutationEvent {\n verb: MutationVerb;\n phase: MutationPhase;\n fromBranch: string = '';\n toBranch: string = '';\n oldMain: string = '';\n newMain: string = '';\n conflict: boolean = false;\n conflictFiles: string[] = [];\n outcome: string = '';\n artifacts: string[] = [];\n // The commit a DELETED branch pointed at, captured immediately before the delete. This is what\n // makes a reap auditable AND reversible: the work is already in main, and the pre-delete tip is\n // still addressable by hash (the reflog holds it ~90 days), so formatDetail renders a literal\n // `recover=git branch <name> <sha>` next to it. Empty for mutations that delete nothing.\n sha: string = '';\n // The `archive/<date>/<branch>` tag written immediately BEFORE a REAP deleted the branch. When set,\n // formatDetail renders `recover=` against the TAG instead of the sha: a tag is a permanent ref that\n // survives `gc` and reflog expiry and can be pushed, whereas a bare sha is only recoverable while\n // this clone's reflog still holds it. Empty when nothing was tagged (retention policy 'delete').\n archiveTag: string = '';\n\n constructor(verb: MutationVerb, phase: MutationPhase) {\n this.verb = verb;\n this.phase = phase;\n }\n}\n\n/** Appends branch-mutation audit lines. `@injectable(bindingScopeValues.Singleton)` so it's injectable + drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class BranchMutationLog {\n branchMutationLogPath(root: string): string {\n return path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);\n }\n\n /**\n * Append one tab-separated line per branch-mutation event to\n * `.webpieces/hooks/branch-mutations.log`. Swallows all errors — logging must NEVER block or fail\n * the workflow it is observing.\n */\n logBranchMutation(root: string, event: BranchMutationEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR);\n fs.mkdirSync(hooksDir, { recursive: true });\n\n const logPath = path.join(hooksDir, LOG_FILE);\n this.rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));\n\n const line = [\n `[${timestamp}]`,\n event.verb,\n event.phase,\n this.oneLine(this.formatDetail(event)),\n ].join('\\t') + '\\n';\n fs.appendFileSync(logPath, line);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // Render only the fields this event actually set, as `key=value` tokens — greppable on one line.\n private formatDetail(event: BranchMutationEvent): string {\n const parts: string[] = [];\n // A rename/move has both ends; a REAP has only the branch it destroyed. Printing `to=?` for\n // the latter reads like a lost destination rather than \"there was never one\".\n if (event.fromBranch !== '' && event.toBranch !== '') parts.push(`from=${event.fromBranch} to=${event.toBranch}`);\n else if (event.fromBranch !== '') parts.push(`branch=${event.fromBranch}`);\n else if (event.toBranch !== '') parts.push(`from=? to=${event.toBranch}`);\n if (event.oldMain !== '' || event.newMain !== '') parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);\n if (event.conflict) parts.push('conflict=true');\n if (event.conflictFiles.length > 0) parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);\n if (event.outcome !== '') parts.push(`outcome=${event.outcome}`);\n // Emitted as one unit so the hash is never separated from the command that undoes the delete.\n // Prefer the archive TAG as the recover ref when there is one — it does not expire.\n if (event.sha !== '' && event.archiveTag !== '') {\n parts.push(\n `sha=${event.sha} archiveTag=${event.archiveTag} ` +\n `recover=git checkout -b ${event.fromBranch || '?'} ${event.archiveTag}`,\n );\n } else if (event.sha !== '') {\n parts.push(`sha=${event.sha} recover=git branch ${event.fromBranch || '?'} ${event.sha}`);\n }\n for (const artifact of event.artifacts) parts.push(`artifact=${artifact}`);\n return parts.join(' ');\n }\n\n // Collapse newlines/tabs and cap length so one event is always exactly one log line.\n private oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';\n }\n\n private rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\n// Temporary migration delegators to BranchMutationLog — removed once consumers inject it.\nconst branchMutationLogSvc = new BranchMutationLog();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function branchMutationLogPath(root: string): string {\n return branchMutationLogSvc.branchMutationLogPath(root);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function logBranchMutation(root: string, event: BranchMutationEvent): void {\n branchMutationLogSvc.logBranchMutation(root, event);\n}\n"]}
1
+ {"version":3,"file":"branch-mutation-log.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/branch-mutation-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// The BRANCH-MUTATION log — an audit trail for every workflow verb that RENAMES or MOVES branches.\n// Records START / each phase boundary / END-with-outcome so the next agent (or a human) can\n// reconstruct what the tooling did to the branches. Writes to `.webpieces/hooks/branch-mutations.log`.\n// Lives in rules-config (the shared dep of pr-gate) so the pr-gate scripts can call it directly.\n\nconst HOOKS_DIR = 'hooks';\nconst LOG_FILE = 'branch-mutations.log';\nconst LOG_FILE_PREV = 'branch-mutations.1.log';\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded (mirrors the other webpieces logs)\nconst MAX_DETAIL_LEN = 400;\n\n// The workflow verb whose branch mutation is being logged (the bin the AI/human invoked).\n// `auto-reap` is the odd one out: no human invoked it — it is the detached background refresher\n// (sync-main.ts) deleting dead branches on its own. It gets a verb precisely BECAUSE it is\n// unattended: a deletion nobody watched happen is the one that most needs an audit line.\nexport type MutationVerb =\n | 'wp-start-update' | 'wp-finish-update' | 'wp-start-upsert-pr' | 'wp-finish-upsert-pr'\n | 'wp-cleanup' | 'auto-reap';\n\n// A boundary within a verb's execution. START/END bracket the whole run; the middle phases mark each\n// irreversible git step so an interrupt leaves a breadcrumb at the last phase reached.\n// REAP is a whole mutation in one line (a branch delete has no phases) — see BranchReaper.\n// REAP_WORKTREE is its worktree twin: archive → `git worktree remove` → `git branch -D`, all three of\n// which succeed or fail as one act. It is a SEPARATE phase, not just a REAP with a path, so that\n// `grep REAP_WORKTREE` answers \"what directories did the tooling delete?\" — a strictly scarier\n// question than \"what refs did it delete?\", since a worktree removal takes real files with it.\nexport type MutationPhase =\n | 'START' | 'BACKUP' | 'CHECKOUT_MAIN' | 'PULL' | 'SQUASH' | 'RENAME'\n | 'FINALIZE' | 'CONFLICT' | 'INTERRUPTED' | 'END' | 'REAP' | 'REAP_WORKTREE';\n\n// Data-only record of one branch-mutation event (per CLAUDE.md: classes for data, explicit construction).\nexport class BranchMutationEvent {\n verb: MutationVerb;\n phase: MutationPhase;\n fromBranch: string = '';\n toBranch: string = '';\n oldMain: string = '';\n newMain: string = '';\n conflict: boolean = false;\n conflictFiles: string[] = [];\n outcome: string = '';\n artifacts: string[] = [];\n // The commit a DELETED branch pointed at, captured immediately before the delete. This is what\n // makes a reap auditable AND reversible: the work is already in main, and the pre-delete tip is\n // still addressable by hash (the reflog holds it ~90 days), so formatDetail renders a literal\n // `recover=git branch <name> <sha>` next to it. Empty for mutations that delete nothing.\n sha: string = '';\n // The `archive/<date>/<branch>` tag written immediately BEFORE a REAP deleted the branch. When set,\n // formatDetail renders `recover=` against the TAG instead of the sha: a tag is a permanent ref that\n // survives `gc` and reflog expiry and can be pushed, whereas a bare sha is only recoverable while\n // this clone's reflog still holds it. Empty when nothing was tagged (retention policy 'delete').\n archiveTag: string = '';\n // The directory a REAP_WORKTREE removed. When set, `recover=` becomes the WORKTREE form\n // (`git worktree add -b <branch> <path> <ref>`) rather than the bare `git branch` form: putting the\n // ref back does not put the directory back, and a recover line that restores half of what was\n // destroyed is worse than none — it reads as done. Empty for every mutation that removes no directory.\n worktreePath: string = '';\n\n constructor(verb: MutationVerb, phase: MutationPhase) {\n this.verb = verb;\n this.phase = phase;\n }\n}\n\n/** Appends branch-mutation audit lines. `@injectable(bindingScopeValues.Singleton)` so it's injectable + drawn in the design. */\n@injectable(bindingScopeValues.Singleton)\nexport class BranchMutationLog {\n branchMutationLogPath(root: string): string {\n return path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR, LOG_FILE);\n }\n\n /**\n * Append one tab-separated line per branch-mutation event to\n * `.webpieces/hooks/branch-mutations.log`. Swallows all errors — logging must NEVER block or fail\n * the workflow it is observing.\n */\n logBranchMutation(root: string, event: BranchMutationEvent): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const timestamp = new Date().toISOString();\n const hooksDir = path.join(root, WEBPIECES_TMP_DIR, HOOKS_DIR);\n fs.mkdirSync(hooksDir, { recursive: true });\n\n const logPath = path.join(hooksDir, LOG_FILE);\n this.rotateLogFile(logPath, path.join(hooksDir, LOG_FILE_PREV));\n\n const line = [\n `[${timestamp}]`,\n event.verb,\n event.phase,\n this.oneLine(this.formatDetail(event)),\n ].join('\\t') + '\\n';\n fs.appendFileSync(logPath, line);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // Render only the fields this event actually set, as `key=value` tokens — greppable on one line.\n private formatDetail(event: BranchMutationEvent): string {\n const parts: string[] = [];\n // A rename/move has both ends; a REAP has only the branch it destroyed. Printing `to=?` for\n // the latter reads like a lost destination rather than \"there was never one\".\n if (event.fromBranch !== '' && event.toBranch !== '') parts.push(`from=${event.fromBranch} to=${event.toBranch}`);\n else if (event.fromBranch !== '') parts.push(`branch=${event.fromBranch}`);\n else if (event.toBranch !== '') parts.push(`from=? to=${event.toBranch}`);\n if (event.oldMain !== '' || event.newMain !== '') parts.push(`oldMain=${event.oldMain || '?'} newMain=${event.newMain || '?'}`);\n if (event.conflict) parts.push('conflict=true');\n if (event.conflictFiles.length > 0) parts.push(`conflictFiles=${event.conflictFiles.length}(${event.conflictFiles.join(',')})`);\n if (event.outcome !== '') parts.push(`outcome=${event.outcome}`);\n // Emitted as one unit so the hash is never separated from the command that undoes the delete.\n // Prefer the archive TAG as the recover ref when there is one — it does not expire.\n if (event.worktreePath !== '') {\n parts.push(this.worktreeDetail(event));\n } else if (event.sha !== '' && event.archiveTag !== '') {\n parts.push(\n `sha=${event.sha} archiveTag=${event.archiveTag} ` +\n `recover=git checkout -b ${event.fromBranch || '?'} ${event.archiveTag}`,\n );\n } else if (event.sha !== '') {\n parts.push(`sha=${event.sha} recover=git branch ${event.fromBranch || '?'} ${event.sha}`);\n }\n for (const artifact of event.artifacts) parts.push(`artifact=${artifact}`);\n return parts.join(' ');\n }\n\n /**\n * The worktree flavour of the sha/recover token: path, tip, archive tag and the ONE command that\n * puts the directory AND the branch back together.\n *\n * `git worktree add -b <branch> <path> <ref>` is verified by hand and in worktree-reaper.spec.ts —\n * plain `git worktree add <path> <tag>` would restore the files at a DETACHED HEAD, silently losing\n * the branch name the reap destroyed. Falls back to the sha when nothing was archived (retention\n * 'delete'), and to a bare `git worktree add <path>` when there was no branch at all (detached).\n */\n private worktreeDetail(event: BranchMutationEvent): string {\n const ref = event.archiveTag !== '' ? event.archiveTag : event.sha;\n const tokens = [`worktree=${event.worktreePath}`];\n if (event.sha !== '') tokens.push(`sha=${event.sha}`);\n if (event.archiveTag !== '') tokens.push(`archiveTag=${event.archiveTag}`);\n if (ref === '') return tokens.join(' ');\n const recover = event.fromBranch !== ''\n ? `git worktree add -b ${event.fromBranch} ${event.worktreePath} ${ref}`\n : `git worktree add ${event.worktreePath} ${ref}`;\n tokens.push(`recover=${recover}`);\n return tokens.join(' ');\n }\n\n // Collapse newlines/tabs and cap length so one event is always exactly one log line.\n private oneLine(value: string): string {\n const flat = value.replace(/[\\t\\r\\n]+/g, ' ').trim();\n return flat.length <= MAX_DETAIL_LEN ? flat : flat.slice(0, MAX_DETAIL_LEN) + '…';\n }\n\n private rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\n// Temporary migration delegators to BranchMutationLog — removed once consumers inject it.\nconst branchMutationLogSvc = new BranchMutationLog();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function branchMutationLogPath(root: string): string {\n return branchMutationLogSvc.branchMutationLogPath(root);\n}\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to BranchMutationLog; removed once consumers inject it\nexport function logBranchMutation(root: string, event: BranchMutationEvent): void {\n branchMutationLogSvc.logBranchMutation(root, event);\n}\n"]}
package/src/index.d.ts CHANGED
@@ -43,10 +43,11 @@ export { GateTokenService, computeGateToken, gateTokenMarker, extractGateToken,
43
43
  export { SubagentProvenanceService, ProvenanceResult, PROVENANCE_OK, PROVENANCE_MISSING, PROVENANCE_SKIPPED, } from './subagent-provenance';
44
44
  export { ReviewJson, PrContext, ChecklistResult, ChecklistVerdict, CK_PASS, CK_WARN, CK_OVERRIDDEN, CK_FAIL, CK_MISSING, CK_BAD_FORMAT, VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED, VERDICT_STATUSES, RequiredChecklist, ChecklistReviewContext, ReviewJsonService, loadReviewJson, prDirFor, reviewJsonPath, reviewJsonSchemaHint, } from './review-json';
45
45
  export { MainSyncStatus, MainSyncLock, MainSyncStatusService, DEFAULT_HANG_TIMEOUT_MINUTES, mainSyncStatusPath, mainSyncLockPath, readMainSyncStatus, writeMainSyncStatus, readMainSyncLock, writeMainSyncLock, isLockStale, isRefreshInProgress, tryAcquireMainSyncLock, inProcessLock, finishedLock, computeMainSyncStatus, stampCleanMainSyncStatus, squashRecoverySteps, } from './main-sync-status';
46
- export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, MergedBranchesService, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, PROMPTABLE_CLASSIFICATIONS, } from './merged-branches';
46
+ export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, MergedBranchesService, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED, PROMPTABLE_CLASSIFICATIONS, } from './merged-branches';
47
47
  export { BranchArchiver, ArchiveResult, ARCHIVE_TAG_PREFIX, BRANCH_RETENTIONS, BRANCH_RETENTION_DELETE, BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTION_KEEP, } from './branch-archiver';
48
48
  export { Worktree, WorktreeService, } from './worktrees';
49
49
  export { ReapedBranch, ReapResult, BranchReaper, } from './branch-reaper';
50
+ export { ReapedWorktree, WorktreeReapResult, WorktreeReaper, } from './worktree-reaper';
50
51
  export type { MutationVerb, MutationPhase } from './branch-mutation-log';
51
52
  export { BranchMutationEvent, BranchMutationLog, branchMutationLogPath, logBranchMutation, } from './branch-mutation-log';
52
53
  export { CommandsConfig, buildCommandsConfig, DEFAULT_UPSERT_PR_COMMAND, DEFAULT_MERGE_COMPLETE_COMMAND, } from './commands-config';
package/src/index.js CHANGED
@@ -4,7 +4,7 @@ exports.sectionForRule = exports.isHookGuard = exports.HOOK_GUARD_NAMES = export
4
4
  exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoCustomCssConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WP_FINISH_UPSERT_PR = exports.WP_START_UPSERT_PR = exports.WP_FINISH_UPDATE = exports.WP_START_UPDATE = exports.SyncFlowGuidance = exports.WebpiecesRulesConfig = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.ChangedFilesOptions = exports.DiffRange = exports.DiffScope = exports.isNewOrModified = exports.hasChangesInRange = exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.getCurrentBranch = exports.shouldSkipRule = exports.FieldDef = void 0;
5
5
  exports.formatFileList = exports.normalizeChecklistDoc = exports.toChecklist = exports.ChecklistDefinition = exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.buildLandPrConfig = exports.buildPrGateConfig = exports.defaultLandPrConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.LandPrConfig = exports.PrGateConfig = exports.GateDefinition = exports.StaleMainBashGuardConfig = exports.MergedBranchBashGuardConfig = exports.ReadStaleGuardConfig = exports.FeatureBranchGuardConfig = exports.CLIENT_CREATION_SEVERITIES = exports.NoClientCreationOutsideServerOrClientConfig = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.RedirectHowToMergeMainConfig = exports.PrMergeGuardConfig = exports.MergeInProgressGuardConfig = exports.PrCreationOrPushGuardConfig = void 0;
6
6
  exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.tryAcquireMainSyncLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.writeMainSyncStatus = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncStatusService = exports.MainSyncLock = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = exports.loadReviewJson = exports.ReviewJsonService = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ChecklistVerdict = exports.ChecklistResult = exports.PrContext = exports.ReviewJson = exports.PROVENANCE_SKIPPED = exports.PROVENANCE_MISSING = exports.PROVENANCE_OK = exports.ProvenanceResult = exports.SubagentProvenanceService = exports.verifyGateToken = exports.extractGateToken = exports.gateTokenMarker = exports.computeGateToken = exports.GateTokenService = exports.ChecklistInstructionsService = exports.ChecklistValidator = void 0;
7
- exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeService = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.MergedBranch = exports.squashRecoverySteps = void 0;
7
+ exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeService = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = exports.ArchiveResult = exports.BranchArchiver = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.MergedBranch = exports.squashRecoverySteps = void 0;
8
8
  var types_1 = require("./types");
9
9
  Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
10
10
  Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
@@ -259,6 +259,10 @@ Object.defineProperty(exports, "CLASSIFICATION_SUPERSEDED", { enumerable: true,
259
259
  Object.defineProperty(exports, "CLASSIFICATION_CONTENT_IN_MAIN", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_CONTENT_IN_MAIN; } });
260
260
  Object.defineProperty(exports, "CLASSIFICATION_NEVER_PROPOSED", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_NEVER_PROPOSED; } });
261
261
  Object.defineProperty(exports, "CLASSIFICATION_IN_USE", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_IN_USE; } });
262
+ Object.defineProperty(exports, "CLASSIFICATION_PRUNABLE", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_PRUNABLE; } });
263
+ Object.defineProperty(exports, "CLASSIFICATION_LOCKED", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_LOCKED; } });
264
+ Object.defineProperty(exports, "CLASSIFICATION_CURRENT", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_CURRENT; } });
265
+ Object.defineProperty(exports, "CLASSIFICATION_DETACHED", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_DETACHED; } });
262
266
  Object.defineProperty(exports, "PROMPTABLE_CLASSIFICATIONS", { enumerable: true, get: function () { return merged_branches_1.PROMPTABLE_CLASSIFICATIONS; } });
263
267
  var branch_archiver_1 = require("./branch-archiver");
264
268
  Object.defineProperty(exports, "BranchArchiver", { enumerable: true, get: function () { return branch_archiver_1.BranchArchiver; } });
@@ -275,6 +279,10 @@ var branch_reaper_1 = require("./branch-reaper");
275
279
  Object.defineProperty(exports, "ReapedBranch", { enumerable: true, get: function () { return branch_reaper_1.ReapedBranch; } });
276
280
  Object.defineProperty(exports, "ReapResult", { enumerable: true, get: function () { return branch_reaper_1.ReapResult; } });
277
281
  Object.defineProperty(exports, "BranchReaper", { enumerable: true, get: function () { return branch_reaper_1.BranchReaper; } });
282
+ var worktree_reaper_1 = require("./worktree-reaper");
283
+ Object.defineProperty(exports, "ReapedWorktree", { enumerable: true, get: function () { return worktree_reaper_1.ReapedWorktree; } });
284
+ Object.defineProperty(exports, "WorktreeReapResult", { enumerable: true, get: function () { return worktree_reaper_1.WorktreeReapResult; } });
285
+ Object.defineProperty(exports, "WorktreeReaper", { enumerable: true, get: function () { return worktree_reaper_1.WorktreeReaper; } });
278
286
  var branch_mutation_log_1 = require("./branch-mutation-log");
279
287
  Object.defineProperty(exports, "BranchMutationEvent", { enumerable: true, get: function () { return branch_mutation_log_1.BranchMutationEvent; } });
280
288
  Object.defineProperty(exports, "BranchMutationLog", { enumerable: true, get: function () { return branch_mutation_log_1.BranchMutationLog; } });
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError } from './rule-fail-error';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR } from './repo-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';\nexport { validateChecklistDocs } from './checklist-docs-validator';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n FeatureBranchGuardConfig,\n ReadStaleGuardConfig,\n MergedBranchBashGuardConfig,\n StaleMainBashGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n LandPrConfig,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n writeMainSyncStatus,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeService,\n} from './worktrees';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError } from './rule-fail-error';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR } from './repo-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateCommandsSection, validateExcludePaths, validateMatchRulesSection, allRuleNames } from './validate-config';\nexport { validateChecklistDocs } from './checklist-docs-validator';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { HOOK_GUARD_NAMES, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport type { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrCreationOrPushGuardConfig,\n MergeInProgressGuardConfig,\n PrMergeGuardConfig,\n RedirectHowToMergeMainConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n FeatureBranchGuardConfig,\n ReadStaleGuardConfig,\n MergedBranchBashGuardConfig,\n StaleMainBashGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n LandPrConfig,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n writeMainSyncStatus,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\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 PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeService,\n} from './worktrees';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport {\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n} from './worktree-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
@@ -22,6 +22,20 @@ export declare const CLASSIFICATION_SUPERSEDED = "superseded";
22
22
  export declare const CLASSIFICATION_CONTENT_IN_MAIN = "content-already-in-main";
23
23
  export declare const CLASSIFICATION_NEVER_PROPOSED = "never-proposed";
24
24
  export declare const CLASSIFICATION_IN_USE = "in-use";
25
+ /**
26
+ * WORKTREE-only classifications. A worktree verdict borrows every token above (its branch is what is
27
+ * being judged), but three of its outcomes have no branch analogue at all, and lumping them under
28
+ * `in-use` would tell wp-cleanup to shut up about exactly the ones a human might want to act on.
29
+ *
30
+ * - PRUNABLE — the directory is already gone; `git worktree prune` is the reap, not `remove`.
31
+ * - LOCKED — a human ran `git worktree lock`. Explicitly "do not touch"; never promptable.
32
+ * - CURRENT — the worktree the command is running IN. Removing your own cwd is a self-destruct.
33
+ * - DETACHED — detached HEAD, so there is no branch to judge and no branch to archive.
34
+ */
35
+ export declare const CLASSIFICATION_PRUNABLE = "prunable-worktree";
36
+ export declare const CLASSIFICATION_LOCKED = "locked-worktree";
37
+ export declare const CLASSIFICATION_CURRENT = "current-worktree";
38
+ export declare const CLASSIFICATION_DETACHED = "detached-worktree";
25
39
  export declare const PROMPTABLE_CLASSIFICATIONS: readonly string[];
26
40
  /**
27
41
  * A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).
@@ -59,6 +73,14 @@ export declare class DeletableBranch {
59
73
  * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`
60
74
  * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in
61
75
  * that order — git refuses to delete a branch that is still checked out somewhere.
76
+ *
77
+ * `classification` is the same STABLE token the branch verdicts carry, plus the four worktree-only
78
+ * ones above. It exists for the same reason it does on DeletableBranch: `deletable` answers "may the
79
+ * tooling reap this unattended?", and everything false used to collapse into one undifferentiated
80
+ * "spared" that a human could not rule on. With a token, WorktreeReaper knows whether the reap is a
81
+ * `prune` or a `remove`, and wp-cleanup knows which spared worktrees are worth ASKING about.
82
+ * Defaulted so every pre-existing call site and every cache written by an older release still builds;
83
+ * an unclassified revived entry reads as 'never-proposed', the most conservative spared verdict.
62
84
  */
63
85
  export declare class DeletableWorktree {
64
86
  path: string;
@@ -66,7 +88,8 @@ export declare class DeletableWorktree {
66
88
  reason: string;
67
89
  pr: number;
68
90
  deletable: boolean;
69
- constructor(path: string, branch: string, reason: string, pr: number, deletable: boolean);
91
+ classification: string;
92
+ constructor(path: string, branch: string, reason: string, pr: number, deletable: boolean, classification?: string);
70
93
  }
71
94
  /**
72
95
  * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.MergedBranch = void 0;
3
+ exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.PROMPTABLE_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.MergedBranch = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const child_process_1 = require("child_process");
6
6
  const fs = tslib_1.__importStar(require("fs"));
@@ -62,6 +62,20 @@ exports.CLASSIFICATION_CONTENT_IN_MAIN = 'content-already-in-main';
62
62
  exports.CLASSIFICATION_NEVER_PROPOSED = 'never-proposed';
63
63
  // Spared for a mechanical reason, not a judgement call (checked out somewhere).
64
64
  exports.CLASSIFICATION_IN_USE = 'in-use';
65
+ /**
66
+ * WORKTREE-only classifications. A worktree verdict borrows every token above (its branch is what is
67
+ * being judged), but three of its outcomes have no branch analogue at all, and lumping them under
68
+ * `in-use` would tell wp-cleanup to shut up about exactly the ones a human might want to act on.
69
+ *
70
+ * - PRUNABLE — the directory is already gone; `git worktree prune` is the reap, not `remove`.
71
+ * - LOCKED — a human ran `git worktree lock`. Explicitly "do not touch"; never promptable.
72
+ * - CURRENT — the worktree the command is running IN. Removing your own cwd is a self-destruct.
73
+ * - DETACHED — detached HEAD, so there is no branch to judge and no branch to archive.
74
+ */
75
+ exports.CLASSIFICATION_PRUNABLE = 'prunable-worktree';
76
+ exports.CLASSIFICATION_LOCKED = 'locked-worktree';
77
+ exports.CLASSIFICATION_CURRENT = 'current-worktree';
78
+ exports.CLASSIFICATION_DETACHED = 'detached-worktree';
65
79
  // Spared classifications a human can meaningfully rule on, most-safe first. wp-cleanup prompts in
66
80
  // exactly this order so the easy yeses come before the ones that need thought.
67
81
  exports.PROMPTABLE_CLASSIFICATIONS = [
@@ -115,6 +129,14 @@ exports.DeletableBranch = DeletableBranch;
115
129
  * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`
116
130
  * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in
117
131
  * that order — git refuses to delete a branch that is still checked out somewhere.
132
+ *
133
+ * `classification` is the same STABLE token the branch verdicts carry, plus the four worktree-only
134
+ * ones above. It exists for the same reason it does on DeletableBranch: `deletable` answers "may the
135
+ * tooling reap this unattended?", and everything false used to collapse into one undifferentiated
136
+ * "spared" that a human could not rule on. With a token, WorktreeReaper knows whether the reap is a
137
+ * `prune` or a `remove`, and wp-cleanup knows which spared worktrees are worth ASKING about.
138
+ * Defaulted so every pre-existing call site and every cache written by an older release still builds;
139
+ * an unclassified revived entry reads as 'never-proposed', the most conservative spared verdict.
118
140
  */
119
141
  class DeletableWorktree {
120
142
  path;
@@ -122,12 +144,15 @@ class DeletableWorktree {
122
144
  reason;
123
145
  pr;
124
146
  deletable;
125
- constructor(path, branch, reason, pr, deletable) {
147
+ classification;
148
+ // eslint-disable-next-line @typescript-eslint/max-params
149
+ constructor(path, branch, reason, pr, deletable, classification = exports.CLASSIFICATION_NEVER_PROPOSED) {
126
150
  this.path = path;
127
151
  this.branch = branch;
128
152
  this.reason = reason;
129
153
  this.pr = pr;
130
154
  this.deletable = deletable;
155
+ this.classification = classification;
131
156
  }
132
157
  }
133
158
  exports.DeletableWorktree = DeletableWorktree;
@@ -297,23 +322,26 @@ let MergedBranchesService = class MergedBranchesService {
297
322
  if (tree.isMain)
298
323
  continue;
299
324
  if (tree.prunable) {
300
- out.push(new DeletableWorktree(tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true));
325
+ out.push(new DeletableWorktree(tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true, exports.CLASSIFICATION_PRUNABLE));
301
326
  continue;
302
327
  }
303
328
  if (tree.locked) {
304
- out.push(new DeletableWorktree(tree.path, tree.branch, 'locked by a human — do not touch', 0, false));
329
+ out.push(new DeletableWorktree(tree.path, tree.branch, 'locked by a human — do not touch', 0, false, exports.CLASSIFICATION_LOCKED));
305
330
  continue;
306
331
  }
307
332
  if (tree.path === repoRoot) {
308
- out.push(new DeletableWorktree(tree.path, tree.branch, 'you are standing in it', 0, false));
333
+ out.push(new DeletableWorktree(tree.path, tree.branch, 'you are standing in it', 0, false, exports.CLASSIFICATION_CURRENT));
309
334
  continue;
310
335
  }
311
336
  if (tree.branch === '') {
312
- out.push(new DeletableWorktree(tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false));
337
+ out.push(new DeletableWorktree(tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false, exports.CLASSIFICATION_DETACHED));
313
338
  continue;
314
339
  }
340
+ // The branch's OWN classification token rides along unchanged. That is what lets wp-cleanup
341
+ // group probably-dead worktrees exactly the way it groups probably-dead branches, instead of
342
+ // inventing a second, parallel notion of "how dead is this".
315
343
  const verdict = this.classify(repoRoot, tree.branch, prs);
316
- out.push(new DeletableWorktree(tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable));
344
+ out.push(new DeletableWorktree(tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable, verdict.entry.classification));
317
345
  }
318
346
  return out;
319
347
  }
@@ -489,7 +517,7 @@ let MergedBranchesService = class MergedBranchesService {
489
517
  reviveWorktrees(raw) {
490
518
  if (!raw)
491
519
  return [];
492
- return raw.map((entry) => new DeletableWorktree(entry.path ?? '', entry.branch ?? '', entry.reason ?? '', entry.pr ?? 0, entry.deletable ?? false));
520
+ return raw.map((entry) => new DeletableWorktree(entry.path ?? '', entry.branch ?? '', entry.reason ?? '', entry.pr ?? 0, entry.deletable ?? false, entry.classification ?? exports.CLASSIFICATION_NEVER_PROPOSED));
493
521
  }
494
522
  // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.
495
523
  capture(repoRoot, cmd, args) {
@@ -1 +1 @@
1
- {"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\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// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n/**\n * WHY a branch is dead, or why it was spared — as a STABLE token, alongside the English prose.\n *\n * `sha`/`commits`/`prState` (below) let a human SEE a spared branch. This adds the other half: a token\n * saying WHICH KIND of spared it is. Every spared branch used to report the identical string\n * `no merged PR found — a human must decide`, and in one observed repo that one string covered three\n * genuinely different situations — a PR CLOSED UNMERGED whose work landed under a later number, a\n * branch that NEVER had a PR and holds the only copy of its commits, and content already in main.\n * Reporting all three identically is why nobody ever decided, and why the pile grew to 6 branches\n * against a cap of 5. A token lets wp-cleanup GROUP them and ask a question that can be answered.\n */\n// Dead by proof — these are auto-deletable.\nexport const CLASSIFICATION_MERGED_PR = 'merged-pr';\nexport const CLASSIFICATION_BACKUP_OF_MERGED = 'backup-of-merged';\nexport const CLASSIFICATION_NO_COMMITS = 'no-commits';\n// Spared, but a human should be ASKED — in descending order of \"obviously fine to delete\".\nexport const CLASSIFICATION_SUPERSEDED = 'superseded';\nexport const CLASSIFICATION_CONTENT_IN_MAIN = 'content-already-in-main';\nexport const CLASSIFICATION_NEVER_PROPOSED = 'never-proposed';\n// Spared for a mechanical reason, not a judgement call (checked out somewhere).\nexport const CLASSIFICATION_IN_USE = 'in-use';\n\n// Spared classifications a human can meaningfully rule on, most-safe first. wp-cleanup prompts in\n// exactly this order so the easy yeses come before the ones that need thought.\nexport const PROMPTABLE_CLASSIFICATIONS: readonly string[] = [\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n];\n\n/**\n * A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\n *\n * `sha`, `commits` and `prState` exist for the SPARED branches specifically. When nothing is\n * auto-reapable the branch cap has nothing safe to offer, and its only remaining advice was \"raise\n * maxLocalBranches\" / \"set turnOffRuleUntilEpoch\" — both of which loosen the rule. An agent with no\n * human present took the config edit, which is the exact failure the cap exists to prevent. To ask a\n * human \"may I delete these?\" instead, the guard has to be able to SHOW what it would delete: tip SHA\n * (so the delete is recoverable), PR state (closed? never opened?) and how much unique work is on it.\n *\n * All three are computed in the DETACHED refresher and read straight off merged-branches.json — the\n * blocking hook path never recomputes them. Defaulted so a cache written by an older release (and\n * every existing call site) revives without them.\n */\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n /** Short tip SHA — what makes the delete undoable (`git branch <name> <sha>`). '' when unknown. */\n sha: string;\n /** Commits on this branch that are not on origin/main. -1 when it could not be established. */\n commits: number;\n /** GitHub's state for the PR whose head is this branch: MERGED / CLOSED / OPEN, or '' for none. */\n prState: string;\n /**\n * One of the CLASSIFICATION_* tokens. Defaulted like the fields above so every pre-existing call\n * site — and every cache written by an older release — still constructs; an unclassified revived\n * entry reads as 'never-proposed', the most conservative of the spared verdicts.\n */\n classification: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n branch: string,\n reason: string,\n pr: number,\n sha: string = '',\n commits: number = -1,\n prState: string = '',\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.sha = sha;\n this.commits = commits;\n this.prState = prState;\n this.classification = classification;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n\n constructor(path: string, branch: string, reason: string, pr: number, deletable: boolean) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\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}\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(private readonly worktrees: WorktreeService = new WorktreeService()) {}\n\n mergedBranchesPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, 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 writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n const cachePath = this.mergedBranchesPath(repoRoot);\n fs.mkdirSync(path.dirname(cachePath), { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\\n');\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): 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);\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 proofs the branch cap uses (merged PR, backup-of-merged, or zero commits of\n * its own). It is spared when it is LOCKED (a human said \"do not touch\"), when it is the worktree we\n * are standing in right now (removing your own cwd is not a thing to suggest to an agent), or when\n * its branch still holds unmerged work.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\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 continue;\n }\n if (tree.locked) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'locked by a human — do not touch', 0, false));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false));\n continue;\n }\n if (tree.branch === '') {\n out.push(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false));\n continue;\n }\n\n const verdict = this.classify(repoRoot, tree.branch, prs);\n out.push(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable));\n }\n\n return out;\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 }\n\n // The one git-local signal that squash-merge CANNOT corrupt: a branch with zero commits of its\n // own holds no work, so deleting it can lose nothing. (Squash breaks patch-id and ancestry, so\n // \"are these commits in main?\" is unanswerable from git — but \"are there any commits at all?\"\n // is exact.) These are the husks left behind by branching and then never committing.\n if (commits === 0) {\n return new Verdict(true, new DeletableBranch(\n branch, 'no commits of its own — identical to origin/main', 0, sha, 0, prState,\n 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 // 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 => new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\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' });\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/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\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// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n/**\n * WHY a branch is dead, or why it was spared — as a STABLE token, alongside the English prose.\n *\n * `sha`/`commits`/`prState` (below) let a human SEE a spared branch. This adds the other half: a token\n * saying WHICH KIND of spared it is. Every spared branch used to report the identical string\n * `no merged PR found — a human must decide`, and in one observed repo that one string covered three\n * genuinely different situations — a PR CLOSED UNMERGED whose work landed under a later number, a\n * branch that NEVER had a PR and holds the only copy of its commits, and content already in main.\n * Reporting all three identically is why nobody ever decided, and why the pile grew to 6 branches\n * against a cap of 5. A token lets wp-cleanup GROUP them and ask a question that can be answered.\n */\n// Dead by proof — these are auto-deletable.\nexport const CLASSIFICATION_MERGED_PR = 'merged-pr';\nexport const CLASSIFICATION_BACKUP_OF_MERGED = 'backup-of-merged';\nexport const CLASSIFICATION_NO_COMMITS = 'no-commits';\n// Spared, but a human should be ASKED — in descending order of \"obviously fine to delete\".\nexport const CLASSIFICATION_SUPERSEDED = 'superseded';\nexport const CLASSIFICATION_CONTENT_IN_MAIN = 'content-already-in-main';\nexport const CLASSIFICATION_NEVER_PROPOSED = 'never-proposed';\n// Spared for a mechanical reason, not a judgement call (checked out somewhere).\nexport const CLASSIFICATION_IN_USE = 'in-use';\n\n/**\n * WORKTREE-only classifications. A worktree verdict borrows every token above (its branch is what is\n * being judged), but three of its outcomes have no branch analogue at all, and lumping them under\n * `in-use` would tell wp-cleanup to shut up about exactly the ones a human might want to act on.\n *\n * - PRUNABLE — the directory is already gone; `git worktree prune` is the reap, not `remove`.\n * - LOCKED — a human ran `git worktree lock`. Explicitly \"do not touch\"; never promptable.\n * - CURRENT — the worktree the command is running IN. Removing your own cwd is a self-destruct.\n * - DETACHED — detached HEAD, so there is no branch to judge and no branch to archive.\n */\nexport const CLASSIFICATION_PRUNABLE = 'prunable-worktree';\nexport const CLASSIFICATION_LOCKED = 'locked-worktree';\nexport const CLASSIFICATION_CURRENT = 'current-worktree';\nexport const CLASSIFICATION_DETACHED = 'detached-worktree';\n\n// Spared classifications a human can meaningfully rule on, most-safe first. wp-cleanup prompts in\n// exactly this order so the easy yeses come before the ones that need thought.\nexport const PROMPTABLE_CLASSIFICATIONS: readonly string[] = [\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n];\n\n/**\n * A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\n *\n * `sha`, `commits` and `prState` exist for the SPARED branches specifically. When nothing is\n * auto-reapable the branch cap has nothing safe to offer, and its only remaining advice was \"raise\n * maxLocalBranches\" / \"set turnOffRuleUntilEpoch\" — both of which loosen the rule. An agent with no\n * human present took the config edit, which is the exact failure the cap exists to prevent. To ask a\n * human \"may I delete these?\" instead, the guard has to be able to SHOW what it would delete: tip SHA\n * (so the delete is recoverable), PR state (closed? never opened?) and how much unique work is on it.\n *\n * All three are computed in the DETACHED refresher and read straight off merged-branches.json — the\n * blocking hook path never recomputes them. Defaulted so a cache written by an older release (and\n * every existing call site) revives without them.\n */\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n /** Short tip SHA — what makes the delete undoable (`git branch <name> <sha>`). '' when unknown. */\n sha: string;\n /** Commits on this branch that are not on origin/main. -1 when it could not be established. */\n commits: number;\n /** GitHub's state for the PR whose head is this branch: MERGED / CLOSED / OPEN, or '' for none. */\n prState: string;\n /**\n * One of the CLASSIFICATION_* tokens. Defaulted like the fields above so every pre-existing call\n * site — and every cache written by an older release — still constructs; an unclassified revived\n * entry reads as 'never-proposed', the most conservative of the spared verdicts.\n */\n classification: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n branch: string,\n reason: string,\n pr: number,\n sha: string = '',\n commits: number = -1,\n prState: string = '',\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.sha = sha;\n this.commits = commits;\n this.prState = prState;\n this.classification = classification;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n *\n * `classification` is the same STABLE token the branch verdicts carry, plus the four worktree-only\n * ones above. It exists for the same reason it does on DeletableBranch: `deletable` answers \"may the\n * tooling reap this unattended?\", and everything false used to collapse into one undifferentiated\n * \"spared\" that a human could not rule on. With a token, WorktreeReaper knows whether the reap is a\n * `prune` or a `remove`, and wp-cleanup knows which spared worktrees are worth ASKING about.\n * Defaulted so every pre-existing call site and every cache written by an older release still builds;\n * an unclassified revived entry reads as 'never-proposed', the most conservative spared verdict.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n classification: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n path: string,\n branch: string,\n reason: string,\n pr: number,\n deletable: boolean,\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n this.classification = classification;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\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}\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(private readonly worktrees: WorktreeService = new WorktreeService()) {}\n\n mergedBranchesPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, 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 writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n const cachePath = this.mergedBranchesPath(repoRoot);\n fs.mkdirSync(path.dirname(cachePath), { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\\n');\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): 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);\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 proofs the branch cap uses (merged PR, backup-of-merged, or zero commits of\n * its own). It is spared when it is LOCKED (a human said \"do not touch\"), when it is the worktree we\n * are standing in right now (removing your own cwd is not a thing to suggest to an agent), or when\n * its branch still holds unmerged work.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\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 if (tree.locked) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'locked by a human — do not touch', 0, false,\n CLASSIFICATION_LOCKED));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false, CLASSIFICATION_CURRENT));\n continue;\n }\n if (tree.branch === '') {\n out.push(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false,\n CLASSIFICATION_DETACHED));\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 const verdict = this.classify(repoRoot, tree.branch, prs);\n out.push(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable,\n verdict.entry.classification));\n }\n\n return out;\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 }\n\n // The one git-local signal that squash-merge CANNOT corrupt: a branch with zero commits of its\n // own holds no work, so deleting it can lose nothing. (Squash breaks patch-id and ancestry, so\n // \"are these commits in main?\" is unanswerable from git — but \"are there any commits at all?\"\n // is exact.) These are the husks left behind by branching and then never committing.\n if (commits === 0) {\n return new Verdict(true, new DeletableBranch(\n branch, 'no commits of its own — identical to origin/main', 0, sha, 0, prState,\n 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 // 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 => 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 }\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' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
@@ -0,0 +1,116 @@
1
+ import { BranchArchiver } from './branch-archiver';
2
+ import { BranchMutationLog, MutationVerb } from './branch-mutation-log';
3
+ import { DeletableWorktree } from './merged-branches';
4
+ import { WorktreeService } from './worktrees';
5
+ /**
6
+ * The EXECUTOR for the dead-WORKTREE verdicts that merged-branches.ts has been computing all along.
7
+ *
8
+ * WHY this exists: `DeletableWorktree` was designed for reaping — it carries `path` (what
9
+ * `git worktree remove` takes) AND `branch` (what `git branch -D` takes afterwards) precisely because
10
+ * a reap is always those two steps in that order. The reaping was then never wired up. The verdicts
11
+ * had exactly one consumer, branch-creation-guard, which used them only to BLOCK: at the worktree cap
12
+ * it printed the reap commands and refused to create the next worktree. Nothing ever ran them.
13
+ *
14
+ * That composes into a deadlock, not merely a missing feature. A merged worktree HOLDS its branch, so
15
+ * the branch lands in `keep` with "checked out in worktree '<path>' — remove that worktree before
16
+ * deleting the branch". Nothing removes the worktree. Both accumulate forever, and the guard's only
17
+ * remaining advice is to loosen its own cap — which is the failure the cap exists to prevent. Observed
18
+ * twice in one day: a `wp-cleanup` run that spared three worktree-held branches with that exact line,
19
+ * and another that spared seven.
20
+ *
21
+ * THE ORDER IS FIXED AND LOAD-BEARING: archive the branch as a tag → `git worktree remove <path>` →
22
+ * `git branch -D <branch>`.
23
+ * - Archive FIRST, and if it fails nothing is deleted. Same rule BranchArchiver already enforces for
24
+ * branches: a branch we could not tag is a branch whose only copy would be the reflog.
25
+ * - Remove the worktree BEFORE the branch, because git flatly refuses to delete a branch that is
26
+ * still checked out somewhere.
27
+ *
28
+ * AND NEVER `--force`. Git refuses to remove a worktree with uncommitted changes or untracked files,
29
+ * and that refusal is the entire safety property here: a worktree removal deletes real FILES, not just
30
+ * a ref, and an untracked file is by definition something no archive tag captured. A failed removal is
31
+ * reported and moved past, exactly as a failed branch delete already is.
32
+ */
33
+ export declare class ReapedWorktree {
34
+ path: string;
35
+ branch: string;
36
+ sha: string;
37
+ reason: string;
38
+ pr: number;
39
+ ok: boolean;
40
+ error: string;
41
+ archiveTag: string;
42
+ /**
43
+ * Did the BRANCH delete also succeed? A worktree removal that succeeds while the branch delete
44
+ * fails is a real, reportable half-state — the directory is gone, the branch is still there — and
45
+ * collapsing it into `ok` would hide it. `ok` means the DIRECTORY is gone; this means the pair is.
46
+ */
47
+ branchDeleted: boolean;
48
+ constructor(treePath: string, branch: string, sha: string, reason: string, pr: number, ok: boolean, error: string);
49
+ }
50
+ /**
51
+ * The outcome of one worktree reap. `spared` carries the worktrees we refused to touch, each with its
52
+ * reason, for the same reason ReapResult does: a cleanup silent about what it did NOT remove reads as
53
+ * "there was nothing else", when those are exactly the ones only a human can rule on.
54
+ */
55
+ export declare class WorktreeReapResult {
56
+ reaped: ReapedWorktree[];
57
+ failed: ReapedWorktree[];
58
+ spared: DeletableWorktree[];
59
+ constructor(reaped: ReapedWorktree[], failed: ReapedWorktree[], spared: DeletableWorktree[]);
60
+ }
61
+ export declare class WorktreeReaper {
62
+ private readonly worktrees;
63
+ private readonly mutationLog;
64
+ private readonly archiver;
65
+ constructor(worktrees?: WorktreeService, mutationLog?: BranchMutationLog, archiver?: BranchArchiver);
66
+ /**
67
+ * Reap every worktree in `targets`, skipping any the safety rails refuse.
68
+ *
69
+ * `cwd` is passed in rather than read from `process.cwd()` here so the "never remove the tree I am
70
+ * standing in" rule is testable and so a caller running from a subdirectory still gets the right
71
+ * answer — the containing WORKTREE is what matters, not the exact directory.
72
+ *
73
+ * `targets` is caller-chosen on purpose. wp-cleanup passes the provably-dead ones unattended, and
74
+ * passes human-approved probably-dead ones on a second call. The safety rails below apply to both:
75
+ * no answer at a prompt can authorise removing your own cwd or the primary clone.
76
+ */
77
+ reapWorktrees(repoRoot: string, cwd: string, verb: MutationVerb, targets: DeletableWorktree[], retention?: string): WorktreeReapResult;
78
+ /**
79
+ * The two directories that must never be removed, resolved to absolute paths so a relative
80
+ * `../foo` in a verdict can still be compared against them.
81
+ *
82
+ * - THE PRIMARY CLONE. It owns `.git`; `git worktree remove` cannot take it, and a caller who
83
+ * somehow got it into a target list has a bug we must not execute.
84
+ * - THE TREE WE ARE STANDING IN. Removing your own cwd mid-command deletes the files underneath
85
+ * the running process — including, when the tooling is invoked by an agent, the checkout the
86
+ * agent's next tool call will try to read. merged-branches.ts already declines to mark it
87
+ * deletable; this is the second, independent line, because the caller supplies the list.
88
+ */
89
+ private protectedPaths;
90
+ private currentTree;
91
+ private refuseReason;
92
+ /**
93
+ * One worktree: ARCHIVE the branch, REMOVE the directory, then DELETE the branch — and stop at the
94
+ * first step that fails.
95
+ *
96
+ * The prunable case is genuinely different and is why the classification token rides along on the
97
+ * verdict: the directory is ALREADY gone, so `git worktree remove` fails on it and the reap is
98
+ * `git worktree prune`. There is also nothing to archive from a directory that no longer exists —
99
+ * the branch itself is still archived, since it may well still hold the only copy of some work.
100
+ */
101
+ private removeOne;
102
+ private archiveBranch;
103
+ private archiveFailed;
104
+ /**
105
+ * git refused to remove the directory — nearly always because it holds uncommitted changes or
106
+ * untracked files. Reported with git's own words and moved past. We do NOT retry with `--force`:
107
+ * an untracked file is work no archive tag captured, and `--force` is how a cleanup command turns
108
+ * into a data-loss command. The branch is left alone too, since it is still checked out here.
109
+ */
110
+ private removalFailed;
111
+ private log;
112
+ /** The literal command that puts BOTH the directory and the branch back. */
113
+ restoreCommand(target: ReapedWorktree): string;
114
+ private revParse;
115
+ private capture;
116
+ }
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const child_process_1 = require("child_process");
6
+ const path = tslib_1.__importStar(require("path"));
7
+ const inversify_1 = require("inversify");
8
+ const branch_archiver_1 = require("./branch-archiver");
9
+ const branch_mutation_log_1 = require("./branch-mutation-log");
10
+ const merged_branches_1 = require("./merged-branches");
11
+ const worktrees_1 = require("./worktrees");
12
+ /**
13
+ * The EXECUTOR for the dead-WORKTREE verdicts that merged-branches.ts has been computing all along.
14
+ *
15
+ * WHY this exists: `DeletableWorktree` was designed for reaping — it carries `path` (what
16
+ * `git worktree remove` takes) AND `branch` (what `git branch -D` takes afterwards) precisely because
17
+ * a reap is always those two steps in that order. The reaping was then never wired up. The verdicts
18
+ * had exactly one consumer, branch-creation-guard, which used them only to BLOCK: at the worktree cap
19
+ * it printed the reap commands and refused to create the next worktree. Nothing ever ran them.
20
+ *
21
+ * That composes into a deadlock, not merely a missing feature. A merged worktree HOLDS its branch, so
22
+ * the branch lands in `keep` with "checked out in worktree '<path>' — remove that worktree before
23
+ * deleting the branch". Nothing removes the worktree. Both accumulate forever, and the guard's only
24
+ * remaining advice is to loosen its own cap — which is the failure the cap exists to prevent. Observed
25
+ * twice in one day: a `wp-cleanup` run that spared three worktree-held branches with that exact line,
26
+ * and another that spared seven.
27
+ *
28
+ * THE ORDER IS FIXED AND LOAD-BEARING: archive the branch as a tag → `git worktree remove <path>` →
29
+ * `git branch -D <branch>`.
30
+ * - Archive FIRST, and if it fails nothing is deleted. Same rule BranchArchiver already enforces for
31
+ * branches: a branch we could not tag is a branch whose only copy would be the reflog.
32
+ * - Remove the worktree BEFORE the branch, because git flatly refuses to delete a branch that is
33
+ * still checked out somewhere.
34
+ *
35
+ * AND NEVER `--force`. Git refuses to remove a worktree with uncommitted changes or untracked files,
36
+ * and that refusal is the entire safety property here: a worktree removal deletes real FILES, not just
37
+ * a ref, and an untracked file is by definition something no archive tag captured. A failed removal is
38
+ * reported and moved past, exactly as a failed branch delete already is.
39
+ */
40
+ // Data-only (per CLAUDE.md, classes for data). One worktree and what happened to it.
41
+ class ReapedWorktree {
42
+ path;
43
+ // The branch the worktree held, '' when it was detached.
44
+ branch;
45
+ // The branch's tip BEFORE anything was destroyed. '' when it could not be resolved (or detached).
46
+ sha;
47
+ reason;
48
+ pr;
49
+ ok;
50
+ // git's own stderr when ok=false. Kept verbatim — a refused removal is a thing a human must read.
51
+ error;
52
+ // The `archive/<date>/<branch>` tag written before the removal, or '' (policy 'delete', or detached).
53
+ archiveTag = '';
54
+ /**
55
+ * Did the BRANCH delete also succeed? A worktree removal that succeeds while the branch delete
56
+ * fails is a real, reportable half-state — the directory is gone, the branch is still there — and
57
+ * collapsing it into `ok` would hide it. `ok` means the DIRECTORY is gone; this means the pair is.
58
+ */
59
+ branchDeleted = false;
60
+ // eslint-disable-next-line @typescript-eslint/max-params
61
+ constructor(treePath, branch, sha, reason, pr, ok, error) {
62
+ this.path = treePath;
63
+ this.branch = branch;
64
+ this.sha = sha;
65
+ this.reason = reason;
66
+ this.pr = pr;
67
+ this.ok = ok;
68
+ this.error = error;
69
+ }
70
+ }
71
+ exports.ReapedWorktree = ReapedWorktree;
72
+ /**
73
+ * The outcome of one worktree reap. `spared` carries the worktrees we refused to touch, each with its
74
+ * reason, for the same reason ReapResult does: a cleanup silent about what it did NOT remove reads as
75
+ * "there was nothing else", when those are exactly the ones only a human can rule on.
76
+ */
77
+ class WorktreeReapResult {
78
+ reaped;
79
+ failed;
80
+ spared;
81
+ constructor(reaped, failed, spared) {
82
+ this.reaped = reaped;
83
+ this.failed = failed;
84
+ this.spared = spared;
85
+ }
86
+ }
87
+ exports.WorktreeReapResult = WorktreeReapResult;
88
+ // The two never-removable sets, kept apart so each refusal can name its own reason (data-only class
89
+ // per CLAUDE.md — `primary` is the clone that owns .git, `current` is the tree wp-cleanup runs in).
90
+ class ProtectedPaths {
91
+ primary;
92
+ current;
93
+ constructor(primary, current) {
94
+ this.primary = primary;
95
+ this.current = current;
96
+ }
97
+ }
98
+ let WorktreeReaper = class WorktreeReaper {
99
+ worktrees;
100
+ mutationLog;
101
+ archiver;
102
+ // Defaulted like BranchReaper's collaborators, so the non-DI call sites can just
103
+ // `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()) {
105
+ this.worktrees = worktrees;
106
+ this.mutationLog = mutationLog;
107
+ this.archiver = archiver;
108
+ }
109
+ /**
110
+ * Reap every worktree in `targets`, skipping any the safety rails refuse.
111
+ *
112
+ * `cwd` is passed in rather than read from `process.cwd()` here so the "never remove the tree I am
113
+ * standing in" rule is testable and so a caller running from a subdirectory still gets the right
114
+ * answer — the containing WORKTREE is what matters, not the exact directory.
115
+ *
116
+ * `targets` is caller-chosen on purpose. wp-cleanup passes the provably-dead ones unattended, and
117
+ * passes human-approved probably-dead ones on a second call. The safety rails below apply to both:
118
+ * no answer at a prompt can authorise removing your own cwd or the primary clone.
119
+ */
120
+ // eslint-disable-next-line @typescript-eslint/max-params
121
+ reapWorktrees(repoRoot, cwd, verb, targets, retention = branch_archiver_1.BRANCH_RETENTION_ARCHIVE_TAG) {
122
+ // 'keep' means "delete nothing, ever" — the reap degrades to a pure report, exactly as it does
123
+ // for branches, so a repo that opted out of destructive cleanup still SEES what would have gone.
124
+ if (retention === branch_archiver_1.BRANCH_RETENTION_KEEP)
125
+ return new WorktreeReapResult([], [], targets);
126
+ const protectedPaths = this.protectedPaths(repoRoot, cwd);
127
+ const reaped = [];
128
+ const failed = [];
129
+ const spared = [];
130
+ for (const target of targets) {
131
+ const refusal = this.refuseReason(target, protectedPaths);
132
+ if (refusal !== '') {
133
+ spared.push(new merged_branches_1.DeletableWorktree(target.path, target.branch, refusal, target.pr, false, target.classification));
134
+ continue;
135
+ }
136
+ const outcome = this.removeOne(repoRoot, verb, target, retention);
137
+ if (outcome.ok)
138
+ reaped.push(outcome);
139
+ else
140
+ failed.push(outcome);
141
+ }
142
+ return new WorktreeReapResult(reaped, failed, spared);
143
+ }
144
+ /**
145
+ * The two directories that must never be removed, resolved to absolute paths so a relative
146
+ * `../foo` in a verdict can still be compared against them.
147
+ *
148
+ * - THE PRIMARY CLONE. It owns `.git`; `git worktree remove` cannot take it, and a caller who
149
+ * somehow got it into a target list has a bug we must not execute.
150
+ * - THE TREE WE ARE STANDING IN. Removing your own cwd mid-command deletes the files underneath
151
+ * the running process — including, when the tooling is invoked by an agent, the checkout the
152
+ * agent's next tool call will try to read. merged-branches.ts already declines to mark it
153
+ * deletable; this is the second, independent line, because the caller supplies the list.
154
+ */
155
+ protectedPaths(repoRoot, cwd) {
156
+ const primary = new Set();
157
+ for (const tree of this.worktrees.listWorktrees(repoRoot)) {
158
+ if (tree.isMain)
159
+ primary.add(path.resolve(tree.path));
160
+ }
161
+ const here = new Set();
162
+ const current = this.currentTree(repoRoot, cwd);
163
+ if (current !== null)
164
+ here.add(path.resolve(current.path));
165
+ // Fail SAFE when git could not name the current worktree: protect the raw paths anyway. An
166
+ // over-protected path costs one worktree that survives to the next cleanup; an under-protected
167
+ // one costs the directory the command is running in.
168
+ here.add(path.resolve(repoRoot));
169
+ here.add(path.resolve(cwd));
170
+ return new ProtectedPaths(primary, here);
171
+ }
172
+ // The worktree record CONTAINING cwd — not merely the one whose path equals it, so `wp-cleanup`
173
+ // run from `packages/whatever` inside a worktree still protects that worktree.
174
+ currentTree(repoRoot, cwd) {
175
+ const here = path.resolve(cwd);
176
+ let best = null;
177
+ for (const tree of this.worktrees.listWorktrees(repoRoot)) {
178
+ const root = path.resolve(tree.path);
179
+ if (here !== root && !here.startsWith(root + path.sep))
180
+ continue;
181
+ // Longest match wins: worktrees can nest, and the innermost one is the one we are in.
182
+ if (best === null || root.length > path.resolve(best.path).length)
183
+ best = tree;
184
+ }
185
+ return best;
186
+ }
187
+ // '' when the target may be reaped; otherwise the human-readable reason it was spared instead. The
188
+ // two rails report SEPARATELY: "you are standing in it" and "that is the primary clone" are
189
+ // different mistakes, and a message covering both tells the reader neither.
190
+ refuseReason(target, protectedPaths) {
191
+ if (target.path === '')
192
+ return 'no path recorded for this worktree — nothing safe to remove';
193
+ const resolved = path.resolve(target.path);
194
+ if (protectedPaths.primary.has(resolved)) {
195
+ return 'refused — that is the primary clone, which owns .git and is not removable';
196
+ }
197
+ if (protectedPaths.current.has(resolved)) {
198
+ return 'refused — this command is running in that worktree; removing your own cwd is a self-destruct';
199
+ }
200
+ return '';
201
+ }
202
+ /**
203
+ * One worktree: ARCHIVE the branch, REMOVE the directory, then DELETE the branch — and stop at the
204
+ * first step that fails.
205
+ *
206
+ * The prunable case is genuinely different and is why the classification token rides along on the
207
+ * verdict: the directory is ALREADY gone, so `git worktree remove` fails on it and the reap is
208
+ * `git worktree prune`. There is also nothing to archive from a directory that no longer exists —
209
+ * the branch itself is still archived, since it may well still hold the only copy of some work.
210
+ */
211
+ removeOne(repoRoot, verb, target, retention) {
212
+ // Tip first: after the branch is gone there is nothing left to resolve, and the audit line's
213
+ // 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);
216
+ if (!archive.ok)
217
+ return this.archiveFailed(repoRoot, verb, target, sha, archive);
218
+ const removed = target.classification === merged_branches_1.CLASSIFICATION_PRUNABLE
219
+ ? this.capture(repoRoot, ['worktree', 'prune'])
220
+ : this.capture(repoRoot, ['worktree', 'remove', target.path]);
221
+ if (!removed.ok)
222
+ return this.removalFailed(repoRoot, verb, target, sha, archive, removed.err);
223
+ // Only NOW may the branch go: git refuses while a worktree still holds it.
224
+ const branchDeleted = target.branch === ''
225
+ || this.capture(repoRoot, ['branch', '-D', target.branch]).ok;
226
+ const result = new ReapedWorktree(target.path, target.branch, sha, target.reason, target.pr, true, '');
227
+ result.archiveTag = archive.tag;
228
+ result.branchDeleted = branchDeleted;
229
+ this.log(repoRoot, verb, target, sha, archive.tag, branchDeleted
230
+ ? `removed worktree and deleted branch (${target.reason})`
231
+ : `removed worktree; branch '${target.branch}' survived (git refused the delete)`);
232
+ return result;
233
+ }
234
+ // Archiving is skipped for a detached worktree (no branch to tag) and under retention 'delete'.
235
+ // Both report ok=true with an empty tag: there is nothing to archive, which is not a failure.
236
+ archiveBranch(repoRoot, target, retention, sha) {
237
+ if (target.branch === '' || retention !== branch_archiver_1.BRANCH_RETENTION_ARCHIVE_TAG) {
238
+ return new branch_archiver_1.ArchiveResult('', sha, true, '');
239
+ }
240
+ return this.archiver.archive(repoRoot, target.branch);
241
+ }
242
+ // Archive refused ⇒ NOTHING is removed. The directory survives to the next cleanup, which is the
243
+ // fail-safe direction: the alternative is deleting files whose branch has no permanent ref.
244
+ // eslint-disable-next-line @typescript-eslint/max-params
245
+ archiveFailed(repoRoot, verb, target, sha, archive) {
246
+ const error = `not removed — could not archive its branch first: ${archive.error}`;
247
+ this.log(repoRoot, verb, target, sha, '', `SKIPPED (${error})`);
248
+ return new ReapedWorktree(target.path, target.branch, sha, target.reason, target.pr, false, error);
249
+ }
250
+ /**
251
+ * git refused to remove the directory — nearly always because it holds uncommitted changes or
252
+ * untracked files. Reported with git's own words and moved past. We do NOT retry with `--force`:
253
+ * an untracked file is work no archive tag captured, and `--force` is how a cleanup command turns
254
+ * into a data-loss command. The branch is left alone too, since it is still checked out here.
255
+ */
256
+ // eslint-disable-next-line @typescript-eslint/max-params
257
+ removalFailed(repoRoot, verb, target, sha, archive, err) {
258
+ const error = `git refused to remove it: ${err} (not forced — untracked or modified files are `
259
+ + 'work nothing has archived; remove them or the worktree by hand)';
260
+ this.log(repoRoot, verb, target, sha, archive.tag, `FAILED (${err})`);
261
+ const result = new ReapedWorktree(target.path, target.branch, sha, target.reason, target.pr, false, error);
262
+ result.archiveTag = archive.tag;
263
+ return result;
264
+ }
265
+ // eslint-disable-next-line @typescript-eslint/max-params
266
+ log(repoRoot, verb, target, sha, archiveTag, outcome) {
267
+ const event = new branch_mutation_log_1.BranchMutationEvent(verb, 'REAP_WORKTREE');
268
+ event.fromBranch = target.branch;
269
+ event.sha = sha;
270
+ event.archiveTag = archiveTag;
271
+ event.worktreePath = target.path;
272
+ event.outcome = outcome;
273
+ this.mutationLog.logBranchMutation(repoRoot, event);
274
+ }
275
+ /** The literal command that puts BOTH the directory and the branch back. */
276
+ restoreCommand(target) {
277
+ const ref = target.archiveTag !== '' ? target.archiveTag : target.sha;
278
+ if (ref === '')
279
+ return `git worktree add ${target.path} <ref>`;
280
+ if (target.branch === '')
281
+ return `git worktree add ${target.path} ${ref}`;
282
+ return `git worktree add -b ${target.branch} ${target.path} ${ref}`;
283
+ }
284
+ revParse(repoRoot, ref) {
285
+ const result = this.capture(repoRoot, ['rev-parse', ref]);
286
+ return result.ok ? result.out : '';
287
+ }
288
+ // Run a git command capturing trimmed stdout/stderr; ok=false on spawn failure or non-zero exit.
289
+ capture(repoRoot, args) {
290
+ const result = (0, child_process_1.spawnSync)('git', args, { cwd: repoRoot, encoding: 'utf8' });
291
+ const err = typeof result.stderr === 'string' ? result.stderr.trim() : '';
292
+ if (result.status !== 0 || typeof result.stdout !== 'string') {
293
+ return { ok: false, out: '', err: err !== '' ? err : 'git command failed' };
294
+ }
295
+ return { ok: true, out: result.stdout.trim(), err };
296
+ }
297
+ };
298
+ exports.WorktreeReaper = WorktreeReaper;
299
+ exports.WorktreeReaper = WorktreeReaper = tslib_1.__decorate([
300
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
301
+ tslib_1.__metadata("design:paramtypes", [worktrees_1.WorktreeService,
302
+ branch_mutation_log_1.BranchMutationLog,
303
+ branch_archiver_1.BranchArchiver])
304
+ ], WorktreeReaper);
305
+ //# sourceMappingURL=worktree-reaper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-reaper.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/worktree-reaper.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport {\n ArchiveResult,\n BranchArchiver,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nimport { BranchMutationEvent, BranchMutationLog, MutationVerb } from './branch-mutation-log';\nimport { CLASSIFICATION_PRUNABLE, DeletableWorktree } from './merged-branches';\nimport { Worktree, WorktreeService } from './worktrees';\n\n/**\n * The EXECUTOR for the dead-WORKTREE verdicts that merged-branches.ts has been computing all along.\n *\n * WHY this exists: `DeletableWorktree` was designed for reaping — it carries `path` (what\n * `git worktree remove` takes) AND `branch` (what `git branch -D` takes afterwards) precisely because\n * a reap is always those two steps in that order. The reaping was then never wired up. The verdicts\n * had exactly one consumer, branch-creation-guard, which used them only to BLOCK: at the worktree cap\n * it printed the reap commands and refused to create the next worktree. Nothing ever ran them.\n *\n * That composes into a deadlock, not merely a missing feature. A merged worktree HOLDS its branch, so\n * the branch lands in `keep` with \"checked out in worktree '<path>' — remove that worktree before\n * deleting the branch\". Nothing removes the worktree. Both accumulate forever, and the guard's only\n * remaining advice is to loosen its own cap — which is the failure the cap exists to prevent. Observed\n * twice in one day: a `wp-cleanup` run that spared three worktree-held branches with that exact line,\n * and another that spared seven.\n *\n * THE ORDER IS FIXED AND LOAD-BEARING: archive the branch as a tag → `git worktree remove <path>` →\n * `git branch -D <branch>`.\n * - Archive FIRST, and if it fails nothing is deleted. Same rule BranchArchiver already enforces for\n * branches: a branch we could not tag is a branch whose only copy would be the reflog.\n * - Remove the worktree BEFORE the branch, because git flatly refuses to delete a branch that is\n * still checked out somewhere.\n *\n * AND NEVER `--force`. Git refuses to remove a worktree with uncommitted changes or untracked files,\n * and that refusal is the entire safety property here: a worktree removal deletes real FILES, not just\n * a ref, and an untracked file is by definition something no archive tag captured. A failed removal is\n * reported and moved past, exactly as a failed branch delete already is.\n */\n\n// Data-only (per CLAUDE.md, classes for data). One worktree and what happened to it.\nexport class ReapedWorktree {\n path: string;\n // The branch the worktree held, '' when it was detached.\n branch: string;\n // The branch's tip BEFORE anything was destroyed. '' when it could not be resolved (or detached).\n sha: string;\n reason: string;\n pr: number;\n ok: boolean;\n // git's own stderr when ok=false. Kept verbatim — a refused removal is a thing a human must read.\n error: string;\n // The `archive/<date>/<branch>` tag written before the removal, or '' (policy 'delete', or detached).\n archiveTag: string = '';\n /**\n * Did the BRANCH delete also succeed? A worktree removal that succeeds while the branch delete\n * fails is a real, reportable half-state — the directory is gone, the branch is still there — and\n * collapsing it into `ok` would hide it. `ok` means the DIRECTORY is gone; this means the pair is.\n */\n branchDeleted: boolean = false;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(treePath: string, branch: string, sha: string, reason: string, pr: number, ok: boolean, error: string) {\n this.path = treePath;\n this.branch = branch;\n this.sha = sha;\n this.reason = reason;\n this.pr = pr;\n this.ok = ok;\n this.error = error;\n }\n}\n\n/**\n * The outcome of one worktree reap. `spared` carries the worktrees we refused to touch, each with its\n * reason, for the same reason ReapResult does: a cleanup silent about what it did NOT remove reads as\n * \"there was nothing else\", when those are exactly the ones only a human can rule on.\n */\nexport class WorktreeReapResult {\n reaped: ReapedWorktree[];\n failed: ReapedWorktree[];\n spared: DeletableWorktree[];\n\n constructor(reaped: ReapedWorktree[], failed: ReapedWorktree[], spared: DeletableWorktree[]) {\n this.reaped = reaped;\n this.failed = failed;\n this.spared = spared;\n }\n}\n\n// The two never-removable sets, kept apart so each refusal can name its own reason (data-only class\n// per CLAUDE.md — `primary` is the clone that owns .git, `current` is the tree wp-cleanup runs in).\nclass ProtectedPaths {\n primary: Set<string>;\n current: Set<string>;\n\n constructor(primary: Set<string>, current: Set<string>) {\n this.primary = primary;\n this.current = current;\n }\n}\n\n// Result of a captured git invocation. `err` carries stderr so a refused removal can be reported.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n err: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeReaper {\n // Defaulted like BranchReaper's collaborators, so the non-DI call sites can just\n // `new WorktreeReaper()` while inversify still injects the singletons from a container.\n constructor(\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly mutationLog: BranchMutationLog = new BranchMutationLog(),\n private readonly archiver: BranchArchiver = new BranchArchiver(),\n ) {}\n\n /**\n * Reap every worktree in `targets`, skipping any the safety rails refuse.\n *\n * `cwd` is passed in rather than read from `process.cwd()` here so the \"never remove the tree I am\n * standing in\" rule is testable and so a caller running from a subdirectory still gets the right\n * answer — the containing WORKTREE is what matters, not the exact directory.\n *\n * `targets` is caller-chosen on purpose. wp-cleanup passes the provably-dead ones unattended, and\n * passes human-approved probably-dead ones on a second call. The safety rails below apply to both:\n * no answer at a prompt can authorise removing your own cwd or the primary clone.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n reapWorktrees(\n repoRoot: string,\n cwd: string,\n verb: MutationVerb,\n targets: DeletableWorktree[],\n retention: string = BRANCH_RETENTION_ARCHIVE_TAG,\n ): WorktreeReapResult {\n // 'keep' means \"delete nothing, ever\" — the reap degrades to a pure report, exactly as it does\n // for branches, so a repo that opted out of destructive cleanup still SEES what would have gone.\n if (retention === BRANCH_RETENTION_KEEP) return new WorktreeReapResult([], [], targets);\n\n const protectedPaths = this.protectedPaths(repoRoot, cwd);\n const reaped: ReapedWorktree[] = [];\n const failed: ReapedWorktree[] = [];\n const spared: DeletableWorktree[] = [];\n\n for (const target of targets) {\n const refusal = this.refuseReason(target, protectedPaths);\n if (refusal !== '') {\n spared.push(new DeletableWorktree(\n target.path, target.branch, refusal, target.pr, false, target.classification));\n continue;\n }\n const outcome = this.removeOne(repoRoot, verb, target, retention);\n if (outcome.ok) reaped.push(outcome);\n else failed.push(outcome);\n }\n\n return new WorktreeReapResult(reaped, failed, spared);\n }\n\n /**\n * The two directories that must never be removed, resolved to absolute paths so a relative\n * `../foo` in a verdict can still be compared against them.\n *\n * - THE PRIMARY CLONE. It owns `.git`; `git worktree remove` cannot take it, and a caller who\n * somehow got it into a target list has a bug we must not execute.\n * - THE TREE WE ARE STANDING IN. Removing your own cwd mid-command deletes the files underneath\n * the running process — including, when the tooling is invoked by an agent, the checkout the\n * agent's next tool call will try to read. merged-branches.ts already declines to mark it\n * deletable; this is the second, independent line, because the caller supplies the list.\n */\n private protectedPaths(repoRoot: string, cwd: string): ProtectedPaths {\n const primary = new Set<string>();\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n if (tree.isMain) primary.add(path.resolve(tree.path));\n }\n\n const here = new Set<string>();\n const current = this.currentTree(repoRoot, cwd);\n if (current !== null) here.add(path.resolve(current.path));\n // Fail SAFE when git could not name the current worktree: protect the raw paths anyway. An\n // over-protected path costs one worktree that survives to the next cleanup; an under-protected\n // one costs the directory the command is running in.\n here.add(path.resolve(repoRoot));\n here.add(path.resolve(cwd));\n return new ProtectedPaths(primary, here);\n }\n\n // The worktree record CONTAINING cwd — not merely the one whose path equals it, so `wp-cleanup`\n // run from `packages/whatever` inside a worktree still protects that worktree.\n private currentTree(repoRoot: string, cwd: string): Worktree | null {\n const here = path.resolve(cwd);\n let best: Worktree | null = null;\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n const root = path.resolve(tree.path);\n if (here !== root && !here.startsWith(root + path.sep)) continue;\n // Longest match wins: worktrees can nest, and the innermost one is the one we are in.\n if (best === null || root.length > path.resolve(best.path).length) best = tree;\n }\n return best;\n }\n\n // '' when the target may be reaped; otherwise the human-readable reason it was spared instead. The\n // two rails report SEPARATELY: \"you are standing in it\" and \"that is the primary clone\" are\n // different mistakes, and a message covering both tells the reader neither.\n private refuseReason(target: DeletableWorktree, protectedPaths: ProtectedPaths): string {\n if (target.path === '') return 'no path recorded for this worktree — nothing safe to remove';\n const resolved = path.resolve(target.path);\n if (protectedPaths.primary.has(resolved)) {\n return 'refused — that is the primary clone, which owns .git and is not removable';\n }\n if (protectedPaths.current.has(resolved)) {\n return 'refused — this command is running in that worktree; removing your own cwd is a self-destruct';\n }\n return '';\n }\n\n /**\n * One worktree: ARCHIVE the branch, REMOVE the directory, then DELETE the branch — and stop at the\n * first step that fails.\n *\n * The prunable case is genuinely different and is why the classification token rides along on the\n * verdict: the directory is ALREADY gone, so `git worktree remove` fails on it and the reap is\n * `git worktree prune`. There is also nothing to archive from a directory that no longer exists —\n * the branch itself is still archived, since it may well still hold the only copy of some work.\n */\n private removeOne(\n repoRoot: string,\n verb: MutationVerb,\n target: DeletableWorktree,\n retention: string,\n ): ReapedWorktree {\n // Tip first: after the branch is gone there is nothing left to resolve, and the audit line's\n // whole job is to record what was destroyed while it still exists.\n const sha = target.branch !== '' ? this.revParse(repoRoot, target.branch) : '';\n\n const archive = this.archiveBranch(repoRoot, target, retention, sha);\n if (!archive.ok) return this.archiveFailed(repoRoot, verb, target, sha, archive);\n\n const removed = target.classification === CLASSIFICATION_PRUNABLE\n ? this.capture(repoRoot, ['worktree', 'prune'])\n : this.capture(repoRoot, ['worktree', 'remove', target.path]);\n if (!removed.ok) return this.removalFailed(repoRoot, verb, target, sha, archive, removed.err);\n\n // Only NOW may the branch go: git refuses while a worktree still holds it.\n const branchDeleted = target.branch === ''\n || this.capture(repoRoot, ['branch', '-D', target.branch]).ok;\n\n const result = new ReapedWorktree(\n target.path, target.branch, sha, target.reason, target.pr, true, '');\n result.archiveTag = archive.tag;\n result.branchDeleted = branchDeleted;\n\n this.log(repoRoot, verb, target, sha, archive.tag, branchDeleted\n ? `removed worktree and deleted branch (${target.reason})`\n : `removed worktree; branch '${target.branch}' survived (git refused the delete)`);\n return result;\n }\n\n // Archiving is skipped for a detached worktree (no branch to tag) and under retention 'delete'.\n // Both report ok=true with an empty tag: there is nothing to archive, which is not a failure.\n private archiveBranch(\n repoRoot: string, target: DeletableWorktree, retention: string, sha: string,\n ): ArchiveResult {\n if (target.branch === '' || retention !== BRANCH_RETENTION_ARCHIVE_TAG) {\n return new ArchiveResult('', sha, true, '');\n }\n return this.archiver.archive(repoRoot, target.branch);\n }\n\n // Archive refused ⇒ NOTHING is removed. The directory survives to the next cleanup, which is the\n // fail-safe direction: the alternative is deleting files whose branch has no permanent ref.\n // eslint-disable-next-line @typescript-eslint/max-params\n private archiveFailed(\n repoRoot: string, verb: MutationVerb, target: DeletableWorktree, sha: string, archive: ArchiveResult,\n ): ReapedWorktree {\n const error = `not removed — could not archive its branch first: ${archive.error}`;\n this.log(repoRoot, verb, target, sha, '', `SKIPPED (${error})`);\n return new ReapedWorktree(target.path, target.branch, sha, target.reason, target.pr, false, error);\n }\n\n /**\n * git refused to remove the directory — nearly always because it holds uncommitted changes or\n * untracked files. Reported with git's own words and moved past. We do NOT retry with `--force`:\n * an untracked file is work no archive tag captured, and `--force` is how a cleanup command turns\n * into a data-loss command. The branch is left alone too, since it is still checked out here.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private removalFailed(\n repoRoot: string, verb: MutationVerb, target: DeletableWorktree, sha: string,\n archive: ArchiveResult, err: string,\n ): ReapedWorktree {\n const error = `git refused to remove it: ${err} (not forced — untracked or modified files are `\n + 'work nothing has archived; remove them or the worktree by hand)';\n this.log(repoRoot, verb, target, sha, archive.tag, `FAILED (${err})`);\n const result = new ReapedWorktree(\n target.path, target.branch, sha, target.reason, target.pr, false, error);\n result.archiveTag = archive.tag;\n return result;\n }\n\n // eslint-disable-next-line @typescript-eslint/max-params\n private log(\n repoRoot: string, verb: MutationVerb, target: DeletableWorktree,\n sha: string, archiveTag: string, outcome: string,\n ): void {\n const event = new BranchMutationEvent(verb, 'REAP_WORKTREE');\n event.fromBranch = target.branch;\n event.sha = sha;\n event.archiveTag = archiveTag;\n event.worktreePath = target.path;\n event.outcome = outcome;\n this.mutationLog.logBranchMutation(repoRoot, event);\n }\n\n /** The literal command that puts BOTH the directory and the branch back. */\n restoreCommand(target: ReapedWorktree): string {\n const ref = target.archiveTag !== '' ? target.archiveTag : target.sha;\n if (ref === '') return `git worktree add ${target.path} <ref>`;\n if (target.branch === '') return `git worktree add ${target.path} ${ref}`;\n return `git worktree add -b ${target.branch} ${target.path} ${ref}`;\n }\n\n private revParse(repoRoot: string, ref: string): string {\n const result = this.capture(repoRoot, ['rev-parse', ref]);\n return result.ok ? result.out : '';\n }\n\n // Run a git command capturing trimmed stdout/stderr; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, args: string[]): CmdCapture {\n const result = spawnSync('git', args, { cwd: repoRoot, encoding: 'utf8' });\n const err = typeof result.stderr === 'string' ? result.stderr.trim() : '';\n if (result.status !== 0 || typeof result.stdout !== 'string') {\n return { ok: false, out: '', err: err !== '' ? err : 'git command failed' };\n }\n return { ok: true, out: result.stdout.trim(), err };\n }\n}\n"]}