@webpieces/ai-hook-rules 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/ai-hook-rules",
3
- "version": "0.4.496",
3
+ "version": "0.4.498",
4
4
  "description": "Pluggable write-time validation framework for AI coding agents (@webpieces/ai-hook-rules). Claude Code PreToolUse + openclaw before_tool_call adapters share one rule engine.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -32,7 +32,7 @@
32
32
  "directory": "packages/tooling/ai-hook-rules"
33
33
  },
34
34
  "dependencies": {
35
- "@webpieces/rules-config": "0.4.496"
35
+ "@webpieces/rules-config": "0.4.498"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
@@ -126,15 +126,21 @@ export declare class BranchCreationGuardRule extends BashRuleBase<BranchCreation
126
126
  */
127
127
  private askHumanOption;
128
128
  /**
129
- * The worktree reap instructions.
130
- *
131
- * The command ORDER is load-bearing and is the whole reason this is generated rather than described:
132
- * 1. `git worktree prune` clears the admin data of worktrees whose directory is already gone.
133
- * `git worktree remove` FAILS on those, so it cannot be the first step.
134
- * 2. `git worktree remove <path>` one path per invocation; git takes no path list here.
135
- * 3. `git branch -D <names>` only now. git flatly refuses to delete a branch that is still
136
- * checked out in a worktree, so a branch delete placed before the removal fails, and because
137
- * it is one multi-name command it takes every other branch in the list down with it.
129
+ * The worktree reap remedy.
130
+ *
131
+ * It is now ONE command `pnpm wp-cleanup` and that is the point of it. The hand-written
132
+ * sequence this used to print (prune remove each path one multi-name `git branch -D`) was
133
+ * correct git, and it was still never run: an AI agent reads a raw `worktree remove` / `-D` as
134
+ * destructive, asks permission, and stops. A guard whose only actionable remedies are "raise the
135
+ * cap" and "set turnOffRuleUntilEpoch" is a guard that teaches config edits, which is the exact
136
+ * failure the cap exists to prevent. wp-cleanup archives each branch as a tag, removes the
137
+ * directory, then deletes the branch in that order, because git refuses to delete a branch a
138
+ * worktree still holds — and it will not touch the primary clone, the tree you are standing in, or
139
+ * anything with uncommitted work.
140
+ *
141
+ * The explicit git sequence is still printed BELOW the command, as reference rather than as the
142
+ * instruction: when wp-cleanup declines one of these (typically untracked files), the human needs
143
+ * to see which directory and what git would have run.
138
144
  */
139
145
  private worktreeCapFixHint;
140
146
  }
@@ -471,15 +471,21 @@ class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
471
471
  'Do NOT delete any of these without an explicit yes, and do NOT edit webpieces.config.json instead.', preferred);
472
472
  }
473
473
  /**
474
- * The worktree reap instructions.
474
+ * The worktree reap remedy.
475
475
  *
476
- * The command ORDER is load-bearing and is the whole reason this is generated rather than described:
477
- * 1. `git worktree prune` clears the admin data of worktrees whose directory is already gone.
478
- * `git worktree remove` FAILS on those, so it cannot be the first step.
479
- * 2. `git worktree remove <path>` one path per invocation; git takes no path list here.
480
- * 3. `git branch -D <names>` only now. git flatly refuses to delete a branch that is still
481
- * checked out in a worktree, so a branch delete placed before the removal fails, and because
482
- * it is one multi-name command it takes every other branch in the list down with it.
476
+ * It is now ONE command `pnpm wp-cleanup` and that is the point of it. The hand-written
477
+ * sequence this used to print (prune remove each path one multi-name `git branch -D`) was
478
+ * correct git, and it was still never run: an AI agent reads a raw `worktree remove` / `-D` as
479
+ * destructive, asks permission, and stops. A guard whose only actionable remedies are "raise the
480
+ * cap" and "set turnOffRuleUntilEpoch" is a guard that teaches config edits, which is the exact
481
+ * failure the cap exists to prevent. wp-cleanup archives each branch as a tag, removes the
482
+ * directory, then deletes the branch in that order, because git refuses to delete a branch a
483
+ * worktree still holds — and it will not touch the primary clone, the tree you are standing in, or
484
+ * anything with uncommitted work.
485
+ *
486
+ * The explicit git sequence is still printed BELOW the command, as reference rather than as the
487
+ * instruction: when wp-cleanup declines one of these (typically untracked files), the human needs
488
+ * to see which directory and what git would have run.
483
489
  */
484
490
  worktreeCapFixHint(cache) {
485
491
  const options = [];
@@ -496,10 +502,22 @@ class BranchCreationGuardRule extends rule_base_1.BashRuleBase {
496
502
  .filter((branch) => branch !== '');
497
503
  if (branches.length > 0)
498
504
  steps.push(`git branch -D ${branches.join(' ')}`);
499
- options.push(new fix_hint_1.Option(`Remove these ${String(dead.length)} dead worktrees each holds a branch backed by a MERGED ` +
500
- 'PR, a branch with no commits of its own, or a directory that is already gone, so no work ' +
501
- 'can be lost (see merged-branches.json for the per-worktree reason). Run it in this order ' +
502
- `(prune first, branches last git refuses to delete a branch a worktree still holds): ${steps.join(' && ')}`, true));
505
+ options.push(new fix_hint_1.Option(`Run: pnpm wp-cleanup — it REMOVES these ${String(dead.length)} dead worktrees for you. Each ` +
506
+ 'holds a branch backed by a MERGED PR, a branch with no commits of its own, or a directory ' +
507
+ 'that is already gone, so no work can be lost (see merged-branches.json for the per-worktree ' +
508
+ 'reason). It archives every branch as an `archive/<date>/<branch>` tag BEFORE removing ' +
509
+ 'anything, logs each removal with a `recover=` command, and refuses to touch the primary ' +
510
+ 'clone, the worktree you are standing in, or any worktree holding uncommitted work. ' +
511
+ `Equivalent by hand, in this order (prune first, branches last — git refuses to delete a ` +
512
+ `branch a worktree still holds): ${steps.join(' && ')}`, true));
513
+ }
514
+ else {
515
+ // Nothing is PROVABLY dead, but wp-cleanup also prompts about the probably-dead ones, so it
516
+ // is still the remedy that can delete something — and it is still better advice than a knob.
517
+ options.push(new fix_hint_1.Option('Run: pnpm wp-cleanup — none of these worktrees is provably dead, but it lists the ' +
518
+ 'probably-dead ones (closed-unmerged PR, content already in main, never proposed) with ' +
519
+ 'their branch and reason and asks which to remove. Answer that prompt instead of raising ' +
520
+ 'the cap.', true));
503
521
  }
504
522
  options.push(new fix_hint_1.Option('If you genuinely need more worktrees in flight, raise branch-creation-guard.maxWorktrees ' +
505
523
  'in webpieces.config.json.'));
@@ -1 +1 @@
1
- {"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport {\n BranchCreationGuardConfig,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n WorktreeService,\n readMainSyncStatus,\n} from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\nimport { toError } from '../to-error';\n\n// Defaults used when the rule has no explicit value in webpieces.config.json.\n// branchFormat is a human sentence telling the AI how to name a branch created off main; it is\n// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).\nconst DEFAULT_BRANCH_FORMAT =\n 'Name it {whoami}/<short-feature-description> — lowercase, no version numbers, no sub/ prefix (e.g. dean/upgrade-webpieces)';\nconst DEFAULT_SUB_BRANCH_NAMING = 'feature/<ticket>/<short-description>';\n\n// Hard cap on local feature branches. Enforced at CREATION because that is the one moment cleanup is\n// both cheap and obviously worth it — reaping happens over time, never \"ASAP\".\n//\n// The branch cap counts PARKED branches only — branches not checked out in any worktree. Worktree-held\n// branches are counted by the worktree cap instead. Two budgets, because they are not substitutes: if\n// held branches also spent the branch budget, five worktrees would leave room for zero branches and no\n// branch could ever be created again.\nconst DEFAULT_MAX_LOCAL_BRANCHES = 5;\nconst DEFAULT_MAX_WORKTREES = 5;\n\n// A plausible git ref name. Deliberately NOT `[^\\s-]` — that class matches shell metacharacters, so\n// `git branch | wc -l` (a read-only LISTING, piped) was parsed as \"create a branch named `|`\" and\n// blocked. Cleanup work necessarily reads and deletes branches, so a listing must never trip this.\nconst REF_NAME = String.raw`[A-Za-z0-9][A-Za-z0-9_./-]*`;\n\nconst BRANCH_PATTERNS: RegExp[] = [\n new RegExp(String.raw`git\\s+checkout\\s+-[bB]\\s+(${REF_NAME})`),\n new RegExp(String.raw`git\\s+switch\\s+-[cC]\\s+(${REF_NAME})`),\n new RegExp(String.raw`git\\s+branch\\s+(?!-)(${REF_NAME})`),\n // `git worktree add ../dir -b <name> origin/main` — the form docs/git-workflow.md recommends for\n // starting a feature. It creates a branch just as surely as `checkout -b` does, and until this\n // pattern existed it walked straight past the cap, the reserved-suffix check and the sub-branch\n // check. `(?:\\S+\\s+)*?` absorbs the path and any other flags, so the -b may precede or follow them.\n new RegExp(String.raw`git\\s+worktree\\s+add\\s+(?:\\S+\\s+)*?-[bB]\\s+(${REF_NAME})`),\n];\n\n// ANY worktree creation, with or without -b. The no-`-b` forms (`git worktree add ../dir existing`,\n// `--detach`) create no branch but DO create a worktree, so they spend the worktree budget and must\n// still hit the cap.\nconst WORKTREE_ADD = /git\\s+worktree\\s+add\\b/;\n\n// `git worktree add <path> <existing-branch>` — the checkout-an-existing-branch form. Captures the\n// LAST bare (non-flag) argument, which is the committish; the first bare argument is the path.\n// Flags that take a value (`--reason <s>`, `-b <name>`) are excluded by the caller, which only uses\n// this on commands with no `-b`/`-B` at all.\nconst WORKTREE_ADD_EXISTING = new RegExp(\n String.raw`git\\s+worktree\\s+add\\s+(?:-{1,2}[A-Za-z-]+\\s+)*\\S+\\s+(${REF_NAME})`,\n);\n\n// `git branch <name> <sha>` — RESTORING a branch at an explicit commit, which is exactly the\n// `recover=` command wp-cleanup writes to branch-mutations.log for every branch it reaps.\n//\n// This must be allowed UNCONDITIONALLY, ahead of even the caps. The entire argument for letting the\n// tooling delete branches unattended is that any delete is one logged command away from being undone\n// — so a guard that blocks that command turns a real guarantee into a decorative one. (It did: the\n// generic `git branch <name>` creation pattern matched the restore and refused it, demanding the\n// branch be recreated off origin/main, which is precisely the content the restore is meant to bring\n// back.) A restore also cannot grow the branch list beyond what already existed.\nconst RESTORE_AT_SHA = new RegExp(String.raw`git\\s+branch\\s+${REF_NAME}\\s+[0-9a-f]{7,40}(?:\\W|$)`);\n\n// A trailing `wp<number>` was the old squash-merge generation marker (base → basewp2 → basewp3).\n// The tooling NO LONGER produces it — a sync now lands back on the same feature name — but the suffix\n// stays RESERVED so a human branch can't collide with a leftover `…wpN` still floating in a consumer\n// repo mid-transition. Block it at creation time and steer the name back to the plain feature form.\nconst RESERVED_GENERATION_SUFFIX = /wp\\d+$/;\n\n// A branch-creation command that explicitly bases off origin/main (e.g. `git checkout -b feat\n// origin/main`). This is exactly the fresh-main base the guard wants, and it works from ANY current\n// branch or linked worktree — main need not (and in a worktree cannot) be checked out here. Allowed\n// unconditionally so the recovery messages can safely tell you to run it from a worktree.\n//\n// The trailing check is `\\W|$`, not `\\s|$`: the ALLOW pattern must not be stricter about delimiters\n// than the BLOCK pattern above, or a `git checkout -b x origin/main` that ends at a quote or backtick\n// is seen as a branch creation but NOT as an origin/main one — recognised, then wrongly blocked.\nconst ORIGIN_MAIN_BASE = /git\\s+(?:checkout\\s+-[bB]|switch\\s+-[cC])\\s+\\S+\\s+origin\\/main(?:\\W|$)/;\n\n// The worktree arm of the same allow: `git worktree add ../dir -b <name> origin/main`. Same fresh-main\n// base, same reasoning — and in a worktree it is the ONLY workable base, since `git checkout main`\n// fatals there. Kept separate from ORIGIN_MAIN_BASE because the argument shape differs (a path sits\n// between the subcommand and the flags).\nconst WORKTREE_ORIGIN_MAIN_BASE = /git\\s+worktree\\s+add\\s+(?:\\S+\\s+)*origin\\/main(?:\\W|$)/;\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n // `--no-write-fetch-head` for the same reason as the background refresher (see\n // MainSyncStatusService.fetchOriginMain): this refresh can run while the agent is mid\n // `git fetch`/`git pull`, and two overlapping writers of the unlocked `.git/FETCH_HEAD` can leave\n // a duplicate for-merge line that makes the agent's `git pull` fatal with \"multiple branches\". We\n // read `origin/main` below, never FETCH_HEAD. On a git too old for the flag, retry the plain form.\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n execSync('git fetch --no-write-fetch-head origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n } catch (err: unknown) {\n const error = toError(err);\n const text = error.message.toLowerCase();\n const tooOld = text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n if (!tooOld) throw error;\n execSync('git fetch origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n }\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nexport class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {\n constructor(config: BranchCreationGuardConfig) { super(config, 'branch-creation-guard'); }\n\n readonly description =\n 'Block new-branch and new-worktree creation when main is stale, when branching off a non-main ' +\n 'branch, or when the branch/worktree count is at its cap (forces cleanup of dead ones).';\n override readonly defaultOptions = {\n subBranchNaming: DEFAULT_SUB_BRANCH_NAMING,\n branchFormat: DEFAULT_BRANCH_FORMAT,\n maxLocalBranches: DEFAULT_MAX_LOCAL_BRANCHES,\n maxWorktrees: DEFAULT_MAX_WORKTREES,\n };\n\n private readonly worktrees = new WorktreeService();\n private readonly mergedBranches = new MergedBranchesService(this.worktrees);\n\n // Set by check() when (and only when) a cap is what blocked, so fixHint can render the reap\n // instructions instead of the branch-naming ones. Same instance-field handoff pr-merge-guard uses.\n // Two fields, because the two caps reap different things and their hints share no wording.\n private capCache: MergedBranchesCache | null = null;\n private worktreeCapCache: MergedBranchesCache | null = null;\n\n // True when the blocked command was a `git worktree add`, so the recovery command we hand back is\n // a worktree command and not a `git checkout -b` the user cannot use here.\n private worktreeAdd = false;\n\n private get branchFormat(): string {\n return this.config.branchFormat ?? DEFAULT_BRANCH_FORMAT;\n }\n\n private get subBranchNaming(): string {\n return this.config.subBranchNaming ?? DEFAULT_SUB_BRANCH_NAMING;\n }\n\n private get maxLocalBranches(): number {\n return this.config.maxLocalBranches ?? DEFAULT_MAX_LOCAL_BRANCHES;\n }\n\n private get maxWorktrees(): number {\n return this.config.maxWorktrees ?? DEFAULT_MAX_WORKTREES;\n }\n\n // The recovery command for \"base this off fresh main\", in the flavour of whatever was blocked.\n private freshMainCommand(name: string): string {\n return this.worktreeAdd\n ? `git fetch origin main && git worktree add ../${name.replace(/\\//g, '-')} -b ${name} origin/main`\n : `git fetch origin main && git checkout -b ${name} origin/main`;\n }\n\n // Mode-aware fix hints. Branches off main follow branchFormat — never the sub-branch\n // convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'\n // hard-blocks it and points instead at the turnOffRuleUntilEpoch escape hatch.\n get fixHint(): FixHint {\n if (this.worktreeCapCache) return this.worktreeCapFixHint(this.worktreeCapCache);\n if (this.capCache) return this.capFixHint(this.capCache);\n\n const create = this.worktreeAdd\n ? 'Create it off fresh main: git fetch origin main && git worktree add ../<dir> -b <name> origin/main'\n : 'Create it off fresh main from anywhere (incl. a worktree): git fetch origin main && git checkout -b <name> origin/main';\n\n const options = [\n new Option(create, true),\n new Option(`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`),\n ];\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n options.push(new Option(\n 'Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +\n \"branch-creation-guard.turnOffRuleUntilEpoch to a future epoch in webpieces.config.json\",\n ));\n } else {\n options.push(new Option(\n `If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`,\n ));\n }\n return new FixHint(\n 'Cannot create this branch (main is stale, or branching off a non-main branch).',\n 'Create your branch from an up-to-date main. Pick one:',\n options,\n );\n }\n\n /**\n * Strip the parts of a shell command that are DATA rather than executable commands, so the guard\n * stops reading prose as instructions.\n *\n * This guard regex-scans the raw command string and has no notion of quoting, so\n * `git commit -m \"... git checkout -b foo ...\"` — or any heredoc commit message that mentions a\n * branch command — was parsed as an actual branch creation and blocked. That bit three separate\n * times while building the branch cap, including on the cap's own commit. It matters far more now\n * that the cap check runs BEFORE the origin/main allow: at the cap, a merely-MENTIONED branch\n * command would block your commit.\n *\n * A quoted span whose content has no whitespace is kept verbatim (it is a single token — the name\n * in `git checkout -b \"dean/foo\"`), so quoting a branch name cannot smuggle a creation past the\n * guard. Anything with whitespace inside quotes is prose, and collapses to a space.\n */\n check(ctx: BashContext): readonly Violation[] {\n this.capCache = null;\n this.worktreeCapCache = null;\n // Match against the command with heredoc bodies and prose-in-quotes removed (BashContext\n // computes it for every guard now — this rule's private copy was the original).\n const command = ctx.commandCode;\n const requestedName = extractBranchName(command);\n this.worktreeAdd = WORKTREE_ADD.test(command);\n\n // A worktree add with no -b creates no branch, but it DOES spend the worktree budget, so it must\n // survive this early-out and reach the worktree cap below.\n if (!requestedName && !this.worktreeAdd) return [];\n\n // Restoring a reaped branch at its logged SHA is undo, not creation — always allowed, and\n // checked before the caps so a full branch list can never trap you on the recovery path.\n if (!this.worktreeAdd && RESTORE_AT_SHA.test(command)) return [];\n\n const reserved = this.checkReservedSuffix(ctx, requestedName);\n if (reserved) return [reserved];\n\n const capViolation = this.checkCaps(ctx, requestedName !== null);\n if (capViolation) return [capViolation];\n\n // `git worktree add` of an EXISTING branch (or --detach) creates no branch, so the naming and\n // fresh-main rules below do not apply — but one thing still does: the branch may be DEAD.\n if (!requestedName) return this.checkWorktreeOntoDeadBranch(ctx, command);\n\n // Explicitly basing off origin/main is always allowed — it creates the branch from fresh main\n // regardless of the current branch, and is the ONLY way that also works inside a linked worktree\n // (where `git checkout main` fatals). Reserved-name check above still applies.\n if (ORIGIN_MAIN_BASE.test(command)) return [];\n if (this.worktreeAdd && WORKTREE_ORIGIN_MAIN_BASE.test(command)) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n // Not on main: creating this branch would stack it on a feature branch (a sub-branch).\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Create the branch OFF origin/main instead of ` +\n `stacking it on this branch: ${this.freshMainCommand(requestedName)} ` +\n `(works here and inside a worktree). ${this.branchFormat}. ` +\n `You can temporarily turn this off if you truly need a sub-branch by setting ` +\n `branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) in webpieces.config.json.`,\n )];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from fresh main: ` +\n `${this.freshMainCommand(requestedName)}. ${this.branchFormat}. ` +\n `If you truly need a stacked sub-branch (requires human approval), name it per ` +\n `branch-creation-guard.subBranchNaming ('${this.subBranchNaming}').`,\n )];\n }\n\n /**\n * `git worktree add ../dir <existing-branch>` onto a branch whose PR is ALREADY MERGED.\n *\n * The count caps never catch this: the command creates no branch, and if you are under the\n * worktree cap it sails straight through — materialising a fresh directory full of PRE-MERGE\n * code that the AI will then read, plan from and edit. read-stale-guard blocks the reads and\n * feature-branch-guard blocks the edits once you are in there, but that is a turn wasted per\n * tool call. Refuse at the moment of creation instead, using the SAME merged-PR proof the caps\n * already have precomputed on disk.\n *\n * Fails OPEN exactly like both caps: no cache (fresh clone, no `gh`, refresher hasn't run) or an\n * unparseable command → no opinion.\n */\n private checkWorktreeOntoDeadBranch(ctx: BashContext, command: string): readonly Violation[] {\n if (!this.worktreeAdd) return [];\n\n const match = WORKTREE_ADD_EXISTING.exec(command);\n if (!match) return [];\n const branch = match[1];\n // `origin/main` (and any remote-tracking ref) is the RECOMMENDED base, never a dead branch.\n if (branch.startsWith('origin/')) return [];\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return [];\n\n const dead = cache.deletable.find((entry: DeletableBranch): boolean => entry.branch === branch);\n if (!dead) return [];\n\n const dir = branch.replace(/\\//g, '-');\n return [new V(\n 1,\n truncate(ctx.command),\n `Branch '${branch}' is dead — ${dead.reason}. A worktree on it would be a directory full of ` +\n `PRE-MERGE code: everything you read there is stale relative to origin/main (read-stale-guard ` +\n `blocks those reads) and every edit is blocked by feature-branch-guard. Base the new worktree ` +\n `on fresh main instead: git fetch origin main && git worktree add ../${dir} -b <new-branch> origin/main`,\n )];\n }\n\n // The reserved `…wpN` generation suffix — see RESERVED_GENERATION_SUFFIX for why it stays blocked\n // even though the tooling no longer produces it.\n private checkReservedSuffix(ctx: BashContext, requestedName: string | null): Violation | null {\n if (!requestedName || !RESERVED_GENERATION_SUFFIX.test(requestedName)) return null;\n return new V(\n 1,\n truncate(ctx.command),\n `Branch name '${requestedName}' ends in 'wp<number>', which is reserved for the ` +\n `squash-merge tool's generation marker (base → basewp2 → basewp3). ` +\n `Rename it to a plain feature branch. ${this.branchFormat}.`,\n );\n }\n\n /**\n * Both budgets, in the order that produces the most useful complaint.\n *\n * Called BEFORE the origin/main allow in check() — `... -b <name> origin/main` is the normal,\n * always-permitted path, so a cap checked after it would never once fire.\n *\n * Worktree cap first: a `git worktree add -b` spends BOTH budgets, and when both are full the\n * worktree is the thing the command was actually trying to make, so it is the thing to talk about.\n */\n private checkCaps(ctx: BashContext, createsBranch: boolean): Violation | null {\n if (this.worktreeAdd) {\n const worktreeViolation = this.checkWorktreeCap(ctx);\n if (worktreeViolation) return worktreeViolation;\n }\n if (createsBranch) return this.checkBranchCap(ctx);\n return null;\n }\n\n /**\n * The cap. Blocks branch #N+1 until already-merged branches are reaped, which is the ONLY thing\n * keeping the local branch list bounded.\n *\n * Fails OPEN when the cache is absent (fresh clone, `gh` unavailable, refresher hasn't run yet):\n * never block on data we don't have. The detached refresher regenerates it within one hook call,\n * so the cap starts enforcing on its own.\n */\n private checkBranchCap(ctx: BashContext): Violation | null {\n // PARKED branches only — a branch checked out in a worktree is the worktree cap's problem, and\n // counting it twice would let five worktrees exhaust the branch budget on their own.\n const held = this.worktrees.heldBranches(ctx.workspaceRoot);\n const parked = this.mergedBranches.localBranches(ctx.workspaceRoot)\n .filter((branch: string): boolean => !held.has(branch));\n const count = parked.length;\n const cap = this.effectiveBranchCap(ctx);\n if (count < cap) return null;\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return null;\n\n this.capCache = cache;\n const reapable = cache.deletable.length;\n const detail = reapable > 0\n ? `${String(reapable)} of them are dead (merged, or holding no commits) and can be deleted right now.`\n : 'None of them are dead, so none can be auto-reaped — see the options below.';\n\n return new V(\n 1,\n truncate(ctx.command),\n `You have ${String(count)} parked local branches (not counting any checked out in a worktree); ` +\n `the cap (branch-creation-guard.maxLocalBranches) is ${String(this.maxLocalBranches)}. ` +\n `${detail} Clean up before creating another.`,\n );\n }\n\n /**\n * The branch cap, yielding by ONE when the agent is standing on an already-merged branch.\n *\n * Two individually-correct guards were composing into a trap: merged-branch-bash-guard blocks\n * almost all Bash until you get off a merged branch, and the ONLY way off it that guard advertises\n * is creating a fresh branch — which this cap then refused. Every printed exit led to editing\n * webpieces.config.json, and that is what an unsupervised agent did.\n *\n * One over cap, and only while a merged branch is what is pushing you: the branch about to be\n * created replaces a branch that is already dead, so the steady-state count does not grow. The cap\n * still fires on the NEXT creation, so this defers the cleanup by exactly one branch, never skips it.\n *\n * Fails toward the strict cap: no cache, a cache for another branch, or a clean branch → no yield.\n */\n private effectiveBranchCap(ctx: BashContext): number {\n const status = readMainSyncStatus(ctx.workspaceRoot);\n if (status === null || !status.branchAlreadyMerged) return this.maxLocalBranches;\n const current = this.currentBranchOrNull(ctx.workspaceRoot);\n if (current === null || status.branch !== current) return this.maxLocalBranches;\n return this.maxLocalBranches + 1;\n }\n\n private currentBranchOrNull(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workspaceRoot, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * The worktree cap — the second budget. Same gate, same fail-open rule as the branch cap: a\n * worktree list we cannot classify (no cache on disk) blocks nothing.\n *\n * Counts LINKED worktrees only. The primary clone is not a thing anyone can remove, so charging the\n * budget for it would just silently cost you one worktree.\n */\n private checkWorktreeCap(ctx: BashContext): Violation | null {\n const count = this.worktrees.linkedWorktrees(ctx.workspaceRoot).length;\n if (count < this.maxWorktrees) return null;\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return null;\n\n this.worktreeCapCache = cache;\n const reapable = cache.worktrees.filter((tree: DeletableWorktree): boolean => tree.deletable).length;\n const detail = reapable > 0\n ? `${String(reapable)} of them are dead (merged branch, no commits, or a missing directory) ` +\n 'and can be removed right now.'\n : 'None of them are dead, so none can be auto-reaped — see the options below.';\n\n return new V(\n 1,\n truncate(ctx.command),\n `You have ${String(count)} linked worktrees; the cap (branch-creation-guard.maxWorktrees) ` +\n `is ${String(this.maxWorktrees)}. ${detail} Clean up before creating another.`,\n );\n }\n\n /**\n * The reap instructions. `deletable` is PRECOMPUTED in the cache, and every entry earned its place\n * by one of exactly two proofs: a MERGED PR (the work is in main), or zero commits of its own\n * (there is no work). Deleting the list cannot lose anything — so just run the command.\n *\n * The command is `pnpm wp-cleanup`, NOT the `git branch -D a b c` this used to emit. Two reasons,\n * both learned the hard way: agents read a bare `-D` as destructive and stop to ask (so nothing\n * was ever cleaned, and this cap kept firing), and the multi-name form aborts wholesale on the\n * first branch git refuses, stranding every branch after it in the list. wp-cleanup recomputes\n * the verdicts, deletes one branch per command, and logs each pre-delete SHA.\n *\n * The wording must not overstate the safety: the list is NOT uniformly \"merged PR\" branches, and\n * a message that tells an agent to delete has to be exactly true about why that's safe.\n */\n private capFixHint(cache: MergedBranchesCache): FixHint {\n const options: Option[] = [];\n // Nothing auto-reapable → the ASK is the preferred move, and it comes FIRST. This is the whole\n // point of the option: with an empty `deletable` list the only advice left used to be \"raise\n // maxLocalBranches\" / \"set turnOffRuleUntilEpoch\", and an agent with no human in the loop\n // edited webpieces.config.json to escape — loosening the very rule that was working correctly.\n const askFirst = cache.deletable.length === 0;\n\n if (cache.deletable.length > 0) {\n const names = cache.deletable.map((entry: DeletableBranch): string => entry.branch);\n options.push(new Option(\n `Run: pnpm wp-cleanup — it deletes these ${String(names.length)} dead branches. Each is either ` +\n `backed by a MERGED PR or has no commits of its own, so no work can be lost, and every delete ` +\n `is logged with a recover-by-SHA command (see merged-branches.json for the per-branch reason): ` +\n names.join(' '),\n true,\n ));\n }\n\n const ask = this.askHumanOption(cache, askFirst);\n if (ask) options.push(ask);\n\n options.push(new Option(\n 'ONLY IF A HUMAN SAYS SO: raise branch-creation-guard.maxLocalBranches in ' +\n 'webpieces.config.json. Editing this config to get past a guard is not a fix you may make on ' +\n 'your own — ask first (use the option above).',\n ));\n options.push(new Option(\n 'ONLY IF A HUMAN SAYS SO: set branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) ' +\n 'in webpieces.config.json to bypass this once. Same rule — ask, do not self-approve.',\n ));\n\n const kept = cache.keep.length > 0\n ? ` ${String(cache.keep.length)} unmerged branch(es) with real commits were deliberately SPARED — ` +\n 'do not delete those; a human decides.'\n : '';\n\n return new FixHint(\n 'Too many local branches — reap the dead ones before creating another.',\n 'Full detail (deletable + spared, with per-branch reasons) is in .webpieces/merged-branches.json, ' +\n `refreshed ${cache.timestamp || 'never'}.${kept} Pick one:`,\n options,\n );\n }\n\n /**\n * The remedy that actually deletes something without loosening anything: SHOW the spared branches\n * and ASK the human which may go.\n *\n * `keep` is the list the tooling refuses to touch on its own — no merged PR, so no proof the work\n * is safe. That is exactly the list a human can adjudicate in five seconds and the tooling never\n * can, and it was never printed: the cap said \"N branches were SPARED, do not delete those\" and\n * then offered only config edits. So the agent edited the config.\n *\n * Every column is read straight off `.webpieces/merged-branches.json` (written by the detached\n * refresher) — nothing is recomputed on this blocking path. The SHA is there so the human can see\n * the delete is reversible; the commit count is there so \"0 commits\" branches are obvious yeses.\n */\n private askHumanOption(cache: MergedBranchesCache, preferred: boolean): Option | null {\n if (cache.keep.length === 0) return null;\n\n const rows = cache.keep.map((entry: DeletableBranch): string => {\n const sha = entry.sha !== '' ? entry.sha : '???????';\n const pr = entry.pr > 0\n ? `PR #${String(entry.pr)} ${entry.prState || 'MERGED'}`\n : (entry.prState !== '' ? `PR ${entry.prState}` : 'no PR');\n const commits = entry.commits >= 0 ? `${String(entry.commits)} commit(s) of its own` : 'commit count unknown';\n return ` ${entry.branch} [${sha}] ${pr} ${commits} — ${entry.reason}`;\n });\n\n return new Option(\n `ASK THE HUMAN which of these ${String(cache.keep.length)} branches may be deleted. They are ` +\n 'not provably dead, so the tooling will not reap them — but a human can decide in seconds, ' +\n 'and deleting one is the correct fix for \"too many branches\". Paste this list and ask:\\n' +\n rows.join('\\n') + '\\n' +\n 'Then delete ONLY the ones approved: git branch -D <approved-branch>\\n' +\n '(each is recoverable — `git branch <name> <sha>` restores it at the SHA shown above).\\n' +\n 'Do NOT delete any of these without an explicit yes, and do NOT edit webpieces.config.json instead.',\n preferred,\n );\n }\n\n /**\n * The worktree reap instructions.\n *\n * The command ORDER is load-bearing and is the whole reason this is generated rather than described:\n * 1. `git worktree prune` — clears the admin data of worktrees whose directory is already gone.\n * `git worktree remove` FAILS on those, so it cannot be the first step.\n * 2. `git worktree remove <path>` — one path per invocation; git takes no path list here.\n * 3. `git branch -D <names>` — only now. git flatly refuses to delete a branch that is still\n * checked out in a worktree, so a branch delete placed before the removal fails, and because\n * it is one multi-name command it takes every other branch in the list down with it.\n */\n private worktreeCapFixHint(cache: MergedBranchesCache): FixHint {\n const options: Option[] = [];\n const dead = cache.worktrees.filter((tree: DeletableWorktree): boolean => tree.deletable);\n\n if (dead.length > 0) {\n const steps = ['git worktree prune'];\n for (const tree of dead) {\n // A prunable worktree has no directory left to remove — step 1 already handled it.\n if (tree.path !== '') steps.push(`git worktree remove ${tree.path}`);\n }\n const branches = dead\n .map((tree: DeletableWorktree): string => tree.branch)\n .filter((branch: string): boolean => branch !== '');\n if (branches.length > 0) steps.push(`git branch -D ${branches.join(' ')}`);\n\n options.push(new Option(\n `Remove these ${String(dead.length)} dead worktrees — each holds a branch backed by a MERGED ` +\n 'PR, a branch with no commits of its own, or a directory that is already gone, so no work ' +\n 'can be lost (see merged-branches.json for the per-worktree reason). Run it in this order ' +\n `(prune first, branches last — git refuses to delete a branch a worktree still holds): ${steps.join(' && ')}`,\n true,\n ));\n }\n\n options.push(new Option(\n 'If you genuinely need more worktrees in flight, raise branch-creation-guard.maxWorktrees ' +\n 'in webpieces.config.json.',\n ));\n options.push(new Option(\n 'To bypass this once, set branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) ' +\n 'in webpieces.config.json.',\n ));\n\n const spared = cache.worktrees.length - dead.length;\n const kept = spared > 0\n ? ` ${String(spared)} worktree(s) were deliberately SPARED (locked, holding unmerged work, or ` +\n 'the one you are standing in) — do not remove those; a human decides.'\n : '';\n\n return new FixHint(\n 'Too many worktrees — reap the dead ones before creating another.',\n 'Full detail (deletable + spared, with per-worktree reasons) is in .webpieces/merged-branches.json, ' +\n `refreshed ${cache.timestamp || 'never'}.${kept} Pick one:`,\n options,\n );\n }\n}\n"]}
1
+ {"version":3,"file":"branch-creation-guard.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/ai-hook-rules/src/core/rules/branch-creation-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from 'child_process';\n\nimport {\n BranchCreationGuardConfig,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n WorktreeService,\n readMainSyncStatus,\n} from '@webpieces/rules-config';\n\nimport type { BashContext, Violation } from '../types';\nimport { Violation as V } from '../types';\nimport { BashRuleBase } from '../rule-base';\nimport { FixHint, Option } from '../fix-hint';\nimport { toError } from '../to-error';\n\n// Defaults used when the rule has no explicit value in webpieces.config.json.\n// branchFormat is a human sentence telling the AI how to name a branch created off main; it is\n// intentionally NOT the sub-branch convention (sub-branches are a separate, human-approved path).\nconst DEFAULT_BRANCH_FORMAT =\n 'Name it {whoami}/<short-feature-description> — lowercase, no version numbers, no sub/ prefix (e.g. dean/upgrade-webpieces)';\nconst DEFAULT_SUB_BRANCH_NAMING = 'feature/<ticket>/<short-description>';\n\n// Hard cap on local feature branches. Enforced at CREATION because that is the one moment cleanup is\n// both cheap and obviously worth it — reaping happens over time, never \"ASAP\".\n//\n// The branch cap counts PARKED branches only — branches not checked out in any worktree. Worktree-held\n// branches are counted by the worktree cap instead. Two budgets, because they are not substitutes: if\n// held branches also spent the branch budget, five worktrees would leave room for zero branches and no\n// branch could ever be created again.\nconst DEFAULT_MAX_LOCAL_BRANCHES = 5;\nconst DEFAULT_MAX_WORKTREES = 5;\n\n// A plausible git ref name. Deliberately NOT `[^\\s-]` — that class matches shell metacharacters, so\n// `git branch | wc -l` (a read-only LISTING, piped) was parsed as \"create a branch named `|`\" and\n// blocked. Cleanup work necessarily reads and deletes branches, so a listing must never trip this.\nconst REF_NAME = String.raw`[A-Za-z0-9][A-Za-z0-9_./-]*`;\n\nconst BRANCH_PATTERNS: RegExp[] = [\n new RegExp(String.raw`git\\s+checkout\\s+-[bB]\\s+(${REF_NAME})`),\n new RegExp(String.raw`git\\s+switch\\s+-[cC]\\s+(${REF_NAME})`),\n new RegExp(String.raw`git\\s+branch\\s+(?!-)(${REF_NAME})`),\n // `git worktree add ../dir -b <name> origin/main` — the form docs/git-workflow.md recommends for\n // starting a feature. It creates a branch just as surely as `checkout -b` does, and until this\n // pattern existed it walked straight past the cap, the reserved-suffix check and the sub-branch\n // check. `(?:\\S+\\s+)*?` absorbs the path and any other flags, so the -b may precede or follow them.\n new RegExp(String.raw`git\\s+worktree\\s+add\\s+(?:\\S+\\s+)*?-[bB]\\s+(${REF_NAME})`),\n];\n\n// ANY worktree creation, with or without -b. The no-`-b` forms (`git worktree add ../dir existing`,\n// `--detach`) create no branch but DO create a worktree, so they spend the worktree budget and must\n// still hit the cap.\nconst WORKTREE_ADD = /git\\s+worktree\\s+add\\b/;\n\n// `git worktree add <path> <existing-branch>` — the checkout-an-existing-branch form. Captures the\n// LAST bare (non-flag) argument, which is the committish; the first bare argument is the path.\n// Flags that take a value (`--reason <s>`, `-b <name>`) are excluded by the caller, which only uses\n// this on commands with no `-b`/`-B` at all.\nconst WORKTREE_ADD_EXISTING = new RegExp(\n String.raw`git\\s+worktree\\s+add\\s+(?:-{1,2}[A-Za-z-]+\\s+)*\\S+\\s+(${REF_NAME})`,\n);\n\n// `git branch <name> <sha>` — RESTORING a branch at an explicit commit, which is exactly the\n// `recover=` command wp-cleanup writes to branch-mutations.log for every branch it reaps.\n//\n// This must be allowed UNCONDITIONALLY, ahead of even the caps. The entire argument for letting the\n// tooling delete branches unattended is that any delete is one logged command away from being undone\n// — so a guard that blocks that command turns a real guarantee into a decorative one. (It did: the\n// generic `git branch <name>` creation pattern matched the restore and refused it, demanding the\n// branch be recreated off origin/main, which is precisely the content the restore is meant to bring\n// back.) A restore also cannot grow the branch list beyond what already existed.\nconst RESTORE_AT_SHA = new RegExp(String.raw`git\\s+branch\\s+${REF_NAME}\\s+[0-9a-f]{7,40}(?:\\W|$)`);\n\n// A trailing `wp<number>` was the old squash-merge generation marker (base → basewp2 → basewp3).\n// The tooling NO LONGER produces it — a sync now lands back on the same feature name — but the suffix\n// stays RESERVED so a human branch can't collide with a leftover `…wpN` still floating in a consumer\n// repo mid-transition. Block it at creation time and steer the name back to the plain feature form.\nconst RESERVED_GENERATION_SUFFIX = /wp\\d+$/;\n\n// A branch-creation command that explicitly bases off origin/main (e.g. `git checkout -b feat\n// origin/main`). This is exactly the fresh-main base the guard wants, and it works from ANY current\n// branch or linked worktree — main need not (and in a worktree cannot) be checked out here. Allowed\n// unconditionally so the recovery messages can safely tell you to run it from a worktree.\n//\n// The trailing check is `\\W|$`, not `\\s|$`: the ALLOW pattern must not be stricter about delimiters\n// than the BLOCK pattern above, or a `git checkout -b x origin/main` that ends at a quote or backtick\n// is seen as a branch creation but NOT as an origin/main one — recognised, then wrongly blocked.\nconst ORIGIN_MAIN_BASE = /git\\s+(?:checkout\\s+-[bB]|switch\\s+-[cC])\\s+\\S+\\s+origin\\/main(?:\\W|$)/;\n\n// The worktree arm of the same allow: `git worktree add ../dir -b <name> origin/main`. Same fresh-main\n// base, same reasoning — and in a worktree it is the ONLY workable base, since `git checkout main`\n// fatals there. Kept separate from ORIGIN_MAIN_BASE because the argument shape differs (a path sits\n// between the subcommand and the flags).\nconst WORKTREE_ORIGIN_MAIN_BASE = /git\\s+worktree\\s+add\\s+(?:\\S+\\s+)*origin\\/main(?:\\W|$)/;\n\nfunction extractBranchName(command: string): string | null {\n for (const pattern of BRANCH_PATTERNS) {\n const m = pattern.exec(command);\n if (m) return m[1];\n }\n return null;\n}\n\nfunction truncate(s: string): string {\n const MAX = 120;\n return s.length <= MAX ? s : s.slice(0, MAX) + '…';\n}\n\nfunction checkMainIsUpToDate(ctx: BashContext, requestedName: string): readonly Violation[] {\n // `--no-write-fetch-head` for the same reason as the background refresher (see\n // MainSyncStatusService.fetchOriginMain): this refresh can run while the agent is mid\n // `git fetch`/`git pull`, and two overlapping writers of the unlocked `.git/FETCH_HEAD` can leave\n // a duplicate for-merge line that makes the agent's `git pull` fatal with \"multiple branches\". We\n // read `origin/main` below, never FETCH_HEAD. On a git too old for the flag, retry the plain form.\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n execSync('git fetch --no-write-fetch-head origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n } catch (err: unknown) {\n const error = toError(err);\n const text = error.message.toLowerCase();\n const tooOld = text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n if (!tooOld) throw error;\n execSync('git fetch origin main --quiet', { cwd: ctx.workspaceRoot, encoding: 'utf8' });\n }\n const countStr = execSync('git rev-list HEAD..origin/main --count', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n const count = parseInt(countStr, 10);\n if (count > 0) {\n return [new V(\n 1,\n truncate(ctx.command),\n `Local main is ${count} commit(s) behind origin/main. Run 'git pull origin main' first, then retry creating branch '${requestedName}'.`,\n )];\n }\n return [];\n}\n\nexport class BranchCreationGuardRule extends BashRuleBase<BranchCreationGuardConfig> {\n constructor(config: BranchCreationGuardConfig) { super(config, 'branch-creation-guard'); }\n\n readonly description =\n 'Block new-branch and new-worktree creation when main is stale, when branching off a non-main ' +\n 'branch, or when the branch/worktree count is at its cap (forces cleanup of dead ones).';\n override readonly defaultOptions = {\n subBranchNaming: DEFAULT_SUB_BRANCH_NAMING,\n branchFormat: DEFAULT_BRANCH_FORMAT,\n maxLocalBranches: DEFAULT_MAX_LOCAL_BRANCHES,\n maxWorktrees: DEFAULT_MAX_WORKTREES,\n };\n\n private readonly worktrees = new WorktreeService();\n private readonly mergedBranches = new MergedBranchesService(this.worktrees);\n\n // Set by check() when (and only when) a cap is what blocked, so fixHint can render the reap\n // instructions instead of the branch-naming ones. Same instance-field handoff pr-merge-guard uses.\n // Two fields, because the two caps reap different things and their hints share no wording.\n private capCache: MergedBranchesCache | null = null;\n private worktreeCapCache: MergedBranchesCache | null = null;\n\n // True when the blocked command was a `git worktree add`, so the recovery command we hand back is\n // a worktree command and not a `git checkout -b` the user cannot use here.\n private worktreeAdd = false;\n\n private get branchFormat(): string {\n return this.config.branchFormat ?? DEFAULT_BRANCH_FORMAT;\n }\n\n private get subBranchNaming(): string {\n return this.config.subBranchNaming ?? DEFAULT_SUB_BRANCH_NAMING;\n }\n\n private get maxLocalBranches(): number {\n return this.config.maxLocalBranches ?? DEFAULT_MAX_LOCAL_BRANCHES;\n }\n\n private get maxWorktrees(): number {\n return this.config.maxWorktrees ?? DEFAULT_MAX_WORKTREES;\n }\n\n // The recovery command for \"base this off fresh main\", in the flavour of whatever was blocked.\n private freshMainCommand(name: string): string {\n return this.worktreeAdd\n ? `git fetch origin main && git worktree add ../${name.replace(/\\//g, '-')} -b ${name} origin/main`\n : `git fetch origin main && git checkout -b ${name} origin/main`;\n }\n\n // Mode-aware fix hints. Branches off main follow branchFormat — never the sub-branch\n // convention. The sub-branch affordance only appears under mode 'ON'; 'ON_NO_SUBBRANCHES'\n // hard-blocks it and points instead at the turnOffRuleUntilEpoch escape hatch.\n get fixHint(): FixHint {\n if (this.worktreeCapCache) return this.worktreeCapFixHint(this.worktreeCapCache);\n if (this.capCache) return this.capFixHint(this.capCache);\n\n const create = this.worktreeAdd\n ? 'Create it off fresh main: git fetch origin main && git worktree add ../<dir> -b <name> origin/main'\n : 'Create it off fresh main from anywhere (incl. a worktree): git fetch origin main && git checkout -b <name> origin/main';\n\n const options = [\n new Option(create, true),\n new Option(`Name a branch off main per branch-creation-guard.branchFormat: ${this.branchFormat}`),\n ];\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n options.push(new Option(\n 'Sub-branches (branching off another feature branch) are disabled. To temporarily allow one, set ' +\n \"branch-creation-guard.turnOffRuleUntilEpoch to a future epoch in webpieces.config.json\",\n ));\n } else {\n options.push(new Option(\n `If you truly need a stacked sub-branch (requires human approval), name it per branch-creation-guard.subBranchNaming: ${this.subBranchNaming}`,\n ));\n }\n return new FixHint(\n 'Cannot create this branch (main is stale, or branching off a non-main branch).',\n 'Create your branch from an up-to-date main. Pick one:',\n options,\n );\n }\n\n /**\n * Strip the parts of a shell command that are DATA rather than executable commands, so the guard\n * stops reading prose as instructions.\n *\n * This guard regex-scans the raw command string and has no notion of quoting, so\n * `git commit -m \"... git checkout -b foo ...\"` — or any heredoc commit message that mentions a\n * branch command — was parsed as an actual branch creation and blocked. That bit three separate\n * times while building the branch cap, including on the cap's own commit. It matters far more now\n * that the cap check runs BEFORE the origin/main allow: at the cap, a merely-MENTIONED branch\n * command would block your commit.\n *\n * A quoted span whose content has no whitespace is kept verbatim (it is a single token — the name\n * in `git checkout -b \"dean/foo\"`), so quoting a branch name cannot smuggle a creation past the\n * guard. Anything with whitespace inside quotes is prose, and collapses to a space.\n */\n check(ctx: BashContext): readonly Violation[] {\n this.capCache = null;\n this.worktreeCapCache = null;\n // Match against the command with heredoc bodies and prose-in-quotes removed (BashContext\n // computes it for every guard now — this rule's private copy was the original).\n const command = ctx.commandCode;\n const requestedName = extractBranchName(command);\n this.worktreeAdd = WORKTREE_ADD.test(command);\n\n // A worktree add with no -b creates no branch, but it DOES spend the worktree budget, so it must\n // survive this early-out and reach the worktree cap below.\n if (!requestedName && !this.worktreeAdd) return [];\n\n // Restoring a reaped branch at its logged SHA is undo, not creation — always allowed, and\n // checked before the caps so a full branch list can never trap you on the recovery path.\n if (!this.worktreeAdd && RESTORE_AT_SHA.test(command)) return [];\n\n const reserved = this.checkReservedSuffix(ctx, requestedName);\n if (reserved) return [reserved];\n\n const capViolation = this.checkCaps(ctx, requestedName !== null);\n if (capViolation) return [capViolation];\n\n // `git worktree add` of an EXISTING branch (or --detach) creates no branch, so the naming and\n // fresh-main rules below do not apply — but one thing still does: the branch may be DEAD.\n if (!requestedName) return this.checkWorktreeOntoDeadBranch(ctx, command);\n\n // Explicitly basing off origin/main is always allowed — it creates the branch from fresh main\n // regardless of the current branch, and is the ONLY way that also works inside a linked worktree\n // (where `git checkout main` fatals). Reserved-name check above still applies.\n if (ORIGIN_MAIN_BASE.test(command)) return [];\n if (this.worktreeAdd && WORKTREE_ORIGIN_MAIN_BASE.test(command)) return [];\n\n const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: ctx.workspaceRoot,\n encoding: 'utf8',\n }).trim();\n\n if (currentBranch === 'main') {\n return checkMainIsUpToDate(ctx, requestedName);\n }\n\n // Not on main: creating this branch would stack it on a feature branch (a sub-branch).\n if (this.config.mode === 'ON_NO_SUBBRANCHES') {\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Create the branch OFF origin/main instead of ` +\n `stacking it on this branch: ${this.freshMainCommand(requestedName)} ` +\n `(works here and inside a worktree). ${this.branchFormat}. ` +\n `You can temporarily turn this off if you truly need a sub-branch by setting ` +\n `branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) in webpieces.config.json.`,\n )];\n }\n\n return [new V(\n 1,\n truncate(ctx.command),\n `You are on '${currentBranch}', not main. Branches must be created from fresh main: ` +\n `${this.freshMainCommand(requestedName)}. ${this.branchFormat}. ` +\n `If you truly need a stacked sub-branch (requires human approval), name it per ` +\n `branch-creation-guard.subBranchNaming ('${this.subBranchNaming}').`,\n )];\n }\n\n /**\n * `git worktree add ../dir <existing-branch>` onto a branch whose PR is ALREADY MERGED.\n *\n * The count caps never catch this: the command creates no branch, and if you are under the\n * worktree cap it sails straight through — materialising a fresh directory full of PRE-MERGE\n * code that the AI will then read, plan from and edit. read-stale-guard blocks the reads and\n * feature-branch-guard blocks the edits once you are in there, but that is a turn wasted per\n * tool call. Refuse at the moment of creation instead, using the SAME merged-PR proof the caps\n * already have precomputed on disk.\n *\n * Fails OPEN exactly like both caps: no cache (fresh clone, no `gh`, refresher hasn't run) or an\n * unparseable command → no opinion.\n */\n private checkWorktreeOntoDeadBranch(ctx: BashContext, command: string): readonly Violation[] {\n if (!this.worktreeAdd) return [];\n\n const match = WORKTREE_ADD_EXISTING.exec(command);\n if (!match) return [];\n const branch = match[1];\n // `origin/main` (and any remote-tracking ref) is the RECOMMENDED base, never a dead branch.\n if (branch.startsWith('origin/')) return [];\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return [];\n\n const dead = cache.deletable.find((entry: DeletableBranch): boolean => entry.branch === branch);\n if (!dead) return [];\n\n const dir = branch.replace(/\\//g, '-');\n return [new V(\n 1,\n truncate(ctx.command),\n `Branch '${branch}' is dead — ${dead.reason}. A worktree on it would be a directory full of ` +\n `PRE-MERGE code: everything you read there is stale relative to origin/main (read-stale-guard ` +\n `blocks those reads) and every edit is blocked by feature-branch-guard. Base the new worktree ` +\n `on fresh main instead: git fetch origin main && git worktree add ../${dir} -b <new-branch> origin/main`,\n )];\n }\n\n // The reserved `…wpN` generation suffix — see RESERVED_GENERATION_SUFFIX for why it stays blocked\n // even though the tooling no longer produces it.\n private checkReservedSuffix(ctx: BashContext, requestedName: string | null): Violation | null {\n if (!requestedName || !RESERVED_GENERATION_SUFFIX.test(requestedName)) return null;\n return new V(\n 1,\n truncate(ctx.command),\n `Branch name '${requestedName}' ends in 'wp<number>', which is reserved for the ` +\n `squash-merge tool's generation marker (base → basewp2 → basewp3). ` +\n `Rename it to a plain feature branch. ${this.branchFormat}.`,\n );\n }\n\n /**\n * Both budgets, in the order that produces the most useful complaint.\n *\n * Called BEFORE the origin/main allow in check() — `... -b <name> origin/main` is the normal,\n * always-permitted path, so a cap checked after it would never once fire.\n *\n * Worktree cap first: a `git worktree add -b` spends BOTH budgets, and when both are full the\n * worktree is the thing the command was actually trying to make, so it is the thing to talk about.\n */\n private checkCaps(ctx: BashContext, createsBranch: boolean): Violation | null {\n if (this.worktreeAdd) {\n const worktreeViolation = this.checkWorktreeCap(ctx);\n if (worktreeViolation) return worktreeViolation;\n }\n if (createsBranch) return this.checkBranchCap(ctx);\n return null;\n }\n\n /**\n * The cap. Blocks branch #N+1 until already-merged branches are reaped, which is the ONLY thing\n * keeping the local branch list bounded.\n *\n * Fails OPEN when the cache is absent (fresh clone, `gh` unavailable, refresher hasn't run yet):\n * never block on data we don't have. The detached refresher regenerates it within one hook call,\n * so the cap starts enforcing on its own.\n */\n private checkBranchCap(ctx: BashContext): Violation | null {\n // PARKED branches only — a branch checked out in a worktree is the worktree cap's problem, and\n // counting it twice would let five worktrees exhaust the branch budget on their own.\n const held = this.worktrees.heldBranches(ctx.workspaceRoot);\n const parked = this.mergedBranches.localBranches(ctx.workspaceRoot)\n .filter((branch: string): boolean => !held.has(branch));\n const count = parked.length;\n const cap = this.effectiveBranchCap(ctx);\n if (count < cap) return null;\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return null;\n\n this.capCache = cache;\n const reapable = cache.deletable.length;\n const detail = reapable > 0\n ? `${String(reapable)} of them are dead (merged, or holding no commits) and can be deleted right now.`\n : 'None of them are dead, so none can be auto-reaped — see the options below.';\n\n return new V(\n 1,\n truncate(ctx.command),\n `You have ${String(count)} parked local branches (not counting any checked out in a worktree); ` +\n `the cap (branch-creation-guard.maxLocalBranches) is ${String(this.maxLocalBranches)}. ` +\n `${detail} Clean up before creating another.`,\n );\n }\n\n /**\n * The branch cap, yielding by ONE when the agent is standing on an already-merged branch.\n *\n * Two individually-correct guards were composing into a trap: merged-branch-bash-guard blocks\n * almost all Bash until you get off a merged branch, and the ONLY way off it that guard advertises\n * is creating a fresh branch — which this cap then refused. Every printed exit led to editing\n * webpieces.config.json, and that is what an unsupervised agent did.\n *\n * One over cap, and only while a merged branch is what is pushing you: the branch about to be\n * created replaces a branch that is already dead, so the steady-state count does not grow. The cap\n * still fires on the NEXT creation, so this defers the cleanup by exactly one branch, never skips it.\n *\n * Fails toward the strict cap: no cache, a cache for another branch, or a clean branch → no yield.\n */\n private effectiveBranchCap(ctx: BashContext): number {\n const status = readMainSyncStatus(ctx.workspaceRoot);\n if (status === null || !status.branchAlreadyMerged) return this.maxLocalBranches;\n const current = this.currentBranchOrNull(ctx.workspaceRoot);\n if (current === null || status.branch !== current) return this.maxLocalBranches;\n return this.maxLocalBranches + 1;\n }\n\n private currentBranchOrNull(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return execSync('git rev-parse --abbrev-ref HEAD', {\n cwd: workspaceRoot, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * The worktree cap — the second budget. Same gate, same fail-open rule as the branch cap: a\n * worktree list we cannot classify (no cache on disk) blocks nothing.\n *\n * Counts LINKED worktrees only. The primary clone is not a thing anyone can remove, so charging the\n * budget for it would just silently cost you one worktree.\n */\n private checkWorktreeCap(ctx: BashContext): Violation | null {\n const count = this.worktrees.linkedWorktrees(ctx.workspaceRoot).length;\n if (count < this.maxWorktrees) return null;\n\n const cache = this.mergedBranches.readMergedBranches(ctx.workspaceRoot);\n if (!cache) return null;\n\n this.worktreeCapCache = cache;\n const reapable = cache.worktrees.filter((tree: DeletableWorktree): boolean => tree.deletable).length;\n const detail = reapable > 0\n ? `${String(reapable)} of them are dead (merged branch, no commits, or a missing directory) ` +\n 'and can be removed right now.'\n : 'None of them are dead, so none can be auto-reaped — see the options below.';\n\n return new V(\n 1,\n truncate(ctx.command),\n `You have ${String(count)} linked worktrees; the cap (branch-creation-guard.maxWorktrees) ` +\n `is ${String(this.maxWorktrees)}. ${detail} Clean up before creating another.`,\n );\n }\n\n /**\n * The reap instructions. `deletable` is PRECOMPUTED in the cache, and every entry earned its place\n * by one of exactly two proofs: a MERGED PR (the work is in main), or zero commits of its own\n * (there is no work). Deleting the list cannot lose anything — so just run the command.\n *\n * The command is `pnpm wp-cleanup`, NOT the `git branch -D a b c` this used to emit. Two reasons,\n * both learned the hard way: agents read a bare `-D` as destructive and stop to ask (so nothing\n * was ever cleaned, and this cap kept firing), and the multi-name form aborts wholesale on the\n * first branch git refuses, stranding every branch after it in the list. wp-cleanup recomputes\n * the verdicts, deletes one branch per command, and logs each pre-delete SHA.\n *\n * The wording must not overstate the safety: the list is NOT uniformly \"merged PR\" branches, and\n * a message that tells an agent to delete has to be exactly true about why that's safe.\n */\n private capFixHint(cache: MergedBranchesCache): FixHint {\n const options: Option[] = [];\n // Nothing auto-reapable → the ASK is the preferred move, and it comes FIRST. This is the whole\n // point of the option: with an empty `deletable` list the only advice left used to be \"raise\n // maxLocalBranches\" / \"set turnOffRuleUntilEpoch\", and an agent with no human in the loop\n // edited webpieces.config.json to escape — loosening the very rule that was working correctly.\n const askFirst = cache.deletable.length === 0;\n\n if (cache.deletable.length > 0) {\n const names = cache.deletable.map((entry: DeletableBranch): string => entry.branch);\n options.push(new Option(\n `Run: pnpm wp-cleanup — it deletes these ${String(names.length)} dead branches. Each is either ` +\n `backed by a MERGED PR or has no commits of its own, so no work can be lost, and every delete ` +\n `is logged with a recover-by-SHA command (see merged-branches.json for the per-branch reason): ` +\n names.join(' '),\n true,\n ));\n }\n\n const ask = this.askHumanOption(cache, askFirst);\n if (ask) options.push(ask);\n\n options.push(new Option(\n 'ONLY IF A HUMAN SAYS SO: raise branch-creation-guard.maxLocalBranches in ' +\n 'webpieces.config.json. Editing this config to get past a guard is not a fix you may make on ' +\n 'your own — ask first (use the option above).',\n ));\n options.push(new Option(\n 'ONLY IF A HUMAN SAYS SO: set branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) ' +\n 'in webpieces.config.json to bypass this once. Same rule — ask, do not self-approve.',\n ));\n\n const kept = cache.keep.length > 0\n ? ` ${String(cache.keep.length)} unmerged branch(es) with real commits were deliberately SPARED — ` +\n 'do not delete those; a human decides.'\n : '';\n\n return new FixHint(\n 'Too many local branches — reap the dead ones before creating another.',\n 'Full detail (deletable + spared, with per-branch reasons) is in .webpieces/merged-branches.json, ' +\n `refreshed ${cache.timestamp || 'never'}.${kept} Pick one:`,\n options,\n );\n }\n\n /**\n * The remedy that actually deletes something without loosening anything: SHOW the spared branches\n * and ASK the human which may go.\n *\n * `keep` is the list the tooling refuses to touch on its own — no merged PR, so no proof the work\n * is safe. That is exactly the list a human can adjudicate in five seconds and the tooling never\n * can, and it was never printed: the cap said \"N branches were SPARED, do not delete those\" and\n * then offered only config edits. So the agent edited the config.\n *\n * Every column is read straight off `.webpieces/merged-branches.json` (written by the detached\n * refresher) — nothing is recomputed on this blocking path. The SHA is there so the human can see\n * the delete is reversible; the commit count is there so \"0 commits\" branches are obvious yeses.\n */\n private askHumanOption(cache: MergedBranchesCache, preferred: boolean): Option | null {\n if (cache.keep.length === 0) return null;\n\n const rows = cache.keep.map((entry: DeletableBranch): string => {\n const sha = entry.sha !== '' ? entry.sha : '???????';\n const pr = entry.pr > 0\n ? `PR #${String(entry.pr)} ${entry.prState || 'MERGED'}`\n : (entry.prState !== '' ? `PR ${entry.prState}` : 'no PR');\n const commits = entry.commits >= 0 ? `${String(entry.commits)} commit(s) of its own` : 'commit count unknown';\n return ` ${entry.branch} [${sha}] ${pr} ${commits} — ${entry.reason}`;\n });\n\n return new Option(\n `ASK THE HUMAN which of these ${String(cache.keep.length)} branches may be deleted. They are ` +\n 'not provably dead, so the tooling will not reap them — but a human can decide in seconds, ' +\n 'and deleting one is the correct fix for \"too many branches\". Paste this list and ask:\\n' +\n rows.join('\\n') + '\\n' +\n 'Then delete ONLY the ones approved: git branch -D <approved-branch>\\n' +\n '(each is recoverable — `git branch <name> <sha>` restores it at the SHA shown above).\\n' +\n 'Do NOT delete any of these without an explicit yes, and do NOT edit webpieces.config.json instead.',\n preferred,\n );\n }\n\n /**\n * The worktree reap remedy.\n *\n * It is now ONE command — `pnpm wp-cleanup` — and that is the point of it. The hand-written\n * sequence this used to print (prune → remove each path → one multi-name `git branch -D`) was\n * correct git, and it was still never run: an AI agent reads a raw `worktree remove` / `-D` as\n * destructive, asks permission, and stops. A guard whose only actionable remedies are \"raise the\n * cap\" and \"set turnOffRuleUntilEpoch\" is a guard that teaches config edits, which is the exact\n * failure the cap exists to prevent. wp-cleanup archives each branch as a tag, removes the\n * directory, then deletes the branch — in that order, because git refuses to delete a branch a\n * worktree still holds — and it will not touch the primary clone, the tree you are standing in, or\n * anything with uncommitted work.\n *\n * The explicit git sequence is still printed BELOW the command, as reference rather than as the\n * instruction: when wp-cleanup declines one of these (typically untracked files), the human needs\n * to see which directory and what git would have run.\n */\n private worktreeCapFixHint(cache: MergedBranchesCache): FixHint {\n const options: Option[] = [];\n const dead = cache.worktrees.filter((tree: DeletableWorktree): boolean => tree.deletable);\n\n if (dead.length > 0) {\n const steps = ['git worktree prune'];\n for (const tree of dead) {\n // A prunable worktree has no directory left to remove — step 1 already handled it.\n if (tree.path !== '') steps.push(`git worktree remove ${tree.path}`);\n }\n const branches = dead\n .map((tree: DeletableWorktree): string => tree.branch)\n .filter((branch: string): boolean => branch !== '');\n if (branches.length > 0) steps.push(`git branch -D ${branches.join(' ')}`);\n\n options.push(new Option(\n `Run: pnpm wp-cleanup — it REMOVES these ${String(dead.length)} dead worktrees for you. Each ` +\n 'holds a branch backed by a MERGED PR, a branch with no commits of its own, or a directory ' +\n 'that is already gone, so no work can be lost (see merged-branches.json for the per-worktree ' +\n 'reason). It archives every branch as an `archive/<date>/<branch>` tag BEFORE removing ' +\n 'anything, logs each removal with a `recover=` command, and refuses to touch the primary ' +\n 'clone, the worktree you are standing in, or any worktree holding uncommitted work. ' +\n `Equivalent by hand, in this order (prune first, branches last — git refuses to delete a ` +\n `branch a worktree still holds): ${steps.join(' && ')}`,\n true,\n ));\n } else {\n // Nothing is PROVABLY dead, but wp-cleanup also prompts about the probably-dead ones, so it\n // is still the remedy that can delete something — and it is still better advice than a knob.\n options.push(new Option(\n 'Run: pnpm wp-cleanup — none of these worktrees is provably dead, but it lists the ' +\n 'probably-dead ones (closed-unmerged PR, content already in main, never proposed) with ' +\n 'their branch and reason and asks which to remove. Answer that prompt instead of raising ' +\n 'the cap.',\n true,\n ));\n }\n\n options.push(new Option(\n 'If you genuinely need more worktrees in flight, raise branch-creation-guard.maxWorktrees ' +\n 'in webpieces.config.json.',\n ));\n options.push(new Option(\n 'To bypass this once, set branch-creation-guard.turnOffRuleUntilEpoch (a future epoch) ' +\n 'in webpieces.config.json.',\n ));\n\n const spared = cache.worktrees.length - dead.length;\n const kept = spared > 0\n ? ` ${String(spared)} worktree(s) were deliberately SPARED (locked, holding unmerged work, or ` +\n 'the one you are standing in) — do not remove those; a human decides.'\n : '';\n\n return new FixHint(\n 'Too many worktrees — reap the dead ones before creating another.',\n 'Full detail (deletable + spared, with per-worktree reasons) is in .webpieces/merged-branches.json, ' +\n `refreshed ${cache.timestamp || 'never'}.${kept} Pick one:`,\n options,\n );\n }\n}\n"]}