@webpieces/rules-config 0.4.635 → 0.4.637
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 +1 -1
- package/src/WebpiecesRulesConfig.d.ts +4 -8
- package/src/WebpiecesRulesConfig.js +5 -6
- package/src/WebpiecesRulesConfig.js.map +1 -1
- package/src/abstract-rule.d.ts +21 -2
- package/src/abstract-rule.js +22 -2
- package/src/abstract-rule.js.map +1 -1
- package/src/default-rules.js +15 -9
- package/src/default-rules.js.map +1 -1
- package/src/index.d.ts +4 -3
- package/src/index.js +11 -13
- package/src/index.js.map +1 -1
- package/src/load-config.d.ts +0 -1
- package/src/load-config.js +13 -18
- package/src/load-config.js.map +1 -1
- package/src/main-sync-file.d.ts +27 -1
- package/src/main-sync-file.js +34 -3
- package/src/main-sync-file.js.map +1 -1
- package/src/main-sync-guard-configs.d.ts +22 -22
- package/src/main-sync-guard-configs.js +27 -72
- package/src/main-sync-guard-configs.js.map +1 -1
- package/src/main-sync-status.d.ts +2 -0
- package/src/main-sync-status.js +18 -2
- package/src/main-sync-status.js.map +1 -1
- package/src/retired-config-keys.js +73 -6
- package/src/retired-config-keys.js.map +1 -1
- package/src/rule-configs.d.ts +31 -16
- package/src/rule-configs.js +32 -33
- package/src/rule-configs.js.map +1 -1
- package/src/rule-schemas.d.ts +11 -0
- package/src/rule-schemas.js +26 -10
- package/src/rule-schemas.js.map +1 -1
- package/src/sections.d.ts +43 -2
- package/src/sections.js +50 -18
- package/src/sections.js.map +1 -1
- package/src/validate-config.js +10 -0
- package/src/validate-config.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.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 { AtomicFile } from './atomic-file';\nimport {\n MAIN_SYNC_STATUS_VERSION,\n MainSyncFileStore,\n MainSyncStatus,\n MainSyncStatusFile,\n PullRequestIndex,\n} from './main-sync-file';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { toError } from './to-error';\nimport { WorktreeService } from './worktrees';\n\n// The on-disk shape lives in main-sync-file.ts; re-exported so every existing importer of\n// `MainSyncStatus` from this module keeps working.\nexport { MainSyncStatus, MainSyncStatusFile, PullRequestIndex, MAIN_SYNC_STATUS_VERSION };\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher\n// process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.\nexport class MainSyncLock {\n state: string;\n started: number;\n pid: number;\n\n constructor(state: string, started: number, pid: number = 0) {\n this.state = state;\n this.started = started;\n this.pid = pid;\n }\n}\n\n// Raw JSON shape for the cast at the parse boundary.\ninterface RawLock {\n state?: string;\n started?: number;\n pid?: number;\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/**\n * Reads/writes the main-sync cache + lock and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class MainSyncStatusService {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly store: MainSyncFileStore = new MainSyncFileStore(atomicFile),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n ) {}\n\n /**\n * SHARED scope: `<primary>/.webpieces/main-sync-status.json`, one per repo.\n *\n * It is the OUTPUT of the single-flight refresher below, so it has to live where the lock lives —\n * one `.git`, one `origin/main`, one fetch, one answer. A per-worktree copy under single-flight\n * would only ever be written for whichever worktree won the lock anyway.\n *\n * FORMERLY a known defect, now fixed: the file's CONTENT used to be keyed to ONE branch, and every\n * reader fails OPEN when the cached branch is not the branch being judged (`stale-cross-branch-cache\n * (fail-open)`). With several worktrees on several branches, only the winner of the lock was ever\n * described, so every other worktree's guards abstained — and which one was armed thrashed as the\n * lock changed hands. The file is now a MAP of branch -> status (see MainSyncStatusFile): the single\n * winning refresher enumerates every worktree and records all of their branches in one atomic write,\n * so one fetch arms everyone. Single-flight is unchanged; only what the winner writes changed.\n */\n mainSyncStatusPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_STATUS_FILE);\n }\n\n /**\n * SHARED scope — the single-flight lock for the whole repo.\n *\n * This is the fix for the filed bug \"the detached main-sync refresher's `git fetch` races the\n * agent's foreground `git fetch`/`git pull` and corrupts `.git/FETCH_HEAD`\". The lock was already\n * atomic (O_CREAT|O_EXCL) and already serialised refreshers against each other — but only WITHIN\n * one worktree, so N worktrees meant N locks and up to N concurrent `git fetch`es against the ONE\n * shared `.git`, which is exactly how a duplicate `for-merge` line lands in FETCH_HEAD. There is\n * one `.git`, so there is now one lock: at most one refresher in flight across all worktrees.\n *\n * Stale-lock handling is unchanged and still required (a lock nobody can clear is worse than the\n * race): the holder records pid + start epoch, and `tryAcquireMainSyncLock` reclaims it once\n * `isRefreshInProgress` proves the holder is finished, hung past hangTimeoutMinutes, or dead.\n */\n mainSyncLockPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_LOCK_FILE);\n }\n\n /**\n * The cached status FOR ONE BRANCH — the only read the four main-sync guards perform.\n *\n * `branch` is mandatory and is the branch being judged. A branch the last refresh never saw\n * returns null, which every guard already treats as `no-sync-cache (fail-open)`, so an unknown\n * branch degrades exactly as a missing file did. Any error (missing, truncated, malformed JSON)\n * also returns null — this must never throw on the blocking path.\n */\n readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null {\n return this.store.branchStatus(this.readMainSyncStatusFile(repoRoot), branch);\n }\n\n // The whole branch-keyed document, or null. Callers that need more than one branch use this.\n readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null {\n return this.store.readFile(this.mainSyncStatusPath(repoRoot));\n }\n\n /**\n * Merge ONE branch's entry into the existing map, atomically, leaving siblings untouched.\n *\n * Read-modify-write rather than replace: this is the writer used by everyone who is NOT the\n * single-flight refresher (notably pr-gate's post-merge stamp, which holds no lock and knows about\n * exactly one branch). Replacing the document from there would erase every other worktree's entry\n * and silently disarm their guards until the next refresh.\n */\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n this.store.mergeBranch(this.mainSyncStatusPath(repoRoot), status);\n }\n\n /**\n * Replace the WHOLE document — only the winning refresher may do this.\n *\n * ATOMIC (temp file + `rename()` in the same directory). The refresher writes this file while\n * guards on the blocking path read it, possibly from another worktree; a plain `writeFileSync`\n * truncates first, so a reader landing in that window gets a torn document. See AtomicFile.\n */\n writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void {\n this.store.writeFile(this.mainSyncStatusPath(repoRoot), file);\n }\n\n readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = this.mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n }\n\n // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.\n isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n }\n\n // True when another refresher is actively running and we should NOT start a second one.\n isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = this.readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (this.isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return this.isProcessAlive(lock.pid);\n }\n\n /**\n * ATOMICALLY take the refresher lock. Returns the held lock, or null when someone else holds it.\n *\n * Replaces the check-then-write pair (`isRefreshInProgress` then `writeMainSyncLock`), whose gap\n * let two detached refreshers both pass the check and then both run `git fetch` at once — the\n * concurrency this lock exists to prevent. The create uses the `wx` flag (O_CREAT|O_EXCL), so of\n * N racing refreshers exactly one creates the file.\n *\n * A lock file left behind by a FINISHED, hung, or killed refresher is reclaimed: we only unlink\n * and re-take it once `isRefreshInProgress` says the holder is provably not running, and we\n * re-read afterwards to confirm the entry we see is ours before claiming the lock.\n */\n tryAcquireMainSyncLock(\n repoRoot: string,\n hangTimeoutMinutes: number,\n now: number = Date.now(),\n pid: number = process.pid,\n ): MainSyncLock | null {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n const lock = this.inProcessLock(now, pid);\n const payload = JSON.stringify(lock, null, 2) + '\\n';\n\n if (this.createExclusive(lockPath, payload)) return lock;\n\n // The file already exists. Leave it alone while a live refresher owns it.\n if (this.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now)) return null;\n\n // Reclaim it: remove the dead entry, then re-take it exclusively so only one reclaimer wins.\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.unlinkSync(lockPath);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // someone else reclaimed it first — the exclusive create below decides\n }\n if (!this.createExclusive(lockPath, payload)) return null;\n\n // Confirm the lock on disk is OURS (a simultaneous reclaimer could have unlinked ours and\n // written its own between the two calls above).\n const held = this.readMainSyncLock(repoRoot);\n if (!held || held.pid !== pid || held.started !== lock.started) return null;\n return lock;\n }\n\n // O_CREAT|O_EXCL write: true when THIS call created the file, false when it already existed.\n private createExclusive(lockPath: string, payload: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.writeFileSync(lockPath, payload, { flag: 'wx' });\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n }\n\n finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n }\n\n /**\n * The SLOW path for ONE branch: the branch checked out in `repoRoot`. Runs its own fetch and its\n * own `gh` query, so it is the right entry point for a single-worktree caller and for tests.\n * The refresher uses computeAllMainSyncStatuses instead — see there for why.\n */\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n this.fetchOriginMain(repoRoot);\n return this.computeBranchStatus(repoRoot, this.gitBranch(repoRoot), this.loadPullRequestIndex(repoRoot));\n }\n\n /**\n * The SLOW path for EVERY branch this repo has checked out — what the detached refresher runs.\n *\n * The cache is shared by all worktrees but was written for one branch, so only one worktree's\n * guards were ever armed. Enumerating the worktrees here and writing all of their branches in one\n * atomic map arms every worktree off a single refresh.\n *\n * NETWORK COST DOES NOT SCALE WITH BRANCH COUNT, which is the whole reason this is a separate\n * method: ONE `git fetch` and ONE repo-wide `gh pr list` for all N branches (the old code asked\n * `gh` twice per branch, so looping it would have cost 2N round trips). Everything after that is\n * local git in each worktree — merge-base, rev-parse, diff, ls-files.\n *\n * The map is rebuilt from the LIVE worktree set every refresh, so a deleted branch or removed\n * worktree simply stops appearing. No pruning pass, no unbounded growth.\n */\n computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile {\n this.fetchOriginMain(repoRoot);\n const index = this.loadPullRequestIndex(repoRoot);\n const branches: Record<string, MainSyncStatus> = {};\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n // Detached HEAD (and a bare worktree) has no branch to key an entry on. Skipping it must\n // not stop the others being recorded, which is why this is a `continue`, not a bail-out.\n if (tree.branch === '' || this.hasBranch(branches, tree.branch)) continue;\n branches[tree.branch] = this.computeBranchStatus(tree.path, tree.branch, index);\n }\n // `main` always present, even with no worktree on it: read-stale-guard's state A looks itself\n // up under 'main', and an absent entry there would silently disarm the stale-main block.\n if (!this.hasBranch(branches, 'main')) branches['main'] = this.mainOnlyStatus(repoRoot);\n return new MainSyncStatusFile(MAIN_SYNC_STATUS_VERSION, new Date().toISOString(), branches);\n }\n\n private hasBranch(branches: Record<string, MainSyncStatus>, branch: string): boolean {\n return Object.prototype.hasOwnProperty.call(branches, branch);\n }\n\n /**\n * One branch's status, computed in `worktreePath` (the tree that has it checked out — the\n * working-tree overlap signals are only correct from there). Does NOT fetch and does NOT call\n * `gh`: both are done once per refresh by the caller.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatus(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n const mergedPr = index.mergedFor(branch);\n const openPr = index.openFor(branch);\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n status.localMain = localMain;\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n noFork.localMain = localMain;\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n status.localMain = localMain;\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n ' ^^ HUMAN-ONLY. `git merge` is blocked for AI (redirect-how-to-merge-main). AI: ask the',\n ' human to run step 3, and warn them it is a raw merge — only correct here because the',\n ' branch is already broken. For a normal update from main they should push back and',\n ' tell you to use the gated 3-point merge instead: `pnpm wp-start-update` (paired with',\n ' `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr` (paired with',\n ' `pnpm wp-finish-upsert-pr`) when a PR IS open — a PR MUST use the upsert-pr pair.',\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n /**\n * Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n *\n * This is the one writer that takes NO lock, and it knows about exactly one branch. It therefore\n * goes through writeMainSyncStatus, which MERGES its entry into the existing map; writing a whole\n * document from here would wipe every other worktree's entry.\n */\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n status.localMain = this.localMainHash(repoRoot);\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n /**\n * Refresh `origin/main` WITHOUT writing `.git/FETCH_HEAD`.\n *\n * WHY the flag is not optional: this runs in a DETACHED background process while the agent is\n * running its own foreground `git fetch` / `git pull` in the very same repo. `.git/FETCH_HEAD` is\n * a single file that git takes no lock on, so two overlapping fetches interleave their writes and\n * can leave the SAME `for-merge` line twice. `git pull` then reads two for-merge entries and dies\n * with `fatal: Cannot fast-forward to multiple branches` — wedging the exact command the\n * read-stale-guard tells the agent to run. `--no-write-fetch-head` (git >= 2.29) still updates the\n * remote-tracking ref, which is all anything downstream reads (`origin/main`, merge-base), so\n * nothing here needs FETCH_HEAD written at all.\n *\n * Older git rejects the flag; only then do we retry the plain form, accepting the old behaviour\n * rather than losing the refresh entirely on a pre-2020 git.\n */\n private fetchOriginMain(repoRoot: string): void {\n const safe = spawnSync('git', ['fetch', '--no-write-fetch-head', 'origin', 'main'], {\n cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'],\n });\n if (safe.status === 0) return;\n if (!this.isUnknownGitOption(safe.stderr)) return; // a real failure (offline, auth) — not our business\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n }\n\n // Did git reject the flag itself (too old), as opposed to failing the network refresh?\n private isUnknownGitOption(stderr: string | null): boolean {\n const text = (stderr ?? '').toLowerCase();\n return text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n }\n\n private ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n }\n\n // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.\n private isProcessAlive(pid: number): boolean {\n if (pid <= 0) return true;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n return !error.message.includes('ESRCH');\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 // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n // The LOCAL main hash. `refs/heads/main` (not the bare name) so it can never resolve to a remote\n // ref or a tag. '' when main does not exist locally — which the guard treats as fail-open.\n private localMainHash(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', 'refs/heads/main']);\n return result.ok ? result.out : '';\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n /**\n * ONE repo-wide `gh pr list` giving every branch's merged/open PR — replaces the two per-branch\n * `--head <branch>` queries. `--limit 200` bounds it; a branch whose only PR is older than that\n * falls out of the index and reads as \"no PR\", which is the fail-OPEN direction (no merged PR =\n * no block), the same way a missing `gh` or an offline machine already degraded.\n */\n private loadPullRequestIndex(repoRoot: string): PullRequestIndex {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list', '--state', 'all', '--json', 'number,headRefName,state', '--limit', '200',\n ]);\n if (!result.ok || result.out === '') return new PullRequestIndex({}, {});\n return this.store.indexPullRequests(result.out);\n }\n\n /**\n * A `main` entry synthesised WITHOUT a worktree standing on main. Carries only the two hashes\n * read-stale-guard / stale-main-bash-guard actually compare (originMain vs the live tree's\n * ancestry) — never merged, never conflicting, so it can only ever produce the stale-main verdict.\n */\n private mainOnlyStatus(repoRoot: string): MainSyncStatus {\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const status = new MainSyncStatus(\n 'main', false, '', true, localMain === '' ? null : localMain,\n originMain.ok ? originMain.out : '', localMain, false, [], new Date().toISOString(),\n );\n status.localMain = localMain;\n return status;\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncLockPath(repoRoot: string): string { return mainSyncSvc.mainSyncLockPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot, branch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null { return mainSyncSvc.readMainSyncStatusFile(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void { mainSyncSvc.writeMainSyncStatusFile(repoRoot, file); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null { return mainSyncSvc.readMainSyncLock(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function tryAcquireMainSyncLock(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now(), pid: number = process.pid): MainSyncLock | null { return mainSyncSvc.tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock { return mainSyncSvc.inProcessLock(now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function finishedLock(started: number): MainSyncLock { return mainSyncSvc.finishedLock(started); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile { return mainSyncSvc.computeAllMainSyncStatuses(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
|
|
1
|
+
{"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.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 { AtomicFile } from './atomic-file';\nimport {\n MAIN_SYNC_STATUS_VERSION,\n MainSyncFileStore,\n MainSyncStatus,\n MainSyncStatusFile,\n PullRequestIndex,\n} from './main-sync-file';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { toError } from './to-error';\nimport { WorktreeService } from './worktrees';\n\n// The on-disk shape lives in main-sync-file.ts; re-exported so every existing importer of\n// `MainSyncStatus` from this module keeps working.\nexport { MainSyncStatus, MainSyncStatusFile, PullRequestIndex, MAIN_SYNC_STATUS_VERSION };\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher\n// process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.\nexport class MainSyncLock {\n state: string;\n started: number;\n pid: number;\n\n constructor(state: string, started: number, pid: number = 0) {\n this.state = state;\n this.started = started;\n this.pid = pid;\n }\n}\n\n// Raw JSON shape for the cast at the parse boundary.\ninterface RawLock {\n state?: string;\n started?: number;\n pid?: number;\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/**\n * Reads/writes the main-sync cache + lock and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class MainSyncStatusService {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly store: MainSyncFileStore = new MainSyncFileStore(atomicFile),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n ) {}\n\n /**\n * SHARED scope: `<primary>/.webpieces/main-sync-status.json`, one per repo.\n *\n * It is the OUTPUT of the single-flight refresher below, so it has to live where the lock lives —\n * one `.git`, one `origin/main`, one fetch, one answer. A per-worktree copy under single-flight\n * would only ever be written for whichever worktree won the lock anyway.\n *\n * FORMERLY a known defect, now fixed: the file's CONTENT used to be keyed to ONE branch, and every\n * reader fails OPEN when the cached branch is not the branch being judged (`stale-cross-branch-cache\n * (fail-open)`). With several worktrees on several branches, only the winner of the lock was ever\n * described, so every other worktree's guards abstained — and which one was armed thrashed as the\n * lock changed hands. The file is now a MAP of branch -> status (see MainSyncStatusFile): the single\n * winning refresher enumerates every worktree and records all of their branches in one atomic write,\n * so one fetch arms everyone. Single-flight is unchanged; only what the winner writes changed.\n */\n mainSyncStatusPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_STATUS_FILE);\n }\n\n /**\n * SHARED scope — the single-flight lock for the whole repo.\n *\n * This is the fix for the filed bug \"the detached main-sync refresher's `git fetch` races the\n * agent's foreground `git fetch`/`git pull` and corrupts `.git/FETCH_HEAD`\". The lock was already\n * atomic (O_CREAT|O_EXCL) and already serialised refreshers against each other — but only WITHIN\n * one worktree, so N worktrees meant N locks and up to N concurrent `git fetch`es against the ONE\n * shared `.git`, which is exactly how a duplicate `for-merge` line lands in FETCH_HEAD. There is\n * one `.git`, so there is now one lock: at most one refresher in flight across all worktrees.\n *\n * Stale-lock handling is unchanged and still required (a lock nobody can clear is worse than the\n * race): the holder records pid + start epoch, and `tryAcquireMainSyncLock` reclaims it once\n * `isRefreshInProgress` proves the holder is finished, hung past hangTimeoutMinutes, or dead.\n */\n mainSyncLockPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_LOCK_FILE);\n }\n\n /**\n * The cached status FOR ONE BRANCH — the only read the four main-sync guards perform.\n *\n * `branch` is mandatory and is the branch being judged. A branch the last refresh never saw\n * returns null, which every guard already treats as `no-sync-cache (fail-open)`, so an unknown\n * branch degrades exactly as a missing file did. Any error (missing, truncated, malformed JSON)\n * also returns null — this must never throw on the blocking path.\n */\n readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null {\n return this.store.branchStatus(this.readMainSyncStatusFile(repoRoot), branch);\n }\n\n // The whole branch-keyed document, or null. Callers that need more than one branch use this.\n readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null {\n return this.store.readFile(this.mainSyncStatusPath(repoRoot));\n }\n\n /**\n * Merge ONE branch's entry into the existing map, atomically, leaving siblings untouched.\n *\n * Read-modify-write rather than replace: this is the writer used by everyone who is NOT the\n * single-flight refresher (notably pr-gate's post-merge stamp, which holds no lock and knows about\n * exactly one branch). Replacing the document from there would erase every other worktree's entry\n * and silently disarm their guards until the next refresh.\n */\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n this.store.mergeBranch(this.mainSyncStatusPath(repoRoot), status);\n }\n\n /**\n * Replace the WHOLE document — only the winning refresher may do this.\n *\n * ATOMIC (temp file + `rename()` in the same directory). The refresher writes this file while\n * guards on the blocking path read it, possibly from another worktree; a plain `writeFileSync`\n * truncates first, so a reader landing in that window gets a torn document. See AtomicFile.\n */\n writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void {\n this.store.writeFile(this.mainSyncStatusPath(repoRoot), file);\n }\n\n readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = this.mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n }\n\n // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.\n isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n }\n\n // True when another refresher is actively running and we should NOT start a second one.\n isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = this.readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (this.isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return this.isProcessAlive(lock.pid);\n }\n\n /**\n * ATOMICALLY take the refresher lock. Returns the held lock, or null when someone else holds it.\n *\n * Replaces the check-then-write pair (`isRefreshInProgress` then `writeMainSyncLock`), whose gap\n * let two detached refreshers both pass the check and then both run `git fetch` at once — the\n * concurrency this lock exists to prevent. The create uses the `wx` flag (O_CREAT|O_EXCL), so of\n * N racing refreshers exactly one creates the file.\n *\n * A lock file left behind by a FINISHED, hung, or killed refresher is reclaimed: we only unlink\n * and re-take it once `isRefreshInProgress` says the holder is provably not running, and we\n * re-read afterwards to confirm the entry we see is ours before claiming the lock.\n */\n tryAcquireMainSyncLock(\n repoRoot: string,\n hangTimeoutMinutes: number,\n now: number = Date.now(),\n pid: number = process.pid,\n ): MainSyncLock | null {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n const lock = this.inProcessLock(now, pid);\n const payload = JSON.stringify(lock, null, 2) + '\\n';\n\n if (this.createExclusive(lockPath, payload)) return lock;\n\n // The file already exists. Leave it alone while a live refresher owns it.\n if (this.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now)) return null;\n\n // Reclaim it: remove the dead entry, then re-take it exclusively so only one reclaimer wins.\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.unlinkSync(lockPath);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // someone else reclaimed it first — the exclusive create below decides\n }\n if (!this.createExclusive(lockPath, payload)) return null;\n\n // Confirm the lock on disk is OURS (a simultaneous reclaimer could have unlinked ours and\n // written its own between the two calls above).\n const held = this.readMainSyncLock(repoRoot);\n if (!held || held.pid !== pid || held.started !== lock.started) return null;\n return lock;\n }\n\n // O_CREAT|O_EXCL write: true when THIS call created the file, false when it already existed.\n private createExclusive(lockPath: string, payload: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.writeFileSync(lockPath, payload, { flag: 'wx' });\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n }\n\n finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n }\n\n /**\n * The SLOW path for ONE branch: the branch checked out in `repoRoot`. Runs its own fetch and its\n * own `gh` query, so it is the right entry point for a single-worktree caller and for tests.\n * The refresher uses computeAllMainSyncStatuses instead — see there for why.\n */\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n this.fetchOriginMain(repoRoot);\n return this.computeBranchStatus(repoRoot, this.gitBranch(repoRoot), this.loadPullRequestIndex(repoRoot));\n }\n\n /**\n * The SLOW path for EVERY branch this repo has checked out — what the detached refresher runs.\n *\n * The cache is shared by all worktrees but was written for one branch, so only one worktree's\n * guards were ever armed. Enumerating the worktrees here and writing all of their branches in one\n * atomic map arms every worktree off a single refresh.\n *\n * NETWORK COST DOES NOT SCALE WITH BRANCH COUNT, which is the whole reason this is a separate\n * method: ONE `git fetch` and ONE repo-wide `gh pr list` for all N branches (the old code asked\n * `gh` twice per branch, so looping it would have cost 2N round trips). Everything after that is\n * local git in each worktree — merge-base, rev-parse, diff, ls-files.\n *\n * The map is rebuilt from the LIVE worktree set every refresh, so a deleted branch or removed\n * worktree simply stops appearing. No pruning pass, no unbounded growth.\n */\n computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile {\n this.fetchOriginMain(repoRoot);\n const index = this.loadPullRequestIndex(repoRoot);\n const branches: Record<string, MainSyncStatus> = {};\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n // Detached HEAD (and a bare worktree) has no branch to key an entry on. Skipping it must\n // not stop the others being recorded, which is why this is a `continue`, not a bail-out.\n if (tree.branch === '' || this.hasBranch(branches, tree.branch)) continue;\n branches[tree.branch] = this.computeBranchStatus(tree.path, tree.branch, index);\n }\n // `main` always present, even with no worktree on it: read-stale-guard's state A looks itself\n // up under 'main', and an absent entry there would silently disarm the stale-main block.\n if (!this.hasBranch(branches, 'main')) branches['main'] = this.mainOnlyStatus(repoRoot);\n return new MainSyncStatusFile(MAIN_SYNC_STATUS_VERSION, new Date().toISOString(), branches);\n }\n\n private hasBranch(branches: Record<string, MainSyncStatus>, branch: string): boolean {\n return Object.prototype.hasOwnProperty.call(branches, branch);\n }\n\n /**\n * One branch's status, computed in `worktreePath` (the tree that has it checked out — the\n * working-tree overlap signals are only correct from there). Does NOT fetch and does NOT call\n * `gh`: both are done once per refresh by the caller.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatus(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n return this.stampForge(this.computeBranchStatusInner(repoRoot, branch, index), index);\n }\n\n // Every return path of the computation below has to carry the forge flag, and threading it through\n // four constructions is how one of them would end up missing it. One wrapper, one assignment.\n private stampForge(status: MainSyncStatus, index: PullRequestIndex): MainSyncStatus {\n status.forgeReachable = index.forgeReachable;\n return status;\n }\n\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatusInner(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n const mergedPr = index.mergedFor(branch);\n const openPr = index.openFor(branch);\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n status.localMain = localMain;\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n noFork.localMain = localMain;\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n status.localMain = localMain;\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n ' ^^ HUMAN-ONLY. `git merge` is blocked for AI (redirect-how-to-merge-main). AI: ask the',\n ' human to run step 3, and warn them it is a raw merge — only correct here because the',\n ' branch is already broken. For a normal update from main they should push back and',\n ' tell you to use the gated 3-point merge instead: `pnpm wp-start-update` (paired with',\n ' `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr` (paired with',\n ' `pnpm wp-finish-upsert-pr`) when a PR IS open — a PR MUST use the upsert-pr pair.',\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n /**\n * Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n *\n * This is the one writer that takes NO lock, and it knows about exactly one branch. It therefore\n * goes through writeMainSyncStatus, which MERGES its entry into the existing map; writing a whole\n * document from here would wipe every other worktree's entry.\n */\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n status.localMain = this.localMainHash(repoRoot);\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n /**\n * Refresh `origin/main` WITHOUT writing `.git/FETCH_HEAD`.\n *\n * WHY the flag is not optional: this runs in a DETACHED background process while the agent is\n * running its own foreground `git fetch` / `git pull` in the very same repo. `.git/FETCH_HEAD` is\n * a single file that git takes no lock on, so two overlapping fetches interleave their writes and\n * can leave the SAME `for-merge` line twice. `git pull` then reads two for-merge entries and dies\n * with `fatal: Cannot fast-forward to multiple branches` — wedging the exact command the\n * read-stale-guard tells the agent to run. `--no-write-fetch-head` (git >= 2.29) still updates the\n * remote-tracking ref, which is all anything downstream reads (`origin/main`, merge-base), so\n * nothing here needs FETCH_HEAD written at all.\n *\n * Older git rejects the flag; only then do we retry the plain form, accepting the old behaviour\n * rather than losing the refresh entirely on a pre-2020 git.\n */\n private fetchOriginMain(repoRoot: string): void {\n const safe = spawnSync('git', ['fetch', '--no-write-fetch-head', 'origin', 'main'], {\n cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'],\n });\n if (safe.status === 0) return;\n if (!this.isUnknownGitOption(safe.stderr)) return; // a real failure (offline, auth) — not our business\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n }\n\n // Did git reject the flag itself (too old), as opposed to failing the network refresh?\n private isUnknownGitOption(stderr: string | null): boolean {\n const text = (stderr ?? '').toLowerCase();\n return text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n }\n\n private ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n }\n\n // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.\n private isProcessAlive(pid: number): boolean {\n if (pid <= 0) return true;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n return !error.message.includes('ESRCH');\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 // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n // The LOCAL main hash. `refs/heads/main` (not the bare name) so it can never resolve to a remote\n // ref or a tag. '' when main does not exist locally — which the guard treats as fail-open.\n private localMainHash(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', 'refs/heads/main']);\n return result.ok ? result.out : '';\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n /**\n * ONE repo-wide `gh pr list` giving every branch's merged/open PR — replaces the two per-branch\n * `--head <branch>` queries. `--limit 200` bounds it; a branch whose only PR is older than that\n * falls out of the index and reads as \"no PR\", which is the fail-OPEN direction (no merged PR =\n * no block), the same way a missing `gh` or an offline machine already degraded.\n */\n private loadPullRequestIndex(repoRoot: string): PullRequestIndex {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list', '--state', 'all', '--json', 'number,headRefName,state', '--limit', '200',\n ]);\n // `gh` could not be run, or exited non-zero (missing, unauthenticated, rate-limited, offline).\n // An EMPTY index is the same fail-open answer as before — but it is now labelled UNREACHABLE, so\n // the guards can report an abstention rather than an approval. Empty STDOUT on a zero exit is a\n // real answer (\"this repo has no PRs\"), so only the failure path clears the flag.\n if (!result.ok) return new PullRequestIndex({}, {}, false);\n if (result.out === '') return new PullRequestIndex({}, {}, true);\n return this.store.indexPullRequests(result.out);\n }\n\n /**\n * A `main` entry synthesised WITHOUT a worktree standing on main. Carries only the two hashes\n * read-stale-guard / stale-main-bash-guard actually compare (originMain vs the live tree's\n * ancestry) — never merged, never conflicting, so it can only ever produce the stale-main verdict.\n */\n private mainOnlyStatus(repoRoot: string): MainSyncStatus {\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const status = new MainSyncStatus(\n 'main', false, '', true, localMain === '' ? null : localMain,\n originMain.ok ? originMain.out : '', localMain, false, [], new Date().toISOString(),\n );\n status.localMain = localMain;\n return status;\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncLockPath(repoRoot: string): string { return mainSyncSvc.mainSyncLockPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot, branch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null { return mainSyncSvc.readMainSyncStatusFile(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void { mainSyncSvc.writeMainSyncStatusFile(repoRoot, file); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null { return mainSyncSvc.readMainSyncLock(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function tryAcquireMainSyncLock(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now(), pid: number = process.pid): MainSyncLock | null { return mainSyncSvc.tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock { return mainSyncSvc.inProcessLock(now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function finishedLock(started: number): MainSyncLock { return mainSyncSvc.finishedLock(started); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile { return mainSyncSvc.computeAllMainSyncStatuses(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
|
|
@@ -99,12 +99,20 @@ exports.RETIRED_CONFIG_KEYS = [
|
|
|
99
99
|
// load-config.ts rewrote the key so no validator ever saw it). That hid the rename from the config file
|
|
100
100
|
// forever: the old name kept working, so no consumer ever updated, and the alias table could never be
|
|
101
101
|
// deleted. Now each is a hard error with the new name.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
//
|
|
103
|
+
// These three RE-POINTED when hookGuards collapsed to one key per policy. Their old destinations
|
|
104
|
+
// (`pr-merge-guard`, `pr-creation-or-push-guard`, `read-stale-guard`) are themselves retired now,
|
|
105
|
+
// and a retirement whose `movedTo` names a dead key teaches the removed API — the same defect one
|
|
106
|
+
// level out. Each therefore names the POLICY key it lands on today, in one hop.
|
|
107
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'pr-merge-cleanup', 'pr-lifecycle-guard', 'Rename the key to "pr-lifecycle-guard" (merging its value into that entry if you already have ' +
|
|
108
|
+
'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over.', '[pr-merge-cleanup]', false),
|
|
109
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'pr-creation-guard', 'pr-lifecycle-guard', 'Rename the key to "pr-lifecycle-guard" (merging its value into that entry if you already have ' +
|
|
110
|
+
'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over; the ' +
|
|
111
|
+
'old "upsertPrCommand" field does NOT — that string lives only in commands.guardHints.prCreationOrPush.', '[pr-creation-guard]', false),
|
|
112
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'main-stale-guard', 'branch-state-guard', 'Rename the key to "branch-state-guard" (merging its value into that entry if you already have ' +
|
|
113
|
+
'one — the four branch-state guards share one key now). Its mode and escape hatches carry over. ' +
|
|
114
|
+
'Note the widened scope: that key arms the Write, Read AND Bash halves of the branch-state ' +
|
|
115
|
+
'policy, not just the Read block this key used to name.', '[main-stale-guard]', false),
|
|
108
116
|
// --- The two flat guard-hint strings, superseded by commands.guardHints so that every guard-facing
|
|
109
117
|
// command string sits in one named sub-object instead of loose beside the gate config.
|
|
110
118
|
new RetiredConfigKey(exports.RETIRED_SCOPE_KEY, 'upsertPr', 'commands.guardHints.prCreationOrPush', 'Move the value to "guardHints": { "prCreationOrPush": <value> } inside the same "commands" ' +
|
|
@@ -129,7 +137,66 @@ exports.RETIRED_CONFIG_KEYS = [
|
|
|
129
137
|
'YOUR machine only, put {"experimental": {"whole-repo-build-guard": true}} in ' +
|
|
130
138
|
'~/.webpieces/config.json — that file is optional, is tracked by no repo, and every webpieces ' +
|
|
131
139
|
'command behaves exactly as it does by default when it does not exist.', '[whole-repo-build-guard]', true),
|
|
140
|
+
// --- THE 9 → 3 COLLAPSE of `hookGuards`: one key per POLICY, not one per implementation CLASS.
|
|
141
|
+
//
|
|
142
|
+
// Eight class-named keys become two policy keys. The CLASSES are untouched — `feature-branch-guard`
|
|
143
|
+
// is still the rule name in every decision-log line and every deny report — so nothing an operator
|
|
144
|
+
// greps for moved. What moved is the SWITCH, because nine keys let a consumer configure HALF a
|
|
145
|
+
// policy: `read-stale-guard: OFF` beside `merged-branch-bash-guard: ON` is "read the file, yes;
|
|
146
|
+
// `cat` the same file, no", which nobody chose and the config made reachable.
|
|
147
|
+
//
|
|
148
|
+
// EVERY ONE OF THESE IS `prunable: false`, and that is not bookkeeping. `PRUNABLE_SECTIONS` in
|
|
149
|
+
// ConfigPruner includes `hookGuards`, and the validation banner actively RECOMMENDS running the
|
|
150
|
+
// pruner. A `prunable: true` here would mean the recommended cure silently DELETES four configured
|
|
151
|
+
// guards with no destination named — a config loss, which is worse than a block. False keeps the
|
|
152
|
+
// migration instruction and makes the pruner return null for these keys.
|
|
153
|
+
//
|
|
154
|
+
// 4 → 1 is legal for this table (nothing requires `movedTo` to be unique), but it is NOT legal for
|
|
155
|
+
// a naive 1:1 migrator: see migrateRetiredRuleNames in ai-hook-rules' setup.ts, which unions the
|
|
156
|
+
// four old entries into the destination and then fills any missing required field, rather than
|
|
157
|
+
// renaming the first and deleting the rest.
|
|
158
|
+
...branchStateRetirements(),
|
|
159
|
+
...prLifecycleRetirements(),
|
|
132
160
|
];
|
|
161
|
+
// The four branch-state classes. Split into a helper purely to keep the table above readable; the
|
|
162
|
+
// instruction is per-key because the fields that carry over differ (only feature-branch-guard had
|
|
163
|
+
// `branchNamingConvention`).
|
|
164
|
+
// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds
|
|
165
|
+
function branchStateRetirements() {
|
|
166
|
+
const merged = 'MERGE it into ONE "branch-state-guard" entry under "hookGuards"';
|
|
167
|
+
const shared = `${merged}. All four of feature-branch-guard, read-stale-guard, stale-main-bash-guard and ` +
|
|
168
|
+
'merged-branch-bash-guard now read that single entry — they are one policy ("may I work here, ' +
|
|
169
|
+
'and is what I read current?") implemented by four classes, and configuring them separately let ' +
|
|
170
|
+
'you switch on half of it. Keep "mode", "turnOffRuleUntilEpoch" and "turnOffRuleWhileOnBranch"; ' +
|
|
171
|
+
'the ONE "hangTimeoutMinutes" survives (there was only ever one refresher and one cache, so at ' +
|
|
172
|
+
'most one of the four values could ever take effect); "branchNamingConvention" survives from ' +
|
|
173
|
+
'feature-branch-guard. If the four disagreed on "mode", pick the one you meant — "ON" arms the ' +
|
|
174
|
+
'whole policy including the Read block. Then DELETE this key.';
|
|
175
|
+
return [
|
|
176
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'feature-branch-guard', 'branch-state-guard', shared, '[feature-branch-guard]', false),
|
|
177
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'read-stale-guard', 'branch-state-guard', shared, '[read-stale-guard]', false),
|
|
178
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'stale-main-bash-guard', 'branch-state-guard', shared, '[stale-main-bash-guard]', false),
|
|
179
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'merged-branch-bash-guard', 'branch-state-guard', shared, '[merged-branch-bash-guard]', false),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
// The four PR-lifecycle classes.
|
|
183
|
+
// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds
|
|
184
|
+
function prLifecycleRetirements() {
|
|
185
|
+
const shared = 'MERGE it into ONE "pr-lifecycle-guard" entry under "hookGuards". All four of ' +
|
|
186
|
+
'pr-creation-or-push-guard, merge-in-progress-guard, pr-merge-guard and ' +
|
|
187
|
+
'redirect-how-to-merge-main now read that single entry — they are one policy ("do PRs and ' +
|
|
188
|
+
'merges go through the gated flow?"). Keep "mode", "turnOffRuleUntilEpoch" and ' +
|
|
189
|
+
'"turnOffRuleWhileOnBranch". Know what "mode": "OFF" now means: it releases the ' +
|
|
190
|
+
'unvalidated-merge gate as well as the PR/push blocks, because merge-in-progress-guard is under ' +
|
|
191
|
+
'this key too. The per-guard "upsertPrCommand" / "mergeCompleteCommand" fields are gone — those ' +
|
|
192
|
+
'strings live ONLY in commands.guardHints.prCreationOrPush / .mergeInProgress. Then DELETE this key.';
|
|
193
|
+
return [
|
|
194
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'pr-creation-or-push-guard', 'pr-lifecycle-guard', shared, '[pr-creation-or-push-guard]', false),
|
|
195
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'merge-in-progress-guard', 'pr-lifecycle-guard', shared, '[merge-in-progress-guard]', false),
|
|
196
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'pr-merge-guard', 'pr-lifecycle-guard', shared, '[pr-merge-guard]', false),
|
|
197
|
+
new RetiredConfigKey(exports.RETIRED_SCOPE_RULE, 'redirect-how-to-merge-main', 'pr-lifecycle-guard', shared, '[redirect-how-to-merge-main]', false),
|
|
198
|
+
];
|
|
199
|
+
}
|
|
133
200
|
/**
|
|
134
201
|
* The shared message. Leads with the retirement, then the destination, then the edit — an agent reading this
|
|
135
202
|
* should not have to infer anything.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retired-config-keys.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/retired-config-keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Retired webpieces.config.json keys — the ONE place in the codebase where a dead config key may be named.\n *\n * ## THE POLICY: webpieces.config.json is never released backwards-compatible\n *\n * When a config key moves, is renamed, or is deleted, the loader REJECTS the old shape with an error that\n * names the destination. It does NOT quietly accept both. There is no fallback, no alias table applied\n * before validation, no \"still accepted for back-compat until every consumer migrates\".\n *\n * This is safe *here* in a way it is not for a normal library, and the reason is the consumer: every reader\n * of this file is a coding agent. The config is validated on startup, the agent is handed the exact edit,\n * and it applies it in one pass — so the upgrade is seamless without shipping a compatibility layer. That\n * makes a hard failure strictly cheaper than duality: two accepted shapes means two code paths, two sets of\n * defaults and two sets of error messages to keep honest forever, and the stale shape then lives in\n * consumer configs indefinitely because nothing ever forces the edit.\n *\n * A hard rejection here is always self-recoverable, which is what makes it safe to do:\n * - editing webpieces.config.json is ALWAYS permitted, even while the config is invalid (see the banner\n * in config-file.ts and the guard matrix — a Write/Edit targeting this file is an unconditional PASS),\n * - and `PRUNE_UNKNOWN_COMMAND` is an L0 CURE, so the mechanical cleanup of keys no validator knows runs\n * from inside the block too.\n * So rejecting an old shape can never wedge a repo. \"It would deadlock the consumer\" is not a reason to add\n * a fallback; it is not true.\n *\n * NOT `pnpm install`. This paragraph used to offer it as the escape hatch that \"fixes the far more common\n * cause of a validation failure: an installed validator lagging the config by a release\". It is permitted\n * (installer bypass), but it is not the cure for anything you can read here: the shim's version-drift guard\n * compares the pin against the installed version and denies every tool call BEFORE the validator runs, so a\n * validation error on screen is proof that package.json and node_modules already agree. The banner from this\n * same package now says \"Do NOT run `pnpm install` — it cannot help\", and a docstring teaching the opposite\n * is how that advice leaked back out.\n *\n * ## Adding an entry\n *\n * When you retire a key, add it here and DELETE its read path — do not leave a `??` fallback behind. The\n * `instruction` is the whole product: it is read by an agent that will act on it verbatim, so give the\n * mechanical edit (\"rename X to Y\", \"move A into B\"), not a description of the change. Saying where a thing\n * moved to is exactly the point of this table; it is what makes the fallback unnecessary.\n *\n * retired-config-keys.spec.ts asserts every entry below actually FAILS the load. That spec is the guard: a\n * future fallback that silently swallows a retired key turns it red.\n */\n\nimport { RETIRED_KEY_MARKER } from './config-error-banner';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// A retired key is matched one of two ways, because config keys live at two very different levels.\n// RULE — a rule/guard NAME, which may sit under either `rules` or `hookGuards`, so it is matched by bare\n// name rather than by a fixed path.\n// KEY — a plain key inside a known section, matched by name within that section.\nexport const RETIRED_SCOPE_RULE = 'rule';\nexport const RETIRED_SCOPE_KEY = 'key';\n\n/** One retired config key and the mechanical edit that replaces it. Data-only (per CLAUDE.md). */\nexport class RetiredConfigKey {\n scope: string;\n // The key exactly as it appears in webpieces.config.json.\n key: string;\n // Where the value goes now. Empty string when the key is deleted outright with no replacement.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it.\n instruction: string;\n // Bracketed label leading the error, matching the `[rule-name]` / `[section]` convention in this package.\n label: string;\n /**\n * True when DELETING the key from webpieces.config.json is the entire edit — nothing in THIS file\n * replaces it, so `PRUNE_UNKNOWN_COMMAND` may strip it mechanically. `whole-repo-build-guard` is the\n * worked example: its switch moved OUT of the repo config into `~/.webpieces/config.json`, so the\n * value has nowhere to go here.\n *\n * False for a rename or an in-file move: deleting those would DISCARD a value the reader still needs,\n * so they keep their migration instruction and the pruner leaves them alone. Required, not defaulted —\n * a defaulted `false` would let a future deletion-only retirement silently opt out of the mechanical\n * cure and land back in \"the reader decides while every Bash call is blocked\".\n */\n prunable: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(scope: string, key: string, movedTo: string, instruction: string, label: string, prunable: boolean) {\n this.scope = scope;\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n this.label = label;\n this.prunable = prunable;\n }\n}\n\n/**\n * Every retired key. Keep the newest at the bottom with the release that retired it, so the list reads as a\n * changelog an agent can walk when a config is several versions behind.\n */\nexport const RETIRED_CONFIG_KEYS: readonly RetiredConfigKey[] = [\n // --- Guard renames. Previously applied SILENTLY before validation (a DEPRECATED_RULE_ALIASES table in\n // load-config.ts rewrote the key so no validator ever saw it). That hid the rename from the config file\n // forever: the old name kept working, so no consumer ever updated, and the alias table could never be\n // deleted. Now each is a hard error with the new name.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-merge-cleanup', 'pr-merge-guard',\n 'Rename the key to \"pr-merge-guard\". Its value carries over unchanged.',\n '[pr-merge-cleanup]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-creation-guard', 'pr-creation-or-push-guard',\n 'Rename the key to \"pr-creation-or-push-guard\" — the guard grew a second blocked action (a manual ' +\n 'git push), so it is no longer only about PR creation. Its value carries over unchanged.',\n '[pr-creation-guard]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'main-stale-guard', 'read-stale-guard',\n 'Rename the key to \"read-stale-guard\" — the guard grew a second blocked state (an already-merged ' +\n 'feature branch), so it is no longer about `main` at all; it is THE guard that can block a Read. ' +\n 'Its value carries over unchanged.',\n '[main-stale-guard]', false,\n ),\n\n // --- The two flat guard-hint strings, superseded by commands.guardHints so that every guard-facing\n // command string sits in one named sub-object instead of loose beside the gate config.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'upsertPr', 'commands.guardHints.prCreationOrPush',\n 'Move the value to \"guardHints\": { \"prCreationOrPush\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"upsertPr\".',\n '[commands]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'mergeComplete', 'commands.guardHints.mergeInProgress',\n 'Move the value to \"guardHints\": { \"mergeInProgress\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"mergeComplete\".',\n '[commands]', false,\n ),\n\n // --- The two-list excludePaths object. The split never earned its keep (every consumer set both lists\n // to the same value), and the one case that would need them to differ is served better by a rule's own\n // `excludePaths`. Retired as a SHAPE: `rules` is the entry validateExcludePaths reports for either key.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'rules', 'excludePaths (one flat array)',\n 'Replace the whole { \"rules\": [...], \"guards\": [...] } object with ONE array holding the union of ' +\n 'both lists, de-duplicated — e.g. \"excludePaths\": [\"repositories/**\"]. A path is governed by ' +\n 'webpieces or it is not, so there is no longer a per-engine split. To exclude a path from one rule ' +\n 'only, use that rule\\'s own \"excludePaths\" inside its config block instead.',\n '[excludePaths]', false,\n ),\n\n // --- whole-repo-build-guard, retired as a REPO-CONFIG key one release after it was added. It shipped\n // as a conventional validated guard (mode ON by default, entry REQUIRED under hookGuards), so every\n // consumer that upgraded hit fault Y — every Bash call blocked — for an experimental feature nobody\n // had opted into. The switch now lives ONLY in the optional machine-local ~/.webpieces/config.json,\n // whose absent state is byte-for-byte the old behaviour. A repo that never added the entry has nothing\n // to do; a repo that added it to escape fault Y is told right here to take it back out.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'whole-repo-build-guard', '~/.webpieces/config.json → experimental.whole-repo-build-guard',\n 'DELETE this entry from webpieces.config.json — no repo config key controls this guard any more, ' +\n 'and with the entry gone the guard is silently off for everyone in this repo. To switch it on for ' +\n 'YOUR machine only, put {\"experimental\": {\"whole-repo-build-guard\": true}} in ' +\n '~/.webpieces/config.json — that file is optional, is tracked by no repo, and every webpieces ' +\n 'command behaves exactly as it does by default when it does not exist.',\n '[whole-repo-build-guard]', true,\n ),\n];\n\n/**\n * The shared message. Leads with the retirement, then the destination, then the edit — an agent reading this\n * should not have to infer anything.\n *\n * RETIRED_KEY_MARKER is the phrase the banner classifier keys off to call this error DEFINITIVE (no\n * install can revive a key this table names), so the marker is IMPORTED rather than re-typed here.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyError(entry: RetiredConfigKey): string {\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n // Deletion-only retirements get the mechanical cure named right here, so the reader never has to\n // decide whether removing a key is safe while the guard is denying every Bash call.\n const mechanical = entry.prunable\n ? ` Deleting it is the WHOLE fix — \\`${PRUNE_UNKNOWN_COMMAND}\\` does it for you.`\n : '';\n return `${entry.label} \"${entry.key}\" ${RETIRED_KEY_MARKER}. ${destination} ${entry.instruction}${mechanical}`;\n}\n\n/**\n * The retired entry for a rule/guard NAME, or null. Callers must consult this BEFORE reporting a name as an\n * unknown rule: the generic unknown-rule message can only say \"delete it\", while this table knows WHERE the\n * setting went — a rename that must carry its value over, or a move to `~/.webpieces/config.json`. The\n * destination is the whole product, and a bare \"delete it\" would throw it away.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredRuleFor(ruleName: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_RULE && e.key === ruleName) ?? null;\n}\n\n/**\n * Whether `key` is retired within `label`'s section. Unknown-key rejection consults this so a retired key\n * gets its migration message instead of a generic \"unknown key, delete it\" — the migration instruction is\n * the entire reason this table exists.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function isRetiredKey(key: string, label: string): boolean {\n return RETIRED_CONFIG_KEYS.some(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key);\n}\n\n/**\n * The retired entry for `key` within `label`'s section. For a retirement that is a SHAPE rather than a\n * single key (the excludePaths object), the validator looks the entry up directly so one table row remains\n * the single source of the message.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredEntry(key: string, label: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key) ?? null;\n}\n\n/** Errors for every retired plain key present in `section`. `label` scopes the lookup to that section. */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyErrorsIn(section: Record<string, unknown>, label: string): string[] {\n return RETIRED_CONFIG_KEYS\n .filter(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key in section)\n .map(e => retiredKeyError(e));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"retired-config-keys.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/retired-config-keys.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Retired webpieces.config.json keys — the ONE place in the codebase where a dead config key may be named.\n *\n * ## THE POLICY: webpieces.config.json is never released backwards-compatible\n *\n * When a config key moves, is renamed, or is deleted, the loader REJECTS the old shape with an error that\n * names the destination. It does NOT quietly accept both. There is no fallback, no alias table applied\n * before validation, no \"still accepted for back-compat until every consumer migrates\".\n *\n * This is safe *here* in a way it is not for a normal library, and the reason is the consumer: every reader\n * of this file is a coding agent. The config is validated on startup, the agent is handed the exact edit,\n * and it applies it in one pass — so the upgrade is seamless without shipping a compatibility layer. That\n * makes a hard failure strictly cheaper than duality: two accepted shapes means two code paths, two sets of\n * defaults and two sets of error messages to keep honest forever, and the stale shape then lives in\n * consumer configs indefinitely because nothing ever forces the edit.\n *\n * A hard rejection here is always self-recoverable, which is what makes it safe to do:\n * - editing webpieces.config.json is ALWAYS permitted, even while the config is invalid (see the banner\n * in config-file.ts and the guard matrix — a Write/Edit targeting this file is an unconditional PASS),\n * - and `PRUNE_UNKNOWN_COMMAND` is an L0 CURE, so the mechanical cleanup of keys no validator knows runs\n * from inside the block too.\n * So rejecting an old shape can never wedge a repo. \"It would deadlock the consumer\" is not a reason to add\n * a fallback; it is not true.\n *\n * NOT `pnpm install`. This paragraph used to offer it as the escape hatch that \"fixes the far more common\n * cause of a validation failure: an installed validator lagging the config by a release\". It is permitted\n * (installer bypass), but it is not the cure for anything you can read here: the shim's version-drift guard\n * compares the pin against the installed version and denies every tool call BEFORE the validator runs, so a\n * validation error on screen is proof that package.json and node_modules already agree. The banner from this\n * same package now says \"Do NOT run `pnpm install` — it cannot help\", and a docstring teaching the opposite\n * is how that advice leaked back out.\n *\n * ## Adding an entry\n *\n * When you retire a key, add it here and DELETE its read path — do not leave a `??` fallback behind. The\n * `instruction` is the whole product: it is read by an agent that will act on it verbatim, so give the\n * mechanical edit (\"rename X to Y\", \"move A into B\"), not a description of the change. Saying where a thing\n * moved to is exactly the point of this table; it is what makes the fallback unnecessary.\n *\n * retired-config-keys.spec.ts asserts every entry below actually FAILS the load. That spec is the guard: a\n * future fallback that silently swallows a retired key turns it red.\n */\n\nimport { RETIRED_KEY_MARKER } from './config-error-banner';\nimport { PRUNE_UNKNOWN_COMMAND } from './constants';\n\n// A retired key is matched one of two ways, because config keys live at two very different levels.\n// RULE — a rule/guard NAME, which may sit under either `rules` or `hookGuards`, so it is matched by bare\n// name rather than by a fixed path.\n// KEY — a plain key inside a known section, matched by name within that section.\nexport const RETIRED_SCOPE_RULE = 'rule';\nexport const RETIRED_SCOPE_KEY = 'key';\n\n/** One retired config key and the mechanical edit that replaces it. Data-only (per CLAUDE.md). */\nexport class RetiredConfigKey {\n scope: string;\n // The key exactly as it appears in webpieces.config.json.\n key: string;\n // Where the value goes now. Empty string when the key is deleted outright with no replacement.\n movedTo: string;\n // The imperative fix, written for the agent that will apply it.\n instruction: string;\n // Bracketed label leading the error, matching the `[rule-name]` / `[section]` convention in this package.\n label: string;\n /**\n * True when DELETING the key from webpieces.config.json is the entire edit — nothing in THIS file\n * replaces it, so `PRUNE_UNKNOWN_COMMAND` may strip it mechanically. `whole-repo-build-guard` is the\n * worked example: its switch moved OUT of the repo config into `~/.webpieces/config.json`, so the\n * value has nowhere to go here.\n *\n * False for a rename or an in-file move: deleting those would DISCARD a value the reader still needs,\n * so they keep their migration instruction and the pruner leaves them alone. Required, not defaulted —\n * a defaulted `false` would let a future deletion-only retirement silently opt out of the mechanical\n * cure and land back in \"the reader decides while every Bash call is blocked\".\n */\n prunable: boolean;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(scope: string, key: string, movedTo: string, instruction: string, label: string, prunable: boolean) {\n this.scope = scope;\n this.key = key;\n this.movedTo = movedTo;\n this.instruction = instruction;\n this.label = label;\n this.prunable = prunable;\n }\n}\n\n/**\n * Every retired key. Keep the newest at the bottom with the release that retired it, so the list reads as a\n * changelog an agent can walk when a config is several versions behind.\n */\nexport const RETIRED_CONFIG_KEYS: readonly RetiredConfigKey[] = [\n // --- Guard renames. Previously applied SILENTLY before validation (a DEPRECATED_RULE_ALIASES table in\n // load-config.ts rewrote the key so no validator ever saw it). That hid the rename from the config file\n // forever: the old name kept working, so no consumer ever updated, and the alias table could never be\n // deleted. Now each is a hard error with the new name.\n //\n // These three RE-POINTED when hookGuards collapsed to one key per policy. Their old destinations\n // (`pr-merge-guard`, `pr-creation-or-push-guard`, `read-stale-guard`) are themselves retired now,\n // and a retirement whose `movedTo` names a dead key teaches the removed API — the same defect one\n // level out. Each therefore names the POLICY key it lands on today, in one hop.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-merge-cleanup', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over.',\n '[pr-merge-cleanup]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'pr-creation-guard', 'pr-lifecycle-guard',\n 'Rename the key to \"pr-lifecycle-guard\" (merging its value into that entry if you already have ' +\n 'one — the four PR/merge guards share one key now). Its mode and escape hatches carry over; the ' +\n 'old \"upsertPrCommand\" field does NOT — that string lives only in commands.guardHints.prCreationOrPush.',\n '[pr-creation-guard]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'main-stale-guard', 'branch-state-guard',\n 'Rename the key to \"branch-state-guard\" (merging its value into that entry if you already have ' +\n 'one — the four branch-state guards share one key now). Its mode and escape hatches carry over. ' +\n 'Note the widened scope: that key arms the Write, Read AND Bash halves of the branch-state ' +\n 'policy, not just the Read block this key used to name.',\n '[main-stale-guard]', false,\n ),\n\n // --- The two flat guard-hint strings, superseded by commands.guardHints so that every guard-facing\n // command string sits in one named sub-object instead of loose beside the gate config.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'upsertPr', 'commands.guardHints.prCreationOrPush',\n 'Move the value to \"guardHints\": { \"prCreationOrPush\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"upsertPr\".',\n '[commands]', false,\n ),\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'mergeComplete', 'commands.guardHints.mergeInProgress',\n 'Move the value to \"guardHints\": { \"mergeInProgress\": <value> } inside the same \"commands\" ' +\n 'section, then delete \"mergeComplete\".',\n '[commands]', false,\n ),\n\n // --- The two-list excludePaths object. The split never earned its keep (every consumer set both lists\n // to the same value), and the one case that would need them to differ is served better by a rule's own\n // `excludePaths`. Retired as a SHAPE: `rules` is the entry validateExcludePaths reports for either key.\n new RetiredConfigKey(\n RETIRED_SCOPE_KEY, 'rules', 'excludePaths (one flat array)',\n 'Replace the whole { \"rules\": [...], \"guards\": [...] } object with ONE array holding the union of ' +\n 'both lists, de-duplicated — e.g. \"excludePaths\": [\"repositories/**\"]. A path is governed by ' +\n 'webpieces or it is not, so there is no longer a per-engine split. To exclude a path from one rule ' +\n 'only, use that rule\\'s own \"excludePaths\" inside its config block instead.',\n '[excludePaths]', false,\n ),\n\n // --- whole-repo-build-guard, retired as a REPO-CONFIG key one release after it was added. It shipped\n // as a conventional validated guard (mode ON by default, entry REQUIRED under hookGuards), so every\n // consumer that upgraded hit fault Y — every Bash call blocked — for an experimental feature nobody\n // had opted into. The switch now lives ONLY in the optional machine-local ~/.webpieces/config.json,\n // whose absent state is byte-for-byte the old behaviour. A repo that never added the entry has nothing\n // to do; a repo that added it to escape fault Y is told right here to take it back out.\n new RetiredConfigKey(\n RETIRED_SCOPE_RULE, 'whole-repo-build-guard', '~/.webpieces/config.json → experimental.whole-repo-build-guard',\n 'DELETE this entry from webpieces.config.json — no repo config key controls this guard any more, ' +\n 'and with the entry gone the guard is silently off for everyone in this repo. To switch it on for ' +\n 'YOUR machine only, put {\"experimental\": {\"whole-repo-build-guard\": true}} in ' +\n '~/.webpieces/config.json — that file is optional, is tracked by no repo, and every webpieces ' +\n 'command behaves exactly as it does by default when it does not exist.',\n '[whole-repo-build-guard]', true,\n ),\n\n // --- THE 9 → 3 COLLAPSE of `hookGuards`: one key per POLICY, not one per implementation CLASS.\n //\n // Eight class-named keys become two policy keys. The CLASSES are untouched — `feature-branch-guard`\n // is still the rule name in every decision-log line and every deny report — so nothing an operator\n // greps for moved. What moved is the SWITCH, because nine keys let a consumer configure HALF a\n // policy: `read-stale-guard: OFF` beside `merged-branch-bash-guard: ON` is \"read the file, yes;\n // `cat` the same file, no\", which nobody chose and the config made reachable.\n //\n // EVERY ONE OF THESE IS `prunable: false`, and that is not bookkeeping. `PRUNABLE_SECTIONS` in\n // ConfigPruner includes `hookGuards`, and the validation banner actively RECOMMENDS running the\n // pruner. A `prunable: true` here would mean the recommended cure silently DELETES four configured\n // guards with no destination named — a config loss, which is worse than a block. False keeps the\n // migration instruction and makes the pruner return null for these keys.\n //\n // 4 → 1 is legal for this table (nothing requires `movedTo` to be unique), but it is NOT legal for\n // a naive 1:1 migrator: see migrateRetiredRuleNames in ai-hook-rules' setup.ts, which unions the\n // four old entries into the destination and then fills any missing required field, rather than\n // renaming the first and deleting the rest.\n ...branchStateRetirements(),\n ...prLifecycleRetirements(),\n];\n\n// The four branch-state classes. Split into a helper purely to keep the table above readable; the\n// instruction is per-key because the fields that carry over differ (only feature-branch-guard had\n// `branchNamingConvention`).\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction branchStateRetirements(): RetiredConfigKey[] {\n const merged = 'MERGE it into ONE \"branch-state-guard\" entry under \"hookGuards\"';\n const shared =\n `${merged}. All four of feature-branch-guard, read-stale-guard, stale-main-bash-guard and ` +\n 'merged-branch-bash-guard now read that single entry — they are one policy (\"may I work here, ' +\n 'and is what I read current?\") implemented by four classes, and configuring them separately let ' +\n 'you switch on half of it. Keep \"mode\", \"turnOffRuleUntilEpoch\" and \"turnOffRuleWhileOnBranch\"; ' +\n 'the ONE \"hangTimeoutMinutes\" survives (there was only ever one refresher and one cache, so at ' +\n 'most one of the four values could ever take effect); \"branchNamingConvention\" survives from ' +\n 'feature-branch-guard. If the four disagreed on \"mode\", pick the one you meant — \"ON\" arms the ' +\n 'whole policy including the Read block. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'feature-branch-guard', 'branch-state-guard', shared, '[feature-branch-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'read-stale-guard', 'branch-state-guard', shared, '[read-stale-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'stale-main-bash-guard', 'branch-state-guard', shared, '[stale-main-bash-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merged-branch-bash-guard', 'branch-state-guard', shared, '[merged-branch-bash-guard]', false),\n ];\n}\n\n// The four PR-lifecycle classes.\n// webpieces-disable no-function-outside-class -- table data for RETIRED_CONFIG_KEYS, beside the array it feeds\nfunction prLifecycleRetirements(): RetiredConfigKey[] {\n const shared =\n 'MERGE it into ONE \"pr-lifecycle-guard\" entry under \"hookGuards\". All four of ' +\n 'pr-creation-or-push-guard, merge-in-progress-guard, pr-merge-guard and ' +\n 'redirect-how-to-merge-main now read that single entry — they are one policy (\"do PRs and ' +\n 'merges go through the gated flow?\"). Keep \"mode\", \"turnOffRuleUntilEpoch\" and ' +\n '\"turnOffRuleWhileOnBranch\". Know what \"mode\": \"OFF\" now means: it releases the ' +\n 'unvalidated-merge gate as well as the PR/push blocks, because merge-in-progress-guard is under ' +\n 'this key too. The per-guard \"upsertPrCommand\" / \"mergeCompleteCommand\" fields are gone — those ' +\n 'strings live ONLY in commands.guardHints.prCreationOrPush / .mergeInProgress. Then DELETE this key.';\n return [\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-creation-or-push-guard', 'pr-lifecycle-guard', shared, '[pr-creation-or-push-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'merge-in-progress-guard', 'pr-lifecycle-guard', shared, '[merge-in-progress-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'pr-merge-guard', 'pr-lifecycle-guard', shared, '[pr-merge-guard]', false),\n new RetiredConfigKey(RETIRED_SCOPE_RULE, 'redirect-how-to-merge-main', 'pr-lifecycle-guard', shared, '[redirect-how-to-merge-main]', false),\n ];\n}\n\n/**\n * The shared message. Leads with the retirement, then the destination, then the edit — an agent reading this\n * should not have to infer anything.\n *\n * RETIRED_KEY_MARKER is the phrase the banner classifier keys off to call this error DEFINITIVE (no\n * install can revive a key this table names), so the marker is IMPORTED rather than re-typed here.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyError(entry: RetiredConfigKey): string {\n const destination = entry.movedTo === ''\n ? 'It was removed with no replacement.'\n : `It moved to \"${entry.movedTo}\".`;\n // Deletion-only retirements get the mechanical cure named right here, so the reader never has to\n // decide whether removing a key is safe while the guard is denying every Bash call.\n const mechanical = entry.prunable\n ? ` Deleting it is the WHOLE fix — \\`${PRUNE_UNKNOWN_COMMAND}\\` does it for you.`\n : '';\n return `${entry.label} \"${entry.key}\" ${RETIRED_KEY_MARKER}. ${destination} ${entry.instruction}${mechanical}`;\n}\n\n/**\n * The retired entry for a rule/guard NAME, or null. Callers must consult this BEFORE reporting a name as an\n * unknown rule: the generic unknown-rule message can only say \"delete it\", while this table knows WHERE the\n * setting went — a rename that must carry its value over, or a move to `~/.webpieces/config.json`. The\n * destination is the whole product, and a bare \"delete it\" would throw it away.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredRuleFor(ruleName: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_RULE && e.key === ruleName) ?? null;\n}\n\n/**\n * Whether `key` is retired within `label`'s section. Unknown-key rejection consults this so a retired key\n * gets its migration message instead of a generic \"unknown key, delete it\" — the migration instruction is\n * the entire reason this table exists.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function isRetiredKey(key: string, label: string): boolean {\n return RETIRED_CONFIG_KEYS.some(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key);\n}\n\n/**\n * The retired entry for `key` within `label`'s section. For a retirement that is a SHAPE rather than a\n * single key (the excludePaths object), the validator looks the entry up directly so one table row remains\n * the single source of the message.\n */\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredEntry(key: string, label: string): RetiredConfigKey | null {\n return RETIRED_CONFIG_KEYS.find(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key === key) ?? null;\n}\n\n/** Errors for every retired plain key present in `section`. `label` scopes the lookup to that section. */\n// webpieces-disable no-any-unknown -- `section` is opaque consumer JSON; only key PRESENCE is read\n// webpieces-disable no-function-outside-class -- module-level config validator, matches the rest of this package\nexport function retiredKeyErrorsIn(section: Record<string, unknown>, label: string): string[] {\n return RETIRED_CONFIG_KEYS\n .filter(e => e.scope === RETIRED_SCOPE_KEY && e.label === label && e.key in section)\n .map(e => retiredKeyError(e));\n}\n"]}
|
package/src/rule-configs.d.ts
CHANGED
|
@@ -159,23 +159,38 @@ export declare class BranchCreationGuardConfig extends BaseRuleConfig {
|
|
|
159
159
|
autoReapMergedBranches?: boolean;
|
|
160
160
|
static readonly SCHEMA: SchemaShape<BranchCreationGuardConfig>;
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* `pr-lifecycle-guard` — ONE key, ONE policy: *PRs and merges go through the gated flow.*
|
|
164
|
+
*
|
|
165
|
+
* Four CLASSES implement it, and their names are unchanged (they are the operator identity every
|
|
166
|
+
* decision-log line and every deny report carries):
|
|
167
|
+
*
|
|
168
|
+
* pr-creation-or-push-guard a manual `git push` / `gh pr create|edit` / a raw pulls API call
|
|
169
|
+
* merge-in-progress-guard a PR command while a 3-point merge is half-finished
|
|
170
|
+
* pr-merge-guard a bare `gh pr merge`
|
|
171
|
+
* redirect-how-to-merge-main "how do I get main into my branch?" → the documented flow
|
|
172
|
+
*
|
|
173
|
+
* ## Say this out loud: `"mode": "OFF"` releases the unvalidated-merge gate too
|
|
174
|
+
*
|
|
175
|
+
* Three of the four are pure COMMAND-SHAPE blocks and can never fire spuriously. The fourth,
|
|
176
|
+
* merge-in-progress-guard, is STATE-conditional — it fires only while a 3-point merge is actually in
|
|
177
|
+
* progress, and it is the one state L2 explicitly stands down for. So turning this key OFF to unblock
|
|
178
|
+
* `gh pr merge` ALSO drops the "you have an unfinished merge" gate. That is the honest cost of one key
|
|
179
|
+
* per policy, and it is stated here, in guards/L4-pr-lifecycle.md, and in the error path, rather than
|
|
180
|
+
* being papered over with a granular sub-mode (which would be four knobs wearing one key's name).
|
|
181
|
+
*
|
|
182
|
+
* ## The command strings are NOT here
|
|
183
|
+
*
|
|
184
|
+
* `upsertPrCommand` and `mergeCompleteCommand` used to sit on the two guards, and — being read at the
|
|
185
|
+
* point of use — they BEAT `commands.guardHints`, so `guardHintsWhy`'s claim that renaming a gated
|
|
186
|
+
* command there makes "every guard message follow" was simply false. They are DELETED, not deprecated.
|
|
187
|
+
* `commands.guardHints.prCreationOrPush` / `.mergeInProgress` is the one place those strings live, and
|
|
188
|
+
* the loader hands the resolved values to the two rules directly. A consumer that still sets the old
|
|
189
|
+
* per-guard field gets a RETIRED_FIELD_HINTS error naming the destination.
|
|
190
|
+
*/
|
|
191
|
+
export declare class PrLifecycleGuardConfig extends BaseRuleConfig {
|
|
163
192
|
mode?: OnOffMode;
|
|
164
|
-
|
|
165
|
-
static readonly SCHEMA: SchemaShape<PrCreationOrPushGuardConfig>;
|
|
166
|
-
}
|
|
167
|
-
export declare class MergeInProgressGuardConfig extends BaseRuleConfig {
|
|
168
|
-
mode?: OnOffMode;
|
|
169
|
-
mergeCompleteCommand?: string;
|
|
170
|
-
static readonly SCHEMA: SchemaShape<MergeInProgressGuardConfig>;
|
|
171
|
-
}
|
|
172
|
-
export declare class PrMergeGuardConfig extends BaseRuleConfig {
|
|
173
|
-
mode?: OnOffMode;
|
|
174
|
-
static readonly SCHEMA: SchemaShape<PrMergeGuardConfig>;
|
|
175
|
-
}
|
|
176
|
-
export declare class RedirectHowToMergeMainConfig extends BaseRuleConfig {
|
|
177
|
-
mode?: OnOffMode;
|
|
178
|
-
static readonly SCHEMA: SchemaShape<RedirectHowToMergeMainConfig>;
|
|
193
|
+
static readonly SCHEMA: SchemaShape<PrLifecycleGuardConfig>;
|
|
179
194
|
}
|
|
180
195
|
export declare class NoFileImportCyclesConfig extends BaseRuleConfig {
|
|
181
196
|
mode?: StructuralMode;
|
package/src/rule-configs.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.ValidateTsInSrcConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = void 0;
|
|
3
|
+
exports.ValidateTsInSrcConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.NoJsFilesConfig = exports.MissingDesignAnnotationConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.PrLifecycleGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoSymbolDiTokensConfig = exports.NoCustomCssConfig = 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.BASE_RULE_SCHEMA = exports.BaseRuleConfig = exports.STRUCTURAL_MODES = exports.VALIDATE_TS_MODES = exports.BRANCH_GUARD_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 = void 0;
|
|
5
4
|
const field_def_1 = require("./field-def");
|
|
6
5
|
// Mode const arrays — TypeScript union types derived from them, and FieldDef enum
|
|
7
6
|
// values reference the same array. Impossible for the type and runtime check to diverge.
|
|
@@ -359,42 +358,42 @@ class BranchCreationGuardConfig extends BaseRuleConfig {
|
|
|
359
358
|
};
|
|
360
359
|
}
|
|
361
360
|
exports.BranchCreationGuardConfig = BranchCreationGuardConfig;
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
361
|
+
/**
|
|
362
|
+
* `pr-lifecycle-guard` — ONE key, ONE policy: *PRs and merges go through the gated flow.*
|
|
363
|
+
*
|
|
364
|
+
* Four CLASSES implement it, and their names are unchanged (they are the operator identity every
|
|
365
|
+
* decision-log line and every deny report carries):
|
|
366
|
+
*
|
|
367
|
+
* pr-creation-or-push-guard a manual `git push` / `gh pr create|edit` / a raw pulls API call
|
|
368
|
+
* merge-in-progress-guard a PR command while a 3-point merge is half-finished
|
|
369
|
+
* pr-merge-guard a bare `gh pr merge`
|
|
370
|
+
* redirect-how-to-merge-main "how do I get main into my branch?" → the documented flow
|
|
371
|
+
*
|
|
372
|
+
* ## Say this out loud: `"mode": "OFF"` releases the unvalidated-merge gate too
|
|
373
|
+
*
|
|
374
|
+
* Three of the four are pure COMMAND-SHAPE blocks and can never fire spuriously. The fourth,
|
|
375
|
+
* merge-in-progress-guard, is STATE-conditional — it fires only while a 3-point merge is actually in
|
|
376
|
+
* progress, and it is the one state L2 explicitly stands down for. So turning this key OFF to unblock
|
|
377
|
+
* `gh pr merge` ALSO drops the "you have an unfinished merge" gate. That is the honest cost of one key
|
|
378
|
+
* per policy, and it is stated here, in guards/L4-pr-lifecycle.md, and in the error path, rather than
|
|
379
|
+
* being papered over with a granular sub-mode (which would be four knobs wearing one key's name).
|
|
380
|
+
*
|
|
381
|
+
* ## The command strings are NOT here
|
|
382
|
+
*
|
|
383
|
+
* `upsertPrCommand` and `mergeCompleteCommand` used to sit on the two guards, and — being read at the
|
|
384
|
+
* point of use — they BEAT `commands.guardHints`, so `guardHintsWhy`'s claim that renaming a gated
|
|
385
|
+
* command there makes "every guard message follow" was simply false. They are DELETED, not deprecated.
|
|
386
|
+
* `commands.guardHints.prCreationOrPush` / `.mergeInProgress` is the one place those strings live, and
|
|
387
|
+
* the loader hands the resolved values to the two rules directly. A consumer that still sets the old
|
|
388
|
+
* per-guard field gets a RETIRED_FIELD_HINTS error naming the destination.
|
|
389
|
+
*/
|
|
390
|
+
class PrLifecycleGuardConfig extends BaseRuleConfig {
|
|
366
391
|
static SCHEMA = {
|
|
367
392
|
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
368
|
-
upsertPrCommand: field_def_1.FieldDef.optional('string'),
|
|
369
393
|
...exports.BASE_RULE_SCHEMA,
|
|
370
394
|
};
|
|
371
395
|
}
|
|
372
|
-
exports.
|
|
373
|
-
class MergeInProgressGuardConfig extends BaseRuleConfig {
|
|
374
|
-
// The gated command the guard points agents to in order to finish a 3-point merge. Per-project
|
|
375
|
-
// override; defaults to `commands.mergeComplete` (pnpm wp-finish-upsert-pr) at load time.
|
|
376
|
-
mergeCompleteCommand;
|
|
377
|
-
static SCHEMA = {
|
|
378
|
-
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
379
|
-
mergeCompleteCommand: field_def_1.FieldDef.optional('string'),
|
|
380
|
-
...exports.BASE_RULE_SCHEMA,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
exports.MergeInProgressGuardConfig = MergeInProgressGuardConfig;
|
|
384
|
-
class PrMergeGuardConfig extends BaseRuleConfig {
|
|
385
|
-
static SCHEMA = {
|
|
386
|
-
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
387
|
-
...exports.BASE_RULE_SCHEMA,
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
exports.PrMergeGuardConfig = PrMergeGuardConfig;
|
|
391
|
-
class RedirectHowToMergeMainConfig extends BaseRuleConfig {
|
|
392
|
-
static SCHEMA = {
|
|
393
|
-
mode: new field_def_1.FieldDef('string', exports.ON_OFF_MODES),
|
|
394
|
-
...exports.BASE_RULE_SCHEMA,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
exports.RedirectHowToMergeMainConfig = RedirectHowToMergeMainConfig;
|
|
396
|
+
exports.PrLifecycleGuardConfig = PrLifecycleGuardConfig;
|
|
398
397
|
// NOTE: there is deliberately no `WholeRepoBuildGuardConfig`. That guard is EXPERIMENTAL and has NO
|
|
399
398
|
// webpieces.config.json entry — its only switch is `experimental.whole-repo-build-guard` in the optional
|
|
400
399
|
// machine-local ~/.webpieces/config.json (see home-config.ts), and the affected-build command it prints
|