@webpieces/rules-config 0.4.688 → 0.4.689
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/index.d.ts +2 -2
- package/src/index.js +4 -3
- package/src/index.js.map +1 -1
- package/src/merged-branch-verdicts.d.ts +13 -4
- package/src/merged-branch-verdicts.js +25 -11
- package/src/merged-branch-verdicts.js.map +1 -1
- package/src/merged-branches.d.ts +4 -2
- package/src/merged-branches.js +13 -11
- package/src/merged-branches.js.map +1 -1
- package/src/worktrees.d.ts +28 -0
- package/src/worktrees.js +42 -1
- package/src/worktrees.js.map +1 -1
- package/templates/webpieces.git-workflow.md +25 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/rules-config",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.689",
|
|
4
4
|
"description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
package/src/index.d.ts
CHANGED
|
@@ -69,9 +69,9 @@ export { ReviewProvenanceService, ReviewProvenance, ReviewerTranscript, Reviewer
|
|
|
69
69
|
export { ReviewJson, PrContext, ChecklistResult, ChecklistVerdict, CK_PASS, CK_WARN, CK_OVERRIDDEN, CK_FAIL, CK_MISSING, CK_BAD_FORMAT, VERDICT_GREEN, VERDICT_YELLOW, VERDICT_RED, VERDICT_STATUSES, RequiredChecklist, ChecklistReviewContext, ReviewJsonService, loadReviewJson, prDirFor, reviewJsonPath, reviewJsonSchemaHint, } from './review-json';
|
|
70
70
|
export { MainSyncStatus, MainSyncStatusFile, MainSyncFileStore, PullRequestIndex, MAIN_SYNC_STATUS_VERSION, } from './main-sync-file';
|
|
71
71
|
export { MainSyncLock, MainSyncStatusService, DEFAULT_HANG_TIMEOUT_MINUTES, mainSyncStatusPath, mainSyncLockPath, readMainSyncStatus, readMainSyncStatusFile, writeMainSyncStatus, writeMainSyncStatusFile, computeAllMainSyncStatuses, readMainSyncLock, writeMainSyncLock, isLockStale, isRefreshInProgress, tryAcquireMainSyncLock, inProcessLock, finishedLock, computeMainSyncStatus, stampCleanMainSyncStatus, squashRecoverySteps, } from './main-sync-status';
|
|
72
|
-
export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, MergedBranchesService, CacheFreshness, CACHE_STALE_AFTER_MS, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_BACKUP_OF_LIVE, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED,
|
|
72
|
+
export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, MergedBranchesService, CacheFreshness, CACHE_STALE_AFTER_MS, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_BACKUP_OF_LIVE, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED, ADJUDICATED_CLASSIFICATIONS, } from './merged-branches';
|
|
73
73
|
export { BranchArchiver, ArchiveResult, ARCHIVE_TAG_PREFIX, BRANCH_RETENTIONS, BRANCH_RETENTION_DELETE, BRANCH_RETENTION_ARCHIVE_TAG, BRANCH_RETENTION_KEEP, } from './branch-archiver';
|
|
74
|
-
export { Worktree, WorktreeService, } from './worktrees';
|
|
74
|
+
export { Worktree, WorktreeWorkInFlight, WorktreeService, } from './worktrees';
|
|
75
75
|
export { AgentWorktreeLock, AgentWorktreeLockReader, } from './agent-worktree-lock';
|
|
76
76
|
export { WorktreeLockVerdicts } from './worktree-lock-verdicts';
|
|
77
77
|
export { ReapedBranch, ReapResult, BranchReaper, } from './branch-reaper';
|
package/src/index.js
CHANGED
|
@@ -6,8 +6,8 @@ exports.shouldSkipRule = exports.FieldDef = exports.sectionForRule = exports.isH
|
|
|
6
6
|
exports.InjectAnnotationNotNeededForConcreteClassConfig = exports.NoFunctionOutsideClassConfig = exports.NoProcessExitOutsideMainConfig = exports.NoCustomCssConfig = exports.NoSymbolDiTokensConfig = exports.AngularNoDirectApiInResolverConfig = exports.ThrowCauseRequiredConfig = exports.CatchErrorPatternConfig = exports.NoUnmanagedExceptionsConfig = exports.NoDestructureConfig = exports.PrismaConverterConfig = exports.PrismaValidateDtosConfig = exports.NoImplicitAnyConfig = exports.NoAnyUnknownConfig = exports.NoInlineTypeLiteralsConfig = exports.RequireReturnTypeConfig = exports.MaxFileLinesConfig = exports.MaxMethodLinesConfig = exports.WP_FINISH_PUSH_DEV = exports.WP_PUSH_DEV = exports.WP_FINISH_UPSERT_PR = exports.WP_START_UPSERT_PR = exports.WP_FINISH_UPDATE = exports.WP_START_UPDATE = exports.SyncFlowGuidance = exports.WebpiecesRulesConfig = exports.PRUNE_UNKNOWN_COMMAND = exports.PUSH_DEV_STATE_FILE = exports.MERGE_EXPLANATION_FILE = exports.MERGE_IN_PROGRESS_FILE = exports.PR_REVIEW_DIR = exports.MERGE_INFO_DIR = exports.WEBPIECES_TMP_DIR = exports.hasDisable = exports.RULE_NAMES = exports.WEBPIECES_DISABLE = exports.AbstractRule = exports.ChangedFilesOptions = exports.DiffRange = exports.DiffScope = exports.isNewOrModified = exports.hasChangesInRange = exports.findNewMethodSignaturesInDiff = exports.getChangedLineNumbers = exports.getFileDiff = exports.getChangedFiles = exports.resolveBase = exports.detectBase = exports.SkipRuleResult = exports.getCurrentBranch = void 0;
|
|
7
7
|
exports.MERGE_MODES = exports.MERGE_MODE_NONE = exports.MERGE_MODE_AUTO = exports.buildDevDeployConfig = exports.buildLandPrConfig = exports.buildPrGateConfig = exports.defaultDevDeployConfig = exports.defaultLandPrConfig = exports.defaultPrGateConfig = exports.defaultGates = exports.ReviewContextEntry = exports.DEFAULT_DEV_BRANCH = exports.DEFAULT_DEV_BRANCH_NAMESPACE = exports.DevDeployConfig = exports.LandPrConfig = exports.DEFAULT_BUILD_COMMAND = exports.PrGateConfig = exports.GateDefinition = exports.BranchStateGuardConfig = exports.CLIENT_CREATION_SEVERITIES = exports.NoClientCreationOutsideServerOrClientConfig = exports.VALIDATE_TS_MODES = exports.STRUCTURAL_MODES = exports.ON_OFF_MODES = exports.THROW_CAUSE_MODES = exports.DIRECT_API_RESOLVER_MODES = exports.PRISMA_CONVERTER_MODES = exports.PRISMA_DTOS_MODES = exports.PROJECT_MODES = exports.MODIFIED_CODE_MODES = exports.INLINE_TYPE_MODES = exports.RETURN_TYPE_MODES = exports.FILE_LIMIT_MODES = exports.METHOD_LIMIT_MODES = exports.BaseRuleConfig = exports.ValidateEslintSyncConfig = exports.ValidateVersionsLockedConfig = exports.ValidatePackageJsonConfig = exports.ValidateNoArchitectureCyclesConfig = exports.ValidateArchitectureUnchangedConfig = exports.ValidateTsInSrcConfig = exports.NoJsFilesConfig = exports.DiGraphConfig = exports.NxWiringConfig = exports.RuntimeArchitectureConfig = exports.NoFileImportCyclesConfig = exports.PrLifecycleGuardConfig = exports.BranchCreationGuardConfig = exports.RoleTagConfig = exports.FrameworkTagConfig = void 0;
|
|
8
8
|
exports.loadReviewJson = exports.ReviewJsonService = exports.ChecklistReviewContext = exports.RequiredChecklist = exports.VERDICT_STATUSES = exports.VERDICT_RED = exports.VERDICT_YELLOW = exports.VERDICT_GREEN = exports.CK_BAD_FORMAT = exports.CK_MISSING = exports.CK_FAIL = exports.CK_OVERRIDDEN = exports.CK_WARN = exports.CK_PASS = exports.ChecklistVerdict = exports.ChecklistResult = exports.PrContext = exports.ReviewJson = exports.DEFAULT_RETENTION_DAYS = exports.ProvenanceWriteRequest = exports.OfferedContext = exports.ReviewerPaths = exports.ReviewerTranscript = exports.ReviewProvenance = exports.ReviewProvenanceService = exports.PROVENANCE_SKIPPED = exports.PROVENANCE_MISSING = exports.PROVENANCE_OK = exports.ProvenanceResult = exports.TranscriptScan = exports.ReviewerContext = exports.ReviewerEvidence = exports.SubagentProvenanceService = exports.verifyGateToken = exports.extractGateToken = exports.gateTokenMarker = exports.computeGateToken = exports.GateTokenService = exports.ALL_DIFF_ONE_READ_LINES = exports.READ_TRUNCATION_LINES = exports.ContextEntry = exports.BriefedFile = exports.ReviewerBriefing = exports.ReviewerInstructionsService = exports.ChecklistInstructionsService = exports.ChecklistValidator = exports.formatFileList = exports.normalizeChecklistDoc = exports.toChecklist = exports.ChecklistDefinition = void 0;
|
|
9
|
-
exports.ArchiveResult = exports.BranchArchiver = exports.
|
|
10
|
-
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeLockVerdicts = exports.AgentWorktreeLockReader = exports.AgentWorktreeLock = exports.WorktreeService = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = void 0;
|
|
9
|
+
exports.ArchiveResult = exports.BranchArchiver = exports.ADJUDICATED_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.CACHE_STALE_AFTER_MS = exports.CacheFreshness = exports.MergedBranchesService = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.MergedBranch = exports.squashRecoverySteps = exports.stampCleanMainSyncStatus = exports.computeMainSyncStatus = exports.finishedLock = exports.inProcessLock = exports.tryAcquireMainSyncLock = exports.isRefreshInProgress = exports.isLockStale = exports.writeMainSyncLock = exports.readMainSyncLock = exports.computeAllMainSyncStatuses = exports.writeMainSyncStatusFile = exports.writeMainSyncStatus = exports.readMainSyncStatusFile = exports.readMainSyncStatus = exports.mainSyncLockPath = exports.mainSyncStatusPath = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MainSyncStatusService = exports.MainSyncLock = exports.MAIN_SYNC_STATUS_VERSION = exports.PullRequestIndex = exports.MainSyncFileStore = exports.MainSyncStatusFile = exports.MainSyncStatus = exports.reviewJsonSchemaHint = exports.reviewJsonPath = exports.prDirFor = void 0;
|
|
10
|
+
exports.DEFAULT_MERGE_COMPLETE_COMMAND = exports.DEFAULT_UPSERT_PR_COMMAND = exports.buildCommandsConfig = exports.CommandsConfig = exports.logBranchMutation = exports.branchMutationLogPath = exports.BranchMutationLog = exports.BranchMutationEvent = exports.WorktreeReaper = exports.WorktreeReapResult = exports.ReapedWorktree = exports.BranchReaper = exports.ReapResult = exports.ReapedBranch = exports.WorktreeLockVerdicts = exports.AgentWorktreeLockReader = exports.AgentWorktreeLock = exports.WorktreeService = exports.WorktreeWorkInFlight = exports.Worktree = exports.BRANCH_RETENTION_KEEP = exports.BRANCH_RETENTION_ARCHIVE_TAG = exports.BRANCH_RETENTION_DELETE = exports.BRANCH_RETENTIONS = exports.ARCHIVE_TAG_PREFIX = void 0;
|
|
11
11
|
var types_1 = require("./types");
|
|
12
12
|
Object.defineProperty(exports, "ResolvedConfig", { enumerable: true, get: function () { return types_1.ResolvedConfig; } });
|
|
13
13
|
Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: function () { return types_1.ResolvedRuleConfig; } });
|
|
@@ -462,7 +462,7 @@ Object.defineProperty(exports, "CLASSIFICATION_PRUNABLE", { enumerable: true, ge
|
|
|
462
462
|
Object.defineProperty(exports, "CLASSIFICATION_LOCKED", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_LOCKED; } });
|
|
463
463
|
Object.defineProperty(exports, "CLASSIFICATION_CURRENT", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_CURRENT; } });
|
|
464
464
|
Object.defineProperty(exports, "CLASSIFICATION_DETACHED", { enumerable: true, get: function () { return merged_branches_1.CLASSIFICATION_DETACHED; } });
|
|
465
|
-
Object.defineProperty(exports, "
|
|
465
|
+
Object.defineProperty(exports, "ADJUDICATED_CLASSIFICATIONS", { enumerable: true, get: function () { return merged_branches_1.ADJUDICATED_CLASSIFICATIONS; } });
|
|
466
466
|
var branch_archiver_1 = require("./branch-archiver");
|
|
467
467
|
Object.defineProperty(exports, "BranchArchiver", { enumerable: true, get: function () { return branch_archiver_1.BranchArchiver; } });
|
|
468
468
|
Object.defineProperty(exports, "ArchiveResult", { enumerable: true, get: function () { return branch_archiver_1.ArchiveResult; } });
|
|
@@ -473,6 +473,7 @@ Object.defineProperty(exports, "BRANCH_RETENTION_ARCHIVE_TAG", { enumerable: tru
|
|
|
473
473
|
Object.defineProperty(exports, "BRANCH_RETENTION_KEEP", { enumerable: true, get: function () { return branch_archiver_1.BRANCH_RETENTION_KEEP; } });
|
|
474
474
|
var worktrees_1 = require("./worktrees");
|
|
475
475
|
Object.defineProperty(exports, "Worktree", { enumerable: true, get: function () { return worktrees_1.Worktree; } });
|
|
476
|
+
Object.defineProperty(exports, "WorktreeWorkInFlight", { enumerable: true, get: function () { return worktrees_1.WorktreeWorkInFlight; } });
|
|
476
477
|
Object.defineProperty(exports, "WorktreeService", { enumerable: true, get: function () { return worktrees_1.WorktreeService; } });
|
|
477
478
|
var agent_worktree_lock_1 = require("./agent-worktree-lock");
|
|
478
479
|
Object.defineProperty(exports, "AgentWorktreeLock", { enumerable: true, get: function () { return agent_worktree_lock_1.AgentWorktreeLock; } });
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError, renderRuleFailForAi, renderRuleFailForHuman } from './rule-fail-error';\n// THE one representation of a cure, shared by RuleFailError (build-time) and FixHint (edit-time), plus\n// the one renderer that owns the \"Fix Option N:\" numbering and the \"(preferred)\" tag.\nexport { Option, formatFixOptions } from './fix-option';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliFlag, CliArgSet, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\n// The validation-failure banner: ONE cure (edit the file), plus the marker phrases the validator\n// messages embed rather than re-type.\nexport {\n formatConfigErrorsBanner,\n CONFIG_POLICY_DOC,\n RETIRED_KEY_MARKER,\n RETIRED_TOP_LEVEL_MARKER,\n SECTION_PLACEMENT_MARKER,\n} from './config-error-banner';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile, ConfigParseAttempt, CONFIG_PARSE_ATTEMPTS, CONFIG_PARSE_RETRY_MILLIS } from './config-file';\n// The PARSED-BUT-UNVALIDATED config shape. Exported for readers that walk the file generically rather\n// than through the typed config (the pr-gate active-hatch dashboard section reads every rule's hatches).\nexport type { RawConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR, INSTRUCT_AI_LEAF } from './repo-root';\n// The scoped `.webpieces` resolver. EVERY reader/writer of `.webpieces/...` goes through one of its two\n// named methods so the call site declares whether the state is repo-wide or worktree-private.\nexport { DotWebpieces, dotWebpieces, GitDirs, WORKTREE_STATE_DIR, LOGS_STATE_DIR } from './state-dir';\nexport { StateDirMigrator, StateMigrationReport } from './state-dir-migration';\n// There is NO machine-global state root. `MachineStateHome`/`StateHome`/`WEBPIECES_STATE_HOME` and the\n// `PrBodyStore` that used them are DELETED: the one artifact that needed a scope above the clone was the\n// gated squash body, and GitHub holds it now (it IS the PR description). Every `.webpieces` path a\n// webpieces tool writes is `{repo}/.webpieces`, resolved through `DotWebpieces` above. See\n// `decisions/0005-the-pr-description-is-the-merge-body.md`.\nexport { AgedTreeSweeper, SweepCount, RETENTION_DAYS } from './aged-tree-sweep';\nexport { ClaudeEnv, claudeEnv, CLAUDE_PROJECT_DIR_ENV, CLAUDE_PROJECT_DIR_UNSET } from './claude-env';\nexport { AtomicFile } from './atomic-file';\n// The ONE formatter for a remedy that must run in a named directory: `cd '<root>' && <command>`.\n// Single-quoted so a repo path containing a space is still runnable (and still un-smuggleable).\nexport { atRoot } from './at-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths, isWebpiecesStateDir } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\n// THE one `no-custom-css` path exemption. Both engines that enforce the rule (the edit-time hook and the\n// CI validator) consult this class, so `allowGlobs` cannot be honoured by one half and ignored by the other.\nexport { NoCustomCssScope } from './no-custom-css-scope';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\n// The instruct-ai docs are delivered as a SET: writing one writes the transitive closure of the docs it\n// links to, so a doc and everything it points at always land together. See instruct-ai-docs.ts.\nexport { InstructAiDoc, InstructAiDocSet, MergeProcessDoc } from './instruct-ai-docs';\nexport { MERGE_PROCESS_DOC, MergeProcessText, MergeRun, ReferenceMergeRun } from './merge-process-doc';\nexport { BUILD_LOG_DOC } from './build-log-doc';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateExcludePaths, validateMatchRulesSection, allRuleNames, recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './validate-config';\nexport { validateCommandsSection } from './commands-section-validators';\nexport { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';\n// The retired-key table + the no-back-compat policy it enforces. Exported so the installer can migrate\n// what the errors instruct, and so consumers can enumerate retirements.\nexport { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';\n// The MECHANICAL cure the unknown-rule error and the banner both name: strip every key no validator has a\n// schema for, so cleanliness is one command rather than a judgement call made while every Bash call is\n// blocked. `PRUNE_UNKNOWN_COMMAND` (constants.ts) is the single spelling of that command.\nexport { ConfigPruner, PruneResult, PrunedKey } from './config-pruner';\nexport { validateChecklistDocs } from './checklist-docs-validator';\n// The OPTIONAL machine-local `~/.webpieces/config.json`: absent (the normal state for every consumer) means\n// each key's declared default, silently; present means STRICT about what it understands and FORWARD-COMPATIBLE\n// about what it does not. A retired key (its own retirement table), a known key of the wrong TYPE and an\n// unparseable document all REJECT; a key this release simply does not recognise is IGNORED with a warning,\n// because the file is machine-global and the repos reading it pin different releases — rejecting a newer\n// release's key would hard-block every repo on the machine that is not yet on it. See home-config.ts.\n// `isHomeConfigPath` is what grants the file its unconditional Write/Edit PASS in the hook guards, which is\n// what keeps a rejection repairable. Every `experimental.*` key is an OPT-IN that defaults OFF, including\n// `whole-repo-build-guard`: ON requires an explicit `true`, and there is no per-key default to export.\nexport {\n HomeConfig, HomeConfigService, RetiredHomeConfigKey, RETIRED_HOME_CONFIG_KEYS,\n HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_EXPERIMENTAL_SECTION, HOME_KEY_BUILD_GATE_LOG_CAPTURE,\n HOME_KEY_ORPHAN_DIR_SWEEP, HOME_KEY_WHOLE_REPO_BUILD_GUARD, HOME_KEY_MAX_CONCURRENT_BUILDS,\n DEFAULT_MAX_CONCURRENT_BUILDS,\n} from './home-config';\n// The MACHINE-WIDE build ledger, `~/.webpieces/builds.log`. THE one carve-out from\n// `no-machine-global-state.spec.ts`'s rule that webpieces writes only under `{repo}/.webpieces` — the\n// argument is in `decisions/0006-the-build-ledger-is-machine-global.md`, and the short form is that \"how\n// many builds are burning this box's CPU\" is a fact about the MACHINE, is not a cache of anything a\n// remote owns, and cannot be answered from a per-repo file because every linked worktree has its own\n// `.webpieces/` and would be blind to the sibling it is contending with. See builds-log.ts.\nexport {\n BuildsLog, BuildTicket, RunningBuild,\n BUILDS_LOG_FILE, BUILDS_LOCK_FILE, BUILD_START, BUILD_DONE_SUCCESS, BUILD_DONE_FAIL,\n MAX_BUILDS_LOG_BYTES, BUILDS_LOG_GENERATIONS, MAX_ROW_BYTES,\n} from './builds-log';\n// The orphan-directory sweep: the corpse an `nx g move` leaves on every clone, which git cannot remove\n// because an ignored dist/ or node_modules/ outlives every tracked file under it. See orphan-dir-scan.ts\n// for why the predicate is git's own `clean -Xdn` answer rather than a hand-rolled ignore walk.\nexport { OrphanDirScanner, OrphanCandidate } from './orphan-dir-scan';\nexport {\n OrphanDirArchiver, ArchivedOrphan, FailedOrphan, OrphanSweepResult, OrphanSweepManifest,\n TRASH_STATE_DIR, TRASH_MANIFEST_FILE, TRASH_RETENTION_DAYS,\n} from './orphan-dir-archive';\nexport { OrphanDirSweeper, OrphanSweepReport } from './orphan-dir-sweep';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { schemaFieldNames } from './rule-schemas';\nexport { HOOK_GUARD_NAMES, BRANCH_STATE_GUARD_KEY, PR_LIFECYCLE_GUARD_KEY, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n PUSH_DEV_STATE_FILE,\n PRUNE_UNKNOWN_COMMAND,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n WP_PUSH_DEV,\n WP_FINISH_PUSH_DEV,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n BranchStateGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n DEFAULT_BUILD_COMMAND,\n LandPrConfig,\n DevDeployConfig,\n DEFAULT_DEV_BRANCH_NAMESPACE,\n DEFAULT_DEV_BRANCH,\n ReviewContextEntry,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n defaultDevDeployConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n buildDevDeployConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n ReviewerInstructionsService,\n ReviewerBriefing,\n BriefedFile,\n ContextEntry,\n READ_TRUNCATION_LINES,\n ALL_DIFF_ONE_READ_LINES,\n} from './reviewer-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ReviewerEvidence,\n ReviewerContext,\n TranscriptScan,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewProvenanceService,\n ReviewProvenance,\n ReviewerTranscript,\n ReviewerPaths,\n OfferedContext,\n ProvenanceWriteRequest,\n DEFAULT_RETENTION_DAYS,\n} from './review-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncStatusFile,\n MainSyncFileStore,\n PullRequestIndex,\n MAIN_SYNC_STATUS_VERSION,\n} from './main-sync-file';\nexport {\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n readMainSyncStatusFile,\n writeMainSyncStatus,\n writeMainSyncStatusFile,\n computeAllMainSyncStatuses,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeService,\n} from './worktrees';\nexport {\n AgentWorktreeLock,\n AgentWorktreeLockReader,\n} from './agent-worktree-lock';\nexport { WorktreeLockVerdicts } from './worktree-lock-verdicts';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport {\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n} from './worktree-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["export { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nexport { InformAiError } from './inform-ai-error';\nexport { RuleFailError, renderRuleFailForAi, renderRuleFailForHuman } from './rule-fail-error';\n// THE one representation of a cure, shared by RuleFailError (build-time) and FixHint (edit-time), plus\n// the one renderer that owns the \"Fix Option N:\" numbering and the \"(preferred)\" tag.\nexport { Option, formatFixOptions } from './fix-option';\nexport { CliExitError } from './cli-exit-error';\nexport { CliUsage, CliFlag, CliArgSet, CliArgsCheck, CliArgs } from './cli-args';\nexport { runMain } from './run-main';\nexport { toError } from './to-error';\nexport { loadAndValidate, LoadedConfig, ConfigLoader } from './load-config';\n// The validation-failure banner: ONE cure (edit the file), plus the marker phrases the validator\n// messages embed rather than re-type.\nexport {\n formatConfigErrorsBanner,\n CONFIG_POLICY_DOC,\n RETIRED_KEY_MARKER,\n RETIRED_TOP_LEVEL_MARKER,\n SECTION_PLACEMENT_MARKER,\n} from './config-error-banner';\nexport { findConfigFile, CONFIG_FILENAME, ConfigFile, ConfigParseAttempt, CONFIG_PARSE_ATTEMPTS, CONFIG_PARSE_RETRY_MILLIS } from './config-file';\n// The PARSED-BUT-UNVALIDATED config shape. Exported for readers that walk the file generically rather\n// than through the typed config (the pr-gate active-hatch dashboard section reads every rule's hatches).\nexport type { RawConfigFile } from './config-file';\nexport { RepoRootFinder, INSTRUCT_AI_DIR, INSTRUCT_AI_LEAF } from './repo-root';\n// The scoped `.webpieces` resolver. EVERY reader/writer of `.webpieces/...` goes through one of its two\n// named methods so the call site declares whether the state is repo-wide or worktree-private.\nexport { DotWebpieces, dotWebpieces, GitDirs, WORKTREE_STATE_DIR, LOGS_STATE_DIR } from './state-dir';\nexport { StateDirMigrator, StateMigrationReport } from './state-dir-migration';\n// There is NO machine-global state root. `MachineStateHome`/`StateHome`/`WEBPIECES_STATE_HOME` and the\n// `PrBodyStore` that used them are DELETED: the one artifact that needed a scope above the clone was the\n// gated squash body, and GitHub holds it now (it IS the PR description). Every `.webpieces` path a\n// webpieces tool writes is `{repo}/.webpieces`, resolved through `DotWebpieces` above. See\n// `decisions/0005-the-pr-description-is-the-merge-body.md`.\nexport { AgedTreeSweeper, SweepCount, RETENTION_DAYS } from './aged-tree-sweep';\nexport { ClaudeEnv, claudeEnv, CLAUDE_PROJECT_DIR_ENV, CLAUDE_PROJECT_DIR_UNSET } from './claude-env';\nexport { AtomicFile } from './atomic-file';\n// The ONE formatter for a remedy that must run in a named directory: `cd '<root>' && <command>`.\n// Single-quoted so a repo path containing a space is still runnable (and still un-smuggleable).\nexport { atRoot } from './at-root';\nexport { RulesConfigDesign } from './rules-config-design';\nexport { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './di';\nexport { ExcludePaths, isWebpiecesStateDir } from './exclude-hook-paths';\nexport { isPathExcluded, matchesAnyGlob } from './exclude-paths';\n// THE one `no-custom-css` path exemption. Both engines that enforce the rule (the edit-time hook and the\n// CI validator) consult this class, so `allowGlobs` cannot be honoured by one half and ignored by the other.\nexport { NoCustomCssScope } from './no-custom-css-scope';\nexport { defaultRules, defaultRulesDir } from './default-rules';\nexport { loadTemplate, writeTemplateIfMissing, writeTemplate, TemplateWriter } from './load-template';\n// The instruct-ai docs are delivered as a SET: writing one writes the transitive closure of the docs it\n// links to, so a doc and everything it points at always land together. See instruct-ai-docs.ts.\nexport { InstructAiDoc, InstructAiDocSet, MergeProcessDoc } from './instruct-ai-docs';\nexport { MERGE_PROCESS_DOC, MergeProcessText, MergeRun, ReferenceMergeRun } from './merge-process-doc';\nexport { BUILD_LOG_DOC } from './build-log-doc';\nexport { validateWebpiecesConfig, validatePrGateSection, validateChecklistsSection, validateSectionPlacement, validateExcludePaths, validateMatchRulesSection, allRuleNames, recommendedSeedMode, recommendedSeedModeFor, seedEntryForRule } from './validate-config';\nexport { validateCommandsSection } from './commands-section-validators';\nexport { unknownKeyErrors, isCommentKey, validateTopLevelKeys, COMMENT_KEY_SUFFIX } from './config-key-rules';\n// The retired-key table + the no-back-compat policy it enforces. Exported so the installer can migrate\n// what the errors instruct, and so consumers can enumerate retirements.\nexport { RETIRED_CONFIG_KEYS, RETIRED_SCOPE_KEY, RETIRED_SCOPE_RULE, RetiredConfigKey, isRetiredKey, retiredEntry, retiredKeyError, retiredKeyErrorsIn, retiredRuleFor } from './retired-config-keys';\n// The MECHANICAL cure the unknown-rule error and the banner both name: strip every key no validator has a\n// schema for, so cleanliness is one command rather than a judgement call made while every Bash call is\n// blocked. `PRUNE_UNKNOWN_COMMAND` (constants.ts) is the single spelling of that command.\nexport { ConfigPruner, PruneResult, PrunedKey } from './config-pruner';\nexport { validateChecklistDocs } from './checklist-docs-validator';\n// The OPTIONAL machine-local `~/.webpieces/config.json`: absent (the normal state for every consumer) means\n// each key's declared default, silently; present means STRICT about what it understands and FORWARD-COMPATIBLE\n// about what it does not. A retired key (its own retirement table), a known key of the wrong TYPE and an\n// unparseable document all REJECT; a key this release simply does not recognise is IGNORED with a warning,\n// because the file is machine-global and the repos reading it pin different releases — rejecting a newer\n// release's key would hard-block every repo on the machine that is not yet on it. See home-config.ts.\n// `isHomeConfigPath` is what grants the file its unconditional Write/Edit PASS in the hook guards, which is\n// what keeps a rejection repairable. Every `experimental.*` key is an OPT-IN that defaults OFF, including\n// `whole-repo-build-guard`: ON requires an explicit `true`, and there is no per-key default to export.\nexport {\n HomeConfig, HomeConfigService, RetiredHomeConfigKey, RETIRED_HOME_CONFIG_KEYS,\n HOME_CONFIG_DIR, HOME_CONFIG_FILE, HOME_EXPERIMENTAL_SECTION, HOME_KEY_BUILD_GATE_LOG_CAPTURE,\n HOME_KEY_ORPHAN_DIR_SWEEP, HOME_KEY_WHOLE_REPO_BUILD_GUARD, HOME_KEY_MAX_CONCURRENT_BUILDS,\n DEFAULT_MAX_CONCURRENT_BUILDS,\n} from './home-config';\n// The MACHINE-WIDE build ledger, `~/.webpieces/builds.log`. THE one carve-out from\n// `no-machine-global-state.spec.ts`'s rule that webpieces writes only under `{repo}/.webpieces` — the\n// argument is in `decisions/0006-the-build-ledger-is-machine-global.md`, and the short form is that \"how\n// many builds are burning this box's CPU\" is a fact about the MACHINE, is not a cache of anything a\n// remote owns, and cannot be answered from a per-repo file because every linked worktree has its own\n// `.webpieces/` and would be blind to the sibling it is contending with. See builds-log.ts.\nexport {\n BuildsLog, BuildTicket, RunningBuild,\n BUILDS_LOG_FILE, BUILDS_LOCK_FILE, BUILD_START, BUILD_DONE_SUCCESS, BUILD_DONE_FAIL,\n MAX_BUILDS_LOG_BYTES, BUILDS_LOG_GENERATIONS, MAX_ROW_BYTES,\n} from './builds-log';\n// The orphan-directory sweep: the corpse an `nx g move` leaves on every clone, which git cannot remove\n// because an ignored dist/ or node_modules/ outlives every tracked file under it. See orphan-dir-scan.ts\n// for why the predicate is git's own `clean -Xdn` answer rather than a hand-rolled ignore walk.\nexport { OrphanDirScanner, OrphanCandidate } from './orphan-dir-scan';\nexport {\n OrphanDirArchiver, ArchivedOrphan, FailedOrphan, OrphanSweepResult, OrphanSweepManifest,\n TRASH_STATE_DIR, TRASH_MANIFEST_FILE, TRASH_RETENTION_DAYS,\n} from './orphan-dir-archive';\nexport { OrphanDirSweeper, OrphanSweepReport } from './orphan-dir-sweep';\nexport {\n MatchRuleConfig,\n MatchRuleViolation,\n findMatchRuleViolations,\n isMatchRuleAllowedPath,\n compileMatchRulePatterns,\n renderMatchRuleMessage,\n DEFAULT_MATCH_RULES,\n} from './match-rules-config';\nexport type { ConfigSection } from './sections';\nexport { schemaFieldNames } from './rule-schemas';\nexport { HOOK_GUARD_NAMES, BRANCH_STATE_GUARD_KEY, PR_LIFECYCLE_GUARD_KEY, isHookGuard, sectionForRule } from './sections';\nexport { FieldDef } from './field-def';\nexport type { SchemaShape } from './field-def';\nexport { shouldSkipRule, getCurrentBranch } from './skip-rule';\nexport { SkipRuleResult } from './skip-rule';\nexport {\n detectBase,\n resolveBase,\n getChangedFiles,\n getFileDiff,\n getChangedLineNumbers,\n findNewMethodSignaturesInDiff,\n hasChangesInRange,\n isNewOrModified,\n DiffScope,\n DiffRange,\n ChangedFilesOptions,\n} from './diff-scope';\nexport { AbstractRule } from './abstract-rule';\nexport {\n WEBPIECES_DISABLE,\n RULE_NAMES,\n hasDisable,\n WEBPIECES_TMP_DIR,\n MERGE_INFO_DIR,\n PR_REVIEW_DIR,\n MERGE_IN_PROGRESS_FILE,\n MERGE_EXPLANATION_FILE,\n PUSH_DEV_STATE_FILE,\n PRUNE_UNKNOWN_COMMAND,\n} from './constants';\nexport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\nexport {\n SyncFlowGuidance,\n WP_START_UPDATE,\n WP_FINISH_UPDATE,\n WP_START_UPSERT_PR,\n WP_FINISH_UPSERT_PR,\n WP_PUSH_DEV,\n WP_FINISH_PUSH_DEV,\n} from './sync-flow-guidance';\nexport {\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n NoUnmanagedExceptionsConfig,\n CatchErrorPatternConfig,\n ThrowCauseRequiredConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n NoCustomCssConfig,\n NoProcessExitOutsideMainConfig,\n NoFunctionOutsideClassConfig,\n InjectAnnotationNotNeededForConcreteClassConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n BranchCreationGuardConfig,\n PrLifecycleGuardConfig,\n NoFileImportCyclesConfig,\n RuntimeArchitectureConfig,\n NxWiringConfig,\n DiGraphConfig,\n NoJsFilesConfig,\n ValidateTsInSrcConfig,\n ValidateArchitectureUnchangedConfig,\n ValidateNoArchitectureCyclesConfig,\n ValidatePackageJsonConfig,\n ValidateVersionsLockedConfig,\n ValidateEslintSyncConfig,\n BaseRuleConfig,\n} from './rule-configs';\n// Mode unions + their value arrays — the single source of truth shared with code-rules.\nexport {\n METHOD_LIMIT_MODES,\n FILE_LIMIT_MODES,\n RETURN_TYPE_MODES,\n INLINE_TYPE_MODES,\n MODIFIED_CODE_MODES,\n PROJECT_MODES,\n PRISMA_DTOS_MODES,\n PRISMA_CONVERTER_MODES,\n DIRECT_API_RESOLVER_MODES,\n THROW_CAUSE_MODES,\n ON_OFF_MODES,\n STRUCTURAL_MODES,\n VALIDATE_TS_MODES,\n} from './rule-configs';\nexport {\n NoClientCreationOutsideServerOrClientConfig,\n CLIENT_CREATION_SEVERITIES,\n} from './no-client-creation-config';\nexport type { ClientCreationSeverity } from './no-client-creation-config';\nexport type {\n MethodLimitMode,\n FileLimitMode,\n ReturnTypeMode,\n InlineTypeMode,\n ModifiedCodeMode,\n ProjectMode,\n PrismaValidateDtosMode,\n PrismaConverterMode,\n DirectApiResolverMode,\n ThrowCauseMode,\n OnOffMode,\n StructuralMode,\n ValidateTsMode,\n} from './rule-configs';\nexport {\n BranchStateGuardConfig,\n} from './main-sync-guard-configs';\nexport {\n GateDefinition,\n PrGateConfig,\n DEFAULT_BUILD_COMMAND,\n LandPrConfig,\n DevDeployConfig,\n DEFAULT_DEV_BRANCH_NAMESPACE,\n DEFAULT_DEV_BRANCH,\n ReviewContextEntry,\n defaultGates,\n defaultPrGateConfig,\n defaultLandPrConfig,\n defaultDevDeployConfig,\n buildPrGateConfig,\n buildLandPrConfig,\n buildDevDeployConfig,\n MERGE_MODE_AUTO,\n MERGE_MODE_NONE,\n MERGE_MODES,\n} from './pr-gate-config';\nexport {\n ChecklistDefinition,\n toChecklist,\n normalizeChecklistDoc,\n formatFileList,\n} from './checklist-config';\nexport type { RawChecklistItem } from './checklist-config';\nexport { ChecklistValidator } from './checklist-validator';\nexport { ChecklistInstructionsService } from './checklist-instructions';\nexport {\n ReviewerInstructionsService,\n ReviewerBriefing,\n BriefedFile,\n ContextEntry,\n READ_TRUNCATION_LINES,\n ALL_DIFF_ONE_READ_LINES,\n} from './reviewer-instructions';\nexport {\n GateTokenService,\n computeGateToken,\n gateTokenMarker,\n extractGateToken,\n verifyGateToken,\n} from './gate-token';\nexport {\n SubagentProvenanceService,\n ReviewerEvidence,\n ReviewerContext,\n TranscriptScan,\n ProvenanceResult,\n PROVENANCE_OK,\n PROVENANCE_MISSING,\n PROVENANCE_SKIPPED,\n} from './subagent-provenance';\nexport {\n ReviewProvenanceService,\n ReviewProvenance,\n ReviewerTranscript,\n ReviewerPaths,\n OfferedContext,\n ProvenanceWriteRequest,\n DEFAULT_RETENTION_DAYS,\n} from './review-provenance';\nexport {\n ReviewJson,\n PrContext,\n ChecklistResult,\n ChecklistVerdict,\n CK_PASS,\n CK_WARN,\n CK_OVERRIDDEN,\n CK_FAIL,\n CK_MISSING,\n CK_BAD_FORMAT,\n VERDICT_GREEN,\n VERDICT_YELLOW,\n VERDICT_RED,\n VERDICT_STATUSES,\n RequiredChecklist,\n ChecklistReviewContext,\n ReviewJsonService,\n loadReviewJson,\n prDirFor,\n reviewJsonPath,\n reviewJsonSchemaHint,\n} from './review-json';\nexport {\n MainSyncStatus,\n MainSyncStatusFile,\n MainSyncFileStore,\n PullRequestIndex,\n MAIN_SYNC_STATUS_VERSION,\n} from './main-sync-file';\nexport {\n MainSyncLock,\n MainSyncStatusService,\n DEFAULT_HANG_TIMEOUT_MINUTES,\n mainSyncStatusPath,\n mainSyncLockPath,\n readMainSyncStatus,\n readMainSyncStatusFile,\n writeMainSyncStatus,\n writeMainSyncStatusFile,\n computeAllMainSyncStatuses,\n readMainSyncLock,\n writeMainSyncLock,\n isLockStale,\n isRefreshInProgress,\n tryAcquireMainSyncLock,\n inProcessLock,\n finishedLock,\n computeMainSyncStatus,\n stampCleanMainSyncStatus,\n squashRecoverySteps,\n} from './main-sync-status';\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n MergedBranchesService,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n ADJUDICATED_CLASSIFICATIONS,\n} from './merged-branches';\nexport {\n BranchArchiver,\n ArchiveResult,\n ARCHIVE_TAG_PREFIX,\n BRANCH_RETENTIONS,\n BRANCH_RETENTION_DELETE,\n BRANCH_RETENTION_ARCHIVE_TAG,\n BRANCH_RETENTION_KEEP,\n} from './branch-archiver';\nexport {\n Worktree,\n WorktreeWorkInFlight,\n WorktreeService,\n} from './worktrees';\nexport {\n AgentWorktreeLock,\n AgentWorktreeLockReader,\n} from './agent-worktree-lock';\nexport { WorktreeLockVerdicts } from './worktree-lock-verdicts';\nexport {\n ReapedBranch,\n ReapResult,\n BranchReaper,\n} from './branch-reaper';\nexport {\n ReapedWorktree,\n WorktreeReapResult,\n WorktreeReaper,\n} from './worktree-reaper';\nexport type { MutationVerb, MutationPhase } from './branch-mutation-log';\nexport {\n BranchMutationEvent,\n BranchMutationLog,\n branchMutationLogPath,\n logBranchMutation,\n} from './branch-mutation-log';\nexport {\n CommandsConfig,\n buildCommandsConfig,\n DEFAULT_UPSERT_PR_COMMAND,\n DEFAULT_MERGE_COMPLETE_COMMAND,\n} from './commands-config';\n"]}
|
|
@@ -46,9 +46,18 @@ export declare const CLASSIFICATION_BACKUP_OF_LIVE = "backup-of-live";
|
|
|
46
46
|
* in it. Observed 2026-07-30: three worktrees with live agents in them were listed as dead, under
|
|
47
47
|
* the sentence "so no work can be lost".
|
|
48
48
|
*
|
|
49
|
-
* Liveness is
|
|
50
|
-
*
|
|
51
|
-
*
|
|
49
|
+
* Liveness for anything holding COMMITS is the same single rule branches already use: reapable == a
|
|
50
|
+
* MERGED PR, and anything short of that is never deleted without an explicit answer.
|
|
51
|
+
*
|
|
52
|
+
* THIS verdict is the exception, and it is not the old auto-delete coming back. wp-cleanup REAPS a
|
|
53
|
+
* zero-commit ref by default — after checking the thing the 2026-07-30 incident actually turned on:
|
|
54
|
+
* whether somebody is HOLDING it. A worktree with uncommitted or untracked files
|
|
55
|
+
* (`WorktreeService.workInFlight`), one LOCKED by a live agent, the tree you are standing in
|
|
56
|
+
* and a detached HEAD are all spared, each with a stated reason. Everything else with zero commits is
|
|
57
|
+
* a husk, and deleting a husk loses a NAME, not a commit — which the archive tag then hands back.
|
|
58
|
+
*
|
|
59
|
+
* So the bar is "prove somebody is holding it", not "prove it is dead". The 2026-07-30 loss was
|
|
60
|
+
* three LIVE worktrees called dead with no such check; the check is now what stands between them.
|
|
52
61
|
*/
|
|
53
62
|
export declare const CLASSIFICATION_NO_COMMITS = "no-commits";
|
|
54
63
|
export declare const CLASSIFICATION_SUPERSEDED = "superseded";
|
|
@@ -72,7 +81,7 @@ export declare const CLASSIFICATION_PRUNABLE = "prunable-worktree";
|
|
|
72
81
|
export declare const CLASSIFICATION_LOCKED = "locked-worktree";
|
|
73
82
|
export declare const CLASSIFICATION_CURRENT = "current-worktree";
|
|
74
83
|
export declare const CLASSIFICATION_DETACHED = "detached-worktree";
|
|
75
|
-
export declare const
|
|
84
|
+
export declare const ADJUDICATED_CLASSIFICATIONS: readonly string[];
|
|
76
85
|
/**
|
|
77
86
|
* A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).
|
|
78
87
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Re-exported from merged-branches.ts, so every existing import path keeps working.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.CacheFreshness = exports.CACHE_STALE_AFTER_MS = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.
|
|
10
|
+
exports.CacheFreshness = exports.CACHE_STALE_AFTER_MS = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.ADJUDICATED_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.MergedBranch = void 0;
|
|
11
11
|
// Data-only (per CLAUDE.md, classes for data).
|
|
12
12
|
class MergedBranch {
|
|
13
13
|
branch;
|
|
@@ -56,9 +56,18 @@ exports.CLASSIFICATION_BACKUP_OF_LIVE = 'backup-of-live';
|
|
|
56
56
|
* in it. Observed 2026-07-30: three worktrees with live agents in them were listed as dead, under
|
|
57
57
|
* the sentence "so no work can be lost".
|
|
58
58
|
*
|
|
59
|
-
* Liveness is
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* Liveness for anything holding COMMITS is the same single rule branches already use: reapable == a
|
|
60
|
+
* MERGED PR, and anything short of that is never deleted without an explicit answer.
|
|
61
|
+
*
|
|
62
|
+
* THIS verdict is the exception, and it is not the old auto-delete coming back. wp-cleanup REAPS a
|
|
63
|
+
* zero-commit ref by default — after checking the thing the 2026-07-30 incident actually turned on:
|
|
64
|
+
* whether somebody is HOLDING it. A worktree with uncommitted or untracked files
|
|
65
|
+
* (`WorktreeService.workInFlight`), one LOCKED by a live agent, the tree you are standing in
|
|
66
|
+
* and a detached HEAD are all spared, each with a stated reason. Everything else with zero commits is
|
|
67
|
+
* a husk, and deleting a husk loses a NAME, not a commit — which the archive tag then hands back.
|
|
68
|
+
*
|
|
69
|
+
* So the bar is "prove somebody is holding it", not "prove it is dead". The 2026-07-30 loss was
|
|
70
|
+
* three LIVE worktrees called dead with no such check; the check is now what stands between them.
|
|
62
71
|
*/
|
|
63
72
|
exports.CLASSIFICATION_NO_COMMITS = 'no-commits';
|
|
64
73
|
// Spared, but a human should be ASKED — in descending order of "obviously fine to delete".
|
|
@@ -84,19 +93,24 @@ exports.CLASSIFICATION_PRUNABLE = 'prunable-worktree';
|
|
|
84
93
|
exports.CLASSIFICATION_LOCKED = 'locked-worktree';
|
|
85
94
|
exports.CLASSIFICATION_CURRENT = 'current-worktree';
|
|
86
95
|
exports.CLASSIFICATION_DETACHED = 'detached-worktree';
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
96
|
+
// The spared classifications wp-cleanup ADJUDICATES, most-safe first — everything else it either
|
|
97
|
+
// reaps on proof or spares for a mechanical reason (in-use, locked, current, detached).
|
|
98
|
+
//
|
|
99
|
+
// Three of the four are prompted / flagged, in exactly this order, so the easy yeses come before the
|
|
100
|
+
// ones that need thought. CLASSIFICATION_NO_COMMITS is the fourth and is REAPED rather than asked
|
|
101
|
+
// about (see its docstring): it stays in this list because this is the list wp-cleanup collects a
|
|
102
|
+
// candidate set from, and pulling it out would mean a husk never reaching the code that reaps it.
|
|
103
|
+
exports.ADJUDICATED_CLASSIFICATIONS = [
|
|
90
104
|
// CLASSIFICATION_BACKUP_OF_LIVE is deliberately NOT here — it is auto-reaped now. Anything that
|
|
91
105
|
// reaches this list is a real judgement call; if a group's answer is always yes, it belongs in
|
|
92
106
|
// the deletable set instead of on a human's plate.
|
|
93
107
|
exports.CLASSIFICATION_SUPERSEDED,
|
|
94
108
|
exports.CLASSIFICATION_CONTENT_IN_MAIN,
|
|
95
109
|
exports.CLASSIFICATION_NEVER_PROPOSED,
|
|
96
|
-
// LAST
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
110
|
+
// LAST, and no longer a question at all. Ordering it last was the old, weak answer to "a worktree
|
|
111
|
+
// in active use looks exactly like this" — a prompt cannot tell a human which one they are
|
|
112
|
+
// looking at, and ordering does not help them either. wp-cleanup now ANSWERS that question
|
|
113
|
+
// instead of asking it (uncommitted files, live lock, current tree) and reaps whatever is left.
|
|
100
114
|
exports.CLASSIFICATION_NO_COMMITS,
|
|
101
115
|
];
|
|
102
116
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merged-branch-verdicts.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branch-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The VERDICT vocabulary — every data class and stable classification token the branch/worktree\n * cleanup story is written in. Split out of merged-branches.ts, which holds the SERVICE that produces\n * these values; this file holds only what they mean.\n *\n * Re-exported from merged-branches.ts, so every existing import path keeps working.\n */\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n/**\n * WHY a branch is dead, or why it was spared — as a STABLE token, alongside the English prose.\n *\n * `sha`/`commits`/`prState` (below) let a human SEE a spared branch. This adds the other half: a token\n * saying WHICH KIND of spared it is. Every spared branch used to report the identical string\n * `no merged PR found — a human must decide`, and in one observed repo that one string covered three\n * genuinely different situations — a PR CLOSED UNMERGED whose work landed under a later number, a\n * branch that NEVER had a PR and holds the only copy of its commits, and content already in main.\n * Reporting all three identically is why nobody ever decided, and why the pile grew to 6 branches\n * against a cap of 5. A token lets wp-cleanup GROUP them and ask a question that can be answered.\n */\n// Dead by proof — these are auto-deletable. Two proofs qualify: a MERGED PR, or a LIVE SIBLING REF\n// that provably holds the same work (see CLASSIFICATION_BACKUP_OF_LIVE).\nexport const CLASSIFICATION_MERGED_PR = 'merged-pr';\nexport const CLASSIFICATION_BACKUP_OF_MERGED = 'backup-of-merged';\n/**\n * A `<feature>PreMerge<N>` / `<feature>Squash` snapshot whose BASE branch is still alive — its PR is\n * open, or it simply has not been proposed yet.\n *\n * AUTO-DELETABLE. A snapshot is a copy of its base BY CONSTRUCTION, so while the base exists this\n * branch cannot be \"the only copy in existence\" — that is a proof, not a judgement call, and it is\n * why this is reaped rather than asked about. It used to be the FIRST promptable group (\"the easiest\n * yes in the list\"), which is precisely the tell: a question whose only honest answer is yes should\n * never have been a question. `wp-start-upsert-pr` mints one of these on every run, so leaving them\n * to a human meant the branch cap filled with copies and the cap's own remedy became \"ask the human\n * to adjudicate six branches\" — the toil this whole story exists to remove.\n */\nexport const CLASSIFICATION_BACKUP_OF_LIVE = 'backup-of-live';\n/**\n * NOT a proof of death — a SPARED verdict, and the reason this whole liveness model exists.\n *\n * \"Zero commits of its own\" used to be auto-deletable, on the reasoning that a branch holding no\n * commits can lose no work. That reasoning is true of a REF and false of a WORKING TREE: every\n * worktree created with `git worktree add -b … origin/main` has zero commits from the moment it is\n * created until its first commit — i.e. for exactly the window in which an agent is doing its work\n * in it. Observed 2026-07-30: three worktrees with live agents in them were listed as dead, under\n * the sentence \"so no work can be lost\".\n *\n * Liveness is now the same single rule branches already use: reapable == a MERGED PR. Anything not\n * provably merged is LIVE, and live means it is never deleted without an explicit human answer. A\n * genuine husk still gets cleaned up — it is PROMPTABLE (see below), so wp-cleanup asks.\n */\nexport const CLASSIFICATION_NO_COMMITS = 'no-commits';\n// Spared, but a human should be ASKED — in descending order of \"obviously fine to delete\".\nexport const CLASSIFICATION_SUPERSEDED = 'superseded';\nexport const CLASSIFICATION_CONTENT_IN_MAIN = 'content-already-in-main';\nexport const CLASSIFICATION_NEVER_PROPOSED = 'never-proposed';\n// Spared for a mechanical reason, not a judgement call (checked out somewhere).\nexport const CLASSIFICATION_IN_USE = 'in-use';\n\n/**\n * WORKTREE-only classifications. A worktree verdict borrows every token above (its branch is what is\n * being judged), but three of its outcomes have no branch analogue at all, and lumping them under\n * `in-use` would tell wp-cleanup to shut up about exactly the ones a human might want to act on.\n *\n * - PRUNABLE — the directory is already gone; `git worktree prune` is the reap, not `remove`.\n * - LOCKED — a `git worktree lock` is standing and whatever took it still is: a running Claude\n * agent, or a reason we cannot attribute to anybody. Never promptable. A lock whose\n * Claude agent is provably DEAD is not this — it is judged on its branch like any\n * unlocked tree, and cleared on the way out (see agent-worktree-lock.ts).\n * - CURRENT — the worktree the command is running IN. Removing your own cwd is a self-destruct.\n * - DETACHED — detached HEAD, so there is no branch to judge and no branch to archive.\n */\nexport const CLASSIFICATION_PRUNABLE = 'prunable-worktree';\nexport const CLASSIFICATION_LOCKED = 'locked-worktree';\nexport const CLASSIFICATION_CURRENT = 'current-worktree';\nexport const CLASSIFICATION_DETACHED = 'detached-worktree';\n\n// Spared classifications a human can meaningfully rule on, most-safe first. wp-cleanup prompts in\n// exactly this order so the easy yeses come before the ones that need thought.\nexport const PROMPTABLE_CLASSIFICATIONS: readonly string[] = [\n // CLASSIFICATION_BACKUP_OF_LIVE is deliberately NOT here — it is auto-reaped now. Anything that\n // reaches this list is a real judgement call; if a group's answer is always yes, it belongs in\n // the deletable set instead of on a human's plate.\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n // LAST deliberately. For a parked branch this is the most obvious yes in the whole list, but the\n // identical verdict on a WORKTREE means \"an agent may be working in here right now\", and the\n // prompt cannot tell the human which one they are looking at any better than by ordering it dead\n // last, after the groups whose evidence is about the PAST rather than about work in flight.\n CLASSIFICATION_NO_COMMITS,\n];\n\n/**\n * A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\n *\n * `sha`, `commits` and `prState` exist for the SPARED branches specifically. When nothing is\n * auto-reapable the branch cap has nothing safe to offer, and its only remaining advice was \"raise\n * maxLocalBranches\" / \"set turnOffRuleUntilEpoch\" — both of which loosen the rule. An agent with no\n * human present took the config edit, which is the exact failure the cap exists to prevent. To ask a\n * human \"may I delete these?\" instead, the guard has to be able to SHOW what it would delete: tip SHA\n * (so the delete is recoverable), PR state (closed? never opened?) and how much unique work is on it.\n *\n * All three are computed in the DETACHED refresher and read straight off merged-branches.json — the\n * blocking hook path never recomputes them. Defaulted so a cache written by an older release (and\n * every existing call site) revives without them.\n */\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n /** Short tip SHA — what makes the delete undoable (`git branch <name> <sha>`). '' when unknown. */\n sha: string;\n /** Commits on this branch that are not on origin/main. -1 when it could not be established. */\n commits: number;\n /** GitHub's state for the PR whose head is this branch: MERGED / CLOSED / OPEN, or '' for none. */\n prState: string;\n /**\n * One of the CLASSIFICATION_* tokens. Defaulted like the fields above so every pre-existing call\n * site — and every cache written by an older release — still constructs; an unclassified revived\n * entry reads as 'never-proposed', the most conservative of the spared verdicts.\n */\n classification: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n branch: string,\n reason: string,\n pr: number,\n sha: string = '',\n commits: number = -1,\n prState: string = '',\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.sha = sha;\n this.commits = commits;\n this.prState = prState;\n this.classification = classification;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n *\n * `classification` is the same STABLE token the branch verdicts carry, plus the four worktree-only\n * ones above. It exists for the same reason it does on DeletableBranch: `deletable` answers \"may the\n * tooling reap this unattended?\", and everything false used to collapse into one undifferentiated\n * \"spared\" that a human could not rule on. With a token, WorktreeReaper knows whether the reap is a\n * `prune` or a `remove`, and wp-cleanup knows which spared worktrees are worth ASKING about.\n * Defaulted so every pre-existing call site and every cache written by an older release still builds;\n * an unclassified revived entry reads as 'never-proposed', the most conservative spared verdict.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n classification: string;\n /**\n * This worktree still carries a `git worktree lock` that the reap must CLEAR before it can remove\n * the directory — set only for a lock whose Claude agent is provably gone (see\n * agent-worktree-lock.ts). Never set for a human lock or a live agent's: those are spared outright\n * and never reach the reaper at all.\n *\n * A field rather than a constructor parameter for the same reason ReapedWorktree.archiveTag is:\n * it is an annotation on an already-computed verdict, not one of the things the verdict IS.\n */\n unlockBeforeRemove: boolean = false;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n path: string,\n branch: string,\n reason: string,\n pr: number,\n deletable: boolean,\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n this.classification = classification;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\n\n/**\n * How old the cache may get before a reader must stop asserting figures from it.\n *\n * The cache is ALLOWED to be stale — that is the whole design (see the file header): the slow lookup\n * runs detached, and a branch that merged 30 seconds ago simply survives to the next refresh. What is\n * NOT allowed is quoting a stale file as present-tense fact. Observed 2026-07-30: the guard announced\n * \"8 parked local branches … none of them are dead\" when there was ONE, having counted branches that\n * had been deleted minutes earlier, and then blocked a legitimate command on that figure.\n *\n * Five minutes, because the refresher is kicked off by the hooks themselves and normally rewrites the\n * file within one tool call; anything older means the refresher did not run or could not finish.\n */\nexport const CACHE_STALE_AFTER_MS = 5 * 60 * 1000;\n\n/**\n * Whether a cache read off disk may be quoted as fact, and how old it is — so a message can SAY\n * \"the cached verdicts are stale\" instead of asserting a count it cannot stand behind.\n */\nexport class CacheFreshness {\n /** True when the file is older than CACHE_STALE_AFTER_MS, or its timestamp is missing/unparseable. */\n stale: boolean;\n /** Whole minutes since the cache was written. -1 when that cannot be established. */\n ageMinutes: number;\n /** The raw ISO timestamp as recorded, or '' — printed verbatim so a human can check it. */\n timestamp: string;\n\n constructor(stale: boolean, ageMinutes: number, timestamp: string) {\n this.stale = stale;\n this.ageMinutes = ageMinutes;\n this.timestamp = timestamp;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"merged-branch-verdicts.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branch-verdicts.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The VERDICT vocabulary — every data class and stable classification token the branch/worktree\n * cleanup story is written in. Split out of merged-branches.ts, which holds the SERVICE that produces\n * these values; this file holds only what they mean.\n *\n * Re-exported from merged-branches.ts, so every existing import path keeps working.\n */\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n/**\n * WHY a branch is dead, or why it was spared — as a STABLE token, alongside the English prose.\n *\n * `sha`/`commits`/`prState` (below) let a human SEE a spared branch. This adds the other half: a token\n * saying WHICH KIND of spared it is. Every spared branch used to report the identical string\n * `no merged PR found — a human must decide`, and in one observed repo that one string covered three\n * genuinely different situations — a PR CLOSED UNMERGED whose work landed under a later number, a\n * branch that NEVER had a PR and holds the only copy of its commits, and content already in main.\n * Reporting all three identically is why nobody ever decided, and why the pile grew to 6 branches\n * against a cap of 5. A token lets wp-cleanup GROUP them and ask a question that can be answered.\n */\n// Dead by proof — these are auto-deletable. Two proofs qualify: a MERGED PR, or a LIVE SIBLING REF\n// that provably holds the same work (see CLASSIFICATION_BACKUP_OF_LIVE).\nexport const CLASSIFICATION_MERGED_PR = 'merged-pr';\nexport const CLASSIFICATION_BACKUP_OF_MERGED = 'backup-of-merged';\n/**\n * A `<feature>PreMerge<N>` / `<feature>Squash` snapshot whose BASE branch is still alive — its PR is\n * open, or it simply has not been proposed yet.\n *\n * AUTO-DELETABLE. A snapshot is a copy of its base BY CONSTRUCTION, so while the base exists this\n * branch cannot be \"the only copy in existence\" — that is a proof, not a judgement call, and it is\n * why this is reaped rather than asked about. It used to be the FIRST promptable group (\"the easiest\n * yes in the list\"), which is precisely the tell: a question whose only honest answer is yes should\n * never have been a question. `wp-start-upsert-pr` mints one of these on every run, so leaving them\n * to a human meant the branch cap filled with copies and the cap's own remedy became \"ask the human\n * to adjudicate six branches\" — the toil this whole story exists to remove.\n */\nexport const CLASSIFICATION_BACKUP_OF_LIVE = 'backup-of-live';\n/**\n * NOT a proof of death — a SPARED verdict, and the reason this whole liveness model exists.\n *\n * \"Zero commits of its own\" used to be auto-deletable, on the reasoning that a branch holding no\n * commits can lose no work. That reasoning is true of a REF and false of a WORKING TREE: every\n * worktree created with `git worktree add -b … origin/main` has zero commits from the moment it is\n * created until its first commit — i.e. for exactly the window in which an agent is doing its work\n * in it. Observed 2026-07-30: three worktrees with live agents in them were listed as dead, under\n * the sentence \"so no work can be lost\".\n *\n * Liveness for anything holding COMMITS is the same single rule branches already use: reapable == a\n * MERGED PR, and anything short of that is never deleted without an explicit answer.\n *\n * THIS verdict is the exception, and it is not the old auto-delete coming back. wp-cleanup REAPS a\n * zero-commit ref by default — after checking the thing the 2026-07-30 incident actually turned on:\n * whether somebody is HOLDING it. A worktree with uncommitted or untracked files\n * (`WorktreeService.workInFlight`), one LOCKED by a live agent, the tree you are standing in\n * and a detached HEAD are all spared, each with a stated reason. Everything else with zero commits is\n * a husk, and deleting a husk loses a NAME, not a commit — which the archive tag then hands back.\n *\n * So the bar is \"prove somebody is holding it\", not \"prove it is dead\". The 2026-07-30 loss was\n * three LIVE worktrees called dead with no such check; the check is now what stands between them.\n */\nexport const CLASSIFICATION_NO_COMMITS = 'no-commits';\n// Spared, but a human should be ASKED — in descending order of \"obviously fine to delete\".\nexport const CLASSIFICATION_SUPERSEDED = 'superseded';\nexport const CLASSIFICATION_CONTENT_IN_MAIN = 'content-already-in-main';\nexport const CLASSIFICATION_NEVER_PROPOSED = 'never-proposed';\n// Spared for a mechanical reason, not a judgement call (checked out somewhere).\nexport const CLASSIFICATION_IN_USE = 'in-use';\n\n/**\n * WORKTREE-only classifications. A worktree verdict borrows every token above (its branch is what is\n * being judged), but three of its outcomes have no branch analogue at all, and lumping them under\n * `in-use` would tell wp-cleanup to shut up about exactly the ones a human might want to act on.\n *\n * - PRUNABLE — the directory is already gone; `git worktree prune` is the reap, not `remove`.\n * - LOCKED — a `git worktree lock` is standing and whatever took it still is: a running Claude\n * agent, or a reason we cannot attribute to anybody. Never promptable. A lock whose\n * Claude agent is provably DEAD is not this — it is judged on its branch like any\n * unlocked tree, and cleared on the way out (see agent-worktree-lock.ts).\n * - CURRENT — the worktree the command is running IN. Removing your own cwd is a self-destruct.\n * - DETACHED — detached HEAD, so there is no branch to judge and no branch to archive.\n */\nexport const CLASSIFICATION_PRUNABLE = 'prunable-worktree';\nexport const CLASSIFICATION_LOCKED = 'locked-worktree';\nexport const CLASSIFICATION_CURRENT = 'current-worktree';\nexport const CLASSIFICATION_DETACHED = 'detached-worktree';\n\n// The spared classifications wp-cleanup ADJUDICATES, most-safe first — everything else it either\n// reaps on proof or spares for a mechanical reason (in-use, locked, current, detached).\n//\n// Three of the four are prompted / flagged, in exactly this order, so the easy yeses come before the\n// ones that need thought. CLASSIFICATION_NO_COMMITS is the fourth and is REAPED rather than asked\n// about (see its docstring): it stays in this list because this is the list wp-cleanup collects a\n// candidate set from, and pulling it out would mean a husk never reaching the code that reaps it.\nexport const ADJUDICATED_CLASSIFICATIONS: readonly string[] = [\n // CLASSIFICATION_BACKUP_OF_LIVE is deliberately NOT here — it is auto-reaped now. Anything that\n // reaches this list is a real judgement call; if a group's answer is always yes, it belongs in\n // the deletable set instead of on a human's plate.\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n // LAST, and no longer a question at all. Ordering it last was the old, weak answer to \"a worktree\n // in active use looks exactly like this\" — a prompt cannot tell a human which one they are\n // looking at, and ordering does not help them either. wp-cleanup now ANSWERS that question\n // instead of asking it (uncommitted files, live lock, current tree) and reaps whatever is left.\n CLASSIFICATION_NO_COMMITS,\n];\n\n/**\n * A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\n *\n * `sha`, `commits` and `prState` exist for the SPARED branches specifically. When nothing is\n * auto-reapable the branch cap has nothing safe to offer, and its only remaining advice was \"raise\n * maxLocalBranches\" / \"set turnOffRuleUntilEpoch\" — both of which loosen the rule. An agent with no\n * human present took the config edit, which is the exact failure the cap exists to prevent. To ask a\n * human \"may I delete these?\" instead, the guard has to be able to SHOW what it would delete: tip SHA\n * (so the delete is recoverable), PR state (closed? never opened?) and how much unique work is on it.\n *\n * All three are computed in the DETACHED refresher and read straight off merged-branches.json — the\n * blocking hook path never recomputes them. Defaulted so a cache written by an older release (and\n * every existing call site) revives without them.\n */\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n /** Short tip SHA — what makes the delete undoable (`git branch <name> <sha>`). '' when unknown. */\n sha: string;\n /** Commits on this branch that are not on origin/main. -1 when it could not be established. */\n commits: number;\n /** GitHub's state for the PR whose head is this branch: MERGED / CLOSED / OPEN, or '' for none. */\n prState: string;\n /**\n * One of the CLASSIFICATION_* tokens. Defaulted like the fields above so every pre-existing call\n * site — and every cache written by an older release — still constructs; an unclassified revived\n * entry reads as 'never-proposed', the most conservative of the spared verdicts.\n */\n classification: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n branch: string,\n reason: string,\n pr: number,\n sha: string = '',\n commits: number = -1,\n prState: string = '',\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.sha = sha;\n this.commits = commits;\n this.prState = prState;\n this.classification = classification;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n *\n * `classification` is the same STABLE token the branch verdicts carry, plus the four worktree-only\n * ones above. It exists for the same reason it does on DeletableBranch: `deletable` answers \"may the\n * tooling reap this unattended?\", and everything false used to collapse into one undifferentiated\n * \"spared\" that a human could not rule on. With a token, WorktreeReaper knows whether the reap is a\n * `prune` or a `remove`, and wp-cleanup knows which spared worktrees are worth ASKING about.\n * Defaulted so every pre-existing call site and every cache written by an older release still builds;\n * an unclassified revived entry reads as 'never-proposed', the most conservative spared verdict.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n classification: string;\n /**\n * This worktree still carries a `git worktree lock` that the reap must CLEAR before it can remove\n * the directory — set only for a lock whose Claude agent is provably gone (see\n * agent-worktree-lock.ts). Never set for a human lock or a live agent's: those are spared outright\n * and never reach the reaper at all.\n *\n * A field rather than a constructor parameter for the same reason ReapedWorktree.archiveTag is:\n * it is an annotation on an already-computed verdict, not one of the things the verdict IS.\n */\n unlockBeforeRemove: boolean = false;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n path: string,\n branch: string,\n reason: string,\n pr: number,\n deletable: boolean,\n classification: string = CLASSIFICATION_NEVER_PROPOSED,\n ) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n this.classification = classification;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\n\n/**\n * How old the cache may get before a reader must stop asserting figures from it.\n *\n * The cache is ALLOWED to be stale — that is the whole design (see the file header): the slow lookup\n * runs detached, and a branch that merged 30 seconds ago simply survives to the next refresh. What is\n * NOT allowed is quoting a stale file as present-tense fact. Observed 2026-07-30: the guard announced\n * \"8 parked local branches … none of them are dead\" when there was ONE, having counted branches that\n * had been deleted minutes earlier, and then blocked a legitimate command on that figure.\n *\n * Five minutes, because the refresher is kicked off by the hooks themselves and normally rewrites the\n * file within one tool call; anything older means the refresher did not run or could not finish.\n */\nexport const CACHE_STALE_AFTER_MS = 5 * 60 * 1000;\n\n/**\n * Whether a cache read off disk may be quoted as fact, and how old it is — so a message can SAY\n * \"the cached verdicts are stale\" instead of asserting a count it cannot stand behind.\n */\nexport class CacheFreshness {\n /** True when the file is older than CACHE_STALE_AFTER_MS, or its timestamp is missing/unparseable. */\n stale: boolean;\n /** Whole minutes since the cache was written. -1 when that cannot be established. */\n ageMinutes: number;\n /** The raw ISO timestamp as recorded, or '' — printed verbatim so a human can check it. */\n timestamp: string;\n\n constructor(stale: boolean, ageMinutes: number, timestamp: string) {\n this.stale = stale;\n this.ageMinutes = ageMinutes;\n this.timestamp = timestamp;\n }\n}\n"]}
|
package/src/merged-branches.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { DotWebpieces } from './state-dir';
|
|
|
3
3
|
import { WorktreeService } from './worktrees';
|
|
4
4
|
import { WorktreeLockVerdicts } from './worktree-lock-verdicts';
|
|
5
5
|
import { CacheFreshness, MergedBranchesCache } from './merged-branch-verdicts';
|
|
6
|
-
export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, CacheFreshness, CACHE_STALE_AFTER_MS, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_BACKUP_OF_LIVE, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED,
|
|
6
|
+
export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, CacheFreshness, CACHE_STALE_AFTER_MS, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_BACKUP_OF_LIVE, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED, ADJUDICATED_CLASSIFICATIONS, } from './merged-branch-verdicts';
|
|
7
7
|
export declare class MergedBranchesService {
|
|
8
8
|
private readonly worktrees;
|
|
9
9
|
private readonly dotDir;
|
|
@@ -71,7 +71,9 @@ export declare class MergedBranchesService {
|
|
|
71
71
|
* lockVerdict), when it is the worktree we are standing in right now (removing your own cwd is not
|
|
72
72
|
* a thing to suggest to an agent), or when its branch is anything short of provably merged —
|
|
73
73
|
* INCLUDING a branch with no commits yet, which is what every worktree looks like while an agent
|
|
74
|
-
* is working in it.
|
|
74
|
+
* is working in it. `deletable` means PROVABLY dead and nothing weaker; wp-cleanup reaps the
|
|
75
|
+
* zero-commit ones on top of that, but only after checking on disk whether anybody is holding
|
|
76
|
+
* them (see CLASSIFICATION_NO_COMMITS), which is a question these verdicts do not answer.
|
|
75
77
|
*/
|
|
76
78
|
private classifyWorktrees;
|
|
77
79
|
/**
|
package/src/merged-branches.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MergedBranchesService = exports.
|
|
3
|
+
exports.MergedBranchesService = exports.ADJUDICATED_CLASSIFICATIONS = exports.CLASSIFICATION_DETACHED = exports.CLASSIFICATION_CURRENT = exports.CLASSIFICATION_LOCKED = exports.CLASSIFICATION_PRUNABLE = exports.CLASSIFICATION_IN_USE = exports.CLASSIFICATION_NEVER_PROPOSED = exports.CLASSIFICATION_CONTENT_IN_MAIN = exports.CLASSIFICATION_SUPERSEDED = exports.CLASSIFICATION_NO_COMMITS = exports.CLASSIFICATION_BACKUP_OF_LIVE = exports.CLASSIFICATION_BACKUP_OF_MERGED = exports.CLASSIFICATION_MERGED_PR = exports.CACHE_STALE_AFTER_MS = exports.CacheFreshness = exports.MergedBranchesCache = exports.DeletableWorktree = exports.DeletableBranch = exports.MergedBranch = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const fs = tslib_1.__importStar(require("fs"));
|
|
@@ -30,7 +30,7 @@ Object.defineProperty(exports, "CLASSIFICATION_PRUNABLE", { enumerable: true, ge
|
|
|
30
30
|
Object.defineProperty(exports, "CLASSIFICATION_LOCKED", { enumerable: true, get: function () { return merged_branch_verdicts_2.CLASSIFICATION_LOCKED; } });
|
|
31
31
|
Object.defineProperty(exports, "CLASSIFICATION_CURRENT", { enumerable: true, get: function () { return merged_branch_verdicts_2.CLASSIFICATION_CURRENT; } });
|
|
32
32
|
Object.defineProperty(exports, "CLASSIFICATION_DETACHED", { enumerable: true, get: function () { return merged_branch_verdicts_2.CLASSIFICATION_DETACHED; } });
|
|
33
|
-
Object.defineProperty(exports, "
|
|
33
|
+
Object.defineProperty(exports, "ADJUDICATED_CLASSIFICATIONS", { enumerable: true, get: function () { return merged_branch_verdicts_2.ADJUDICATED_CLASSIFICATIONS; } });
|
|
34
34
|
/**
|
|
35
35
|
* The "which local branches are dead?" cache.
|
|
36
36
|
*
|
|
@@ -247,7 +247,9 @@ let MergedBranchesService = class MergedBranchesService {
|
|
|
247
247
|
* lockVerdict), when it is the worktree we are standing in right now (removing your own cwd is not
|
|
248
248
|
* a thing to suggest to an agent), or when its branch is anything short of provably merged —
|
|
249
249
|
* INCLUDING a branch with no commits yet, which is what every worktree looks like while an agent
|
|
250
|
-
* is working in it.
|
|
250
|
+
* is working in it. `deletable` means PROVABLY dead and nothing weaker; wp-cleanup reaps the
|
|
251
|
+
* zero-commit ones on top of that, but only after checking on disk whether anybody is holding
|
|
252
|
+
* them (see CLASSIFICATION_NO_COMMITS), which is a question these verdicts do not answer.
|
|
251
253
|
*/
|
|
252
254
|
classifyWorktrees(repoRoot, trees, prs) {
|
|
253
255
|
const out = [];
|
|
@@ -325,15 +327,15 @@ let MergedBranchesService = class MergedBranchesService {
|
|
|
325
327
|
`this work, so this is a spare copy, not the only one`, basePrState ? basePrState.number : 0, sha, commits, prState, merged_branch_verdicts_1.CLASSIFICATION_BACKUP_OF_LIVE));
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
|
-
// Zero commits of its own.
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
330
|
+
// Zero commits of its own. NOT deletable BY THIS VERDICT — the verdicts answer "is this
|
|
331
|
+
// provably dead?", and a husk is not. wp-cleanup reaps it anyway, after asking the one
|
|
332
|
+
// question this service cannot: is anybody HOLDING it (uncommitted files, a live lock, the
|
|
333
|
+
// tree you are standing in)? That check needs the worktree on disk, so it lives there and the
|
|
334
|
+
// verdict stays exactly what it is — see CLASSIFICATION_NO_COMMITS.
|
|
333
335
|
if (commits === 0) {
|
|
334
|
-
return new Verdict(false, new merged_branch_verdicts_1.DeletableBranch(branch, 'no commits of its own — identical to origin/main
|
|
335
|
-
+ '
|
|
336
|
-
+ '
|
|
336
|
+
return new Verdict(false, new merged_branch_verdicts_1.DeletableBranch(branch, 'no commits of its own — identical to origin/main, so deleting the ref loses a NAME, not a '
|
|
337
|
+
+ 'commit. wp-cleanup reaps this by default; a worktree holding it is spared when it has '
|
|
338
|
+
+ 'uncommitted files or a live lock, i.e. when somebody is working in it', 0, sha, 0, prState, merged_branch_verdicts_1.CLASSIFICATION_NO_COMMITS));
|
|
337
339
|
}
|
|
338
340
|
return new Verdict(false, this.classifySpared(repoRoot, branch, prs, sha, commits, prState));
|
|
339
341
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from './atomic-file';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\nimport { WorktreeLockVerdicts } from './worktree-lock-verdicts';\nimport {\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_SUPERSEDED,\n DeletableBranch,\n DeletableWorktree,\n MergedBranch,\n MergedBranchesCache,\n} from './merged-branch-verdicts';\n\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n PROMPTABLE_CLASSIFICATIONS,\n} from './merged-branch-verdicts';\n\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n/**\n * Internal: the bulk PR lookup, indexed.\n *\n * `merged` drives the DELETE verdicts and comes from the `--state merged` call, unchanged — the\n * reaping logic must not get looser or tighter here. `state` is display-only, from a second\n * `--state all` call, and answers the question a human needs in order to say \"yes, delete it\":\n * was there a PR at all, and did it close without merging? It fails soft to an empty map.\n */\nclass PrRef {\n number: number;\n state: string;\n\n constructor(prNumber: number, state: string) {\n this.number = prNumber;\n this.state = state;\n }\n}\n\nclass PrLookup {\n merged: Map<string, number>;\n state: Map<string, PrRef>;\n /**\n * Highest merged PR number seen. A branch whose own PR CLOSED UNMERGED below this number means work\n * kept landing after it was abandoned — the \"superseded by a later PR\" signal, obtained without\n * having to guess WHICH PR superseded it.\n */\n latestMergedPr: number;\n\n constructor(merged: Map<string, number>, state: Map<string, PrRef>, latestMergedPr: number = 0) {\n this.merged = merged;\n this.state = state;\n this.latestMergedPr = latestMergedPr;\n }\n}\n\n// Internal: a classification result. `deletable` is the decision; `entry` carries the branch + reason\n// either way (a spared branch still needs its reason recorded, and it has no PR to key off).\nclass Verdict {\n deletable: boolean;\n entry: DeletableBranch;\n\n constructor(deletable: boolean, entry: DeletableBranch) {\n this.deletable = deletable;\n this.entry = entry;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawDeletable {\n branch?: string;\n reason?: string;\n pr?: number;\n // Absent in caches written before the \"ask the human which of these to delete\" remedy existed.\n sha?: string;\n commits?: number;\n prState?: string;\n // Absent before classification existed — revives to the conservative 'never-proposed'.\n classification?: string;\n}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n // Absent before worktree verdicts carried a classification — revives to 'never-proposed'.\n classification?: string;\n // Absent before stale agent locks were recognised — revives to false, i.e. \"there is no lock to\n // clear\", which is the fail-safe direction: the removal fails loudly instead of overriding a lock\n // this release cannot see a reason for.\n unlockBeforeRemove?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n // Only requested by the display-only fetchPrStates call.\n state?: string;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly locks: WorktreeLockVerdicts = new WorktreeLockVerdicts(),\n ) {}\n\n /**\n * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents\n * describe every branch AND every worktree in the repo, so a per-worktree copy was a repo-wide fact\n * stored N times and therefore N times wrong: branch-creation-guard reported \"8 parked local\n * branches\" against an actual 1, reading a copy that predated deletions made from another worktree.\n */\n mergedBranchesPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * How old is this cache, and may its numbers be quoted as fact?\n *\n * `now` is a parameter purely so the specs can pin it; every caller passes nothing.\n */\n freshness(cache: MergedBranchesCache, now: number = Date.now()): CacheFreshness {\n const written = Date.parse(cache.timestamp);\n if (Number.isNaN(written)) return new CacheFreshness(true, -1, cache.timestamp);\n const ageMs = Math.max(0, now - written);\n const ageMinutes = Math.floor(ageMs / 60000);\n return new CacheFreshness(ageMs > CACHE_STALE_AFTER_MS, ageMinutes, cache.timestamp);\n }\n\n /**\n * Drop every cached verdict whose subject no longer exists, using only LOCAL, instant git reads.\n *\n * The verdicts themselves are NOT re-derived here — that is the detached refresher's job and it\n * needs the network. This answers a different and much cheaper question: does this branch / this\n * worktree still exist at all? A cache written before a `git branch -D` or a `git worktree remove`\n * keeps naming things that are gone, and a reader that counts those entries reports a repo that\n * does not exist (observed: 8 parked branches asserted against an actual 1) and then blocks on it.\n *\n * Fails toward the cache: if git cannot answer (empty branch list), the cache is returned untouched\n * rather than emptied, since \"git failed\" must never read as \"everything was deleted\".\n */\n reconcile(repoRoot: string, cache: MergedBranchesCache): MergedBranchesCache {\n const live = new Set(this.localBranches(repoRoot));\n const trees = this.worktrees.listWorktrees(repoRoot);\n const livePaths = new Set(trees.map((tree: Worktree): string => tree.path));\n\n const keepBranch = (entry: DeletableBranch): boolean => live.size === 0 || live.has(entry.branch);\n const keepTree = (entry: DeletableWorktree): boolean =>\n trees.length === 0 || livePaths.has(entry.path);\n\n return new MergedBranchesCache(\n cache.timestamp,\n cache.deletable.filter(keepBranch),\n cache.keep.filter(keepBranch),\n cache.worktrees.filter(keepTree),\n );\n }\n\n /**\n * ATOMIC write. Now that this file is shared by every worktree it has N concurrent writers (one\n * detached refresher per agent), and its readers are the guards' BLOCKING path. A plain\n * `writeFileSync` truncates before it writes, so a reader landing in that window gets a torn\n * document — the reader-side retry added in PR #526 cannot rescue a syntactically valid PREFIX.\n * Fixed where it belongs: temp file + `rename()`, which POSIX makes atomic. See AtomicFile.\n *\n * This buys ATOMIC READS, not lost-update protection: two read-modify-write cycles still end\n * last-writer-wins. That is acceptable and deliberate — this is a DERIVED cache, recomputed from\n * `gh` + git by the refresher, and wp-cleanup recomputes rather than trusting it before deleting\n * anything. Nothing here is transactional and no caller may assume it is.\n */\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n this.atomicFile.writeJsonAtomic(this.mergedBranchesPath(repoRoot), cache);\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n const result = this.capture(repoRoot, 'git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/']);\n if (!result.ok || result.out === '') return [];\n return result.out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n computeMergedBranches(repoRoot: string): MergedBranchesCache {\n const merged = this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n let latestMergedPr = 0;\n for (const entry of merged) {\n if (entry.pr > latestMergedPr) latestMergedPr = entry.pr;\n }\n const prs = new PrLookup(byBranch, this.fetchPrStates(repoRoot), latestMergedPr);\n\n const trees = this.worktrees.listWorktrees(repoRoot);\n const holder = new Map<string, string>();\n for (const tree of trees) {\n if (tree.branch !== '') holder.set(tree.branch, tree.path);\n }\n\n const deletable: DeletableBranch[] = [];\n const keep: DeletableBranch[] = [];\n\n for (const branch of this.localBranches(repoRoot)) {\n const verdict = this.classify(repoRoot, branch, prs);\n\n // A branch checked out in ANY worktree (including the branch we are standing on right here)\n // cannot be deleted — git refuses, and since the reap is ONE `git branch -D a b c`, a single\n // such branch would fail the entire command and strand the branches that would have deleted\n // fine. Spare it LOUDLY (into `keep`, with the reason) rather than dropping it silently: the\n // worktree list below is what actually reaps it, and a human should see the connection.\n const heldAt = holder.get(branch);\n if (heldAt !== undefined) {\n keep.push(new DeletableBranch(\n branch,\n `checked out in worktree '${heldAt}' — remove that worktree before deleting the branch`,\n verdict.entry.pr,\n verdict.entry.sha,\n verdict.entry.commits,\n verdict.entry.prState,\n CLASSIFICATION_IN_USE,\n ));\n continue;\n }\n\n if (verdict.deletable) deletable.push(verdict.entry);\n else keep.push(verdict.entry);\n }\n\n const worktrees = this.classifyWorktrees(repoRoot, trees, prs);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proof the branch cap uses — a MERGED PR, its own or that of the branch it\n * snapshots. Nothing else. It is spared when it is LOCKED BY SOMETHING STILL THERE (see\n * lockVerdict), when it is the worktree we are standing in right now (removing your own cwd is not\n * a thing to suggest to an agent), or when its branch is anything short of provably merged —\n * INCLUDING a branch with no commits yet, which is what every worktree looks like while an agent\n * is working in it.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\n ): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true,\n CLASSIFICATION_PRUNABLE));\n continue;\n }\n // A dead agent's lock returns null here and the worktree is judged on its branch below,\n // exactly like an unlocked one — with `unlockBeforeRemove` set so the reap clears the lock.\n const staleLock = tree.locked ? this.locks.staleAgentLock(tree) : null;\n if (tree.locked && staleLock === null) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, this.locks.sparedReason(tree), 0, false, CLASSIFICATION_LOCKED));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false, CLASSIFICATION_CURRENT),\n staleLock));\n continue;\n }\n if (tree.branch === '') {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false,\n CLASSIFICATION_DETACHED), staleLock));\n continue;\n }\n\n // The branch's OWN classification token rides along unchanged. That is what lets wp-cleanup\n // group probably-dead worktrees exactly the way it groups probably-dead branches, instead of\n // inventing a second, parallel notion of \"how dead is this\".\n const verdict = this.classify(repoRoot, tree.branch, prs);\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable,\n verdict.entry.classification), staleLock));\n }\n\n return out;\n }\n\n /**\n * Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n *\n * The verdict itself is unchanged. What is new is that EVERY entry now carries its tip SHA, its\n * unique-commit count and its PR state, so a `keep` can be shown to a human as a delete candidate\n * rather than just counted. This runs only in the detached refresher, so the two extra local git\n * calls per branch cost the blocking hook path nothing.\n */\n private classify(repoRoot: string, branch: string, prs: PrLookup): Verdict {\n const sha = this.tipSha(repoRoot, branch);\n const commits = this.commitsAheadOfMain(repoRoot, branch);\n const known = prs.state.get(branch);\n const prState = known ? known.state : '';\n\n const own = prs.merged.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch, `PR #${String(own)} merged`, own, sha, commits, prState || 'MERGED',\n CLASSIFICATION_MERGED_PR));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = prs.merged.get(base);\n if (basePr !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch,\n `squash-merge backup of '${base}' (PR #${String(basePr)} merged) — its job is done`,\n basePr, sha, commits, prState, CLASSIFICATION_BACKUP_OF_MERGED,\n ));\n }\n // The base has not MERGED — but a snapshot is a copy of its base by construction, so as\n // long as the base still exists this branch is provably not the only copy of anything.\n // Falling through to classifySpared told the human the opposite (\"may be the only copy in\n // existence\"), which is unanswerable-by-design and is why these accumulated.\n //\n // DELETABLE, not merely promptable. Asking about it was still asking a question with only\n // one honest answer — the base branch is right there holding the same work — and every\n // ask lands on a human who is being asked to authorise a delete the tooling can already\n // PROVE is lossless. That is the pile: `wp-start-upsert-pr` mints another snapshot on\n // every run, so the branch cap fills with copies nobody can be bothered to adjudicate.\n // The proof here is as strong as a merged PR (a live sibling ref holds the content), and\n // the reap archives to an `archive/<date>/<branch>` tag and logs a `recover=` first.\n if (this.branchExists(repoRoot, base)) {\n const basePrState = prs.state.get(base);\n const liveness = basePrState\n ? `PR #${String(basePrState.number)} ${basePrState.state}`\n : 'no PR yet';\n return new Verdict(true, new DeletableBranch(\n branch,\n `pre-merge snapshot of '${base}' (${liveness}) — '${base}' still exists and holds ` +\n `this work, so this is a spare copy, not the only one`,\n basePrState ? basePrState.number : 0, sha, commits, prState,\n CLASSIFICATION_BACKUP_OF_LIVE,\n ));\n }\n }\n\n // Zero commits of its own. This is SPARED, not deletable — see CLASSIFICATION_NO_COMMITS for\n // why. It is a real signal (the branch is identical to origin/main), but it is equally the\n // signature of a worktree an agent created thirty seconds ago and has not committed in yet,\n // and there is no git-local way to tell those two apart. So it becomes a question for a human\n // (wp-cleanup prompts on it) instead of an unattended delete.\n if (commits === 0) {\n return new Verdict(false, new DeletableBranch(\n branch,\n 'no commits of its own — identical to origin/main. That is either an abandoned husk OR a '\n + 'worktree/branch someone is working in right now and has not committed to yet, so it is '\n + 'never reaped unattended',\n 0, sha, 0, prState, CLASSIFICATION_NO_COMMITS));\n }\n\n return new Verdict(false, this.classifySpared(repoRoot, branch, prs, sha, commits, prState));\n }\n\n /**\n * The three genuinely different reasons a branch survives the deletable proofs, ordered by how\n * confidently a human can say yes to deleting it.\n *\n * The safety posture is UNCHANGED: all three are still SPARED, never auto-deleted. The only thing\n * that changed is that wp-cleanup can now ask a question whose answer is knowable.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private classifySpared(\n repoRoot: string, branch: string, prs: PrLookup, sha: string, commits: number, prState: string,\n ): DeletableBranch {\n const known = prs.state.get(branch);\n\n // A PR that CLOSED without merging, in a repo that has merged later PRs, is near-certainly the\n // abandoned first attempt at work that landed under a different number.\n if (known !== undefined && known.state === 'CLOSED' && prs.latestMergedPr > known.number) {\n return new DeletableBranch(\n branch,\n `PR #${String(known.number)} was CLOSED UNMERGED and later PRs (through ` +\n `#${String(prs.latestMergedPr)}) have merged — near-certainly superseded`,\n known.number, sha, commits, prState, CLASSIFICATION_SUPERSEDED,\n );\n }\n\n // `git cherry` compares by PATCH-ID, so it survives cherry-picks and rebases. It does NOT\n // survive a squash — which is exactly why this is a spared verdict and not a deletable proof.\n if (this.contentAlreadyInMain(repoRoot, branch)) {\n return new DeletableBranch(\n branch,\n `all ${String(commits)} commit(s) already have an equivalent in origin/main ` +\n `(git cherry) — content is not unique`,\n known ? known.number : 0, sha, commits, prState, CLASSIFICATION_CONTENT_IN_MAIN,\n );\n }\n\n const why = known\n ? `PR #${String(known.number)} is ${known.state} (not merged); holds ` +\n `${String(commits)} unique commit(s) — a human must decide`\n : `never had a PR; holds ${String(commits)} unique commit(s) that may be the only copy in existence`;\n return new DeletableBranch(\n branch, why, 0, sha, commits, prState, CLASSIFICATION_NEVER_PROPOSED);\n }\n\n /**\n * True when EVERY commit on the branch has a patch-equivalent already in origin/main.\n *\n * `git cherry origin/main <branch>` prints one line per commit: `+ <sha>` = not upstream,\n * `- <sha>` = an equivalent change IS upstream. All-minus means the content landed (typically by\n * cherry-pick or rebase-merge) even though the SHAs differ. Any failure returns false — \"cannot\n * prove the content is in main\" must never read as \"safe\".\n */\n private contentAlreadyInMain(repoRoot: string, branch: string): boolean {\n const result = this.capture(repoRoot, 'git', ['cherry', 'origin/main', branch]);\n if (!result.ok || result.out === '') return false;\n const lines = result.out.split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line !== '');\n if (lines.length === 0) return false;\n return lines.every((line: string): boolean => line.startsWith('-'));\n }\n\n /**\n * Does this local branch exist? Used to prove a snapshot is not the last copy of its work — the\n * base branch being present is that proof. Verifies it is a BRANCH (`refs/heads/`), not just any\n * resolvable rev, so a tag or a stray SHA sharing the name cannot stand in for the base.\n */\n private branchExists(repoRoot: string, branch: string): boolean {\n return this.capture(repoRoot, 'git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]).ok;\n }\n\n // Short tip SHA — the value that makes any delete of this branch reversible.\n private tipSha(repoRoot: string, branch: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--short', branch]);\n return result.ok ? result.out : '';\n }\n\n /**\n * Commits on `branch` that are not on origin/main. Returns -1 (\"assume it has work\") whenever the\n * count cannot be established — an unresolvable origin/main must never read as \"empty branch\".\n */\n private commitsAheadOfMain(repoRoot: string, branch: string): number {\n const result = this.capture(repoRoot, 'git', ['rev-list', '--count', `origin/main..${branch}`]);\n if (!result.ok) return -1;\n const count = Number(result.out);\n return Number.isInteger(count) ? count : -1;\n }\n\n /**\n * The ONE bulk network call. Every merged PR's head branch in a single round trip — no per-branch\n * lookups. Fails SOFT: if `gh` is missing, unauthenticated, or offline we return [], which makes\n * every branch a `keep`. The guard then deletes nothing rather than guessing.\n */\n private fetchMergedPrs(repoRoot: string): MergedBranch[] {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'merged',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName',\n ]);\n if (!result.ok || result.out === '') return [];\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n const out: MergedBranch[] = [];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n if (branch !== '' && pr > 0) out.push(new MergedBranch(branch, pr));\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n /**\n * The DISPLAY-only second lookup: every PR (any state) keyed by head branch, so a spared branch can\n * be shown to a human as \"PR #12 CLOSED (not merged)\" or \"no PR\" instead of an opaque name. Kept\n * SEPARATE from fetchMergedPrs on purpose — folding both into one `--state all` call would let a\n * flood of open/closed PRs push older MERGED ones out of the limit and silently stop reaping real\n * dead branches. Fails soft to an empty map: the verdicts do not depend on it.\n */\n private fetchPrStates(repoRoot: string): Map<string, PrRef> {\n const out = new Map<string, PrRef>();\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'all',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName,state',\n ]);\n if (!result.ok || result.out === '') return out;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n // `gh` lists newest-first, so the FIRST entry for a branch is its latest PR — keep it.\n if (branch !== '' && pr > 0 && !out.has(branch)) {\n out.set(branch, new PrRef(pr, entry.state ?? ''));\n }\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return out;\n }\n }\n\n private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch => new DeletableBranch(\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.sha ?? '',\n entry.commits ?? -1,\n entry.prState ?? '',\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n ));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => {\n const verdict = new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n );\n verdict.unlockBeforeRemove = entry.unlockBeforeRemove ?? false;\n return verdict;\n });\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from './atomic-file';\nimport { DotWebpieces, dotWebpieces } from './state-dir';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\nimport { WorktreeLockVerdicts } from './worktree-lock-verdicts';\nimport {\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_SUPERSEDED,\n DeletableBranch,\n DeletableWorktree,\n MergedBranch,\n MergedBranchesCache,\n} from './merged-branch-verdicts';\n\nexport {\n MergedBranch,\n DeletableBranch,\n DeletableWorktree,\n MergedBranchesCache,\n CacheFreshness,\n CACHE_STALE_AFTER_MS,\n CLASSIFICATION_MERGED_PR,\n CLASSIFICATION_BACKUP_OF_MERGED,\n CLASSIFICATION_BACKUP_OF_LIVE,\n CLASSIFICATION_NO_COMMITS,\n CLASSIFICATION_SUPERSEDED,\n CLASSIFICATION_CONTENT_IN_MAIN,\n CLASSIFICATION_NEVER_PROPOSED,\n CLASSIFICATION_IN_USE,\n CLASSIFICATION_PRUNABLE,\n CLASSIFICATION_LOCKED,\n CLASSIFICATION_CURRENT,\n CLASSIFICATION_DETACHED,\n ADJUDICATED_CLASSIFICATIONS,\n} from './merged-branch-verdicts';\n\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n/**\n * Internal: the bulk PR lookup, indexed.\n *\n * `merged` drives the DELETE verdicts and comes from the `--state merged` call, unchanged — the\n * reaping logic must not get looser or tighter here. `state` is display-only, from a second\n * `--state all` call, and answers the question a human needs in order to say \"yes, delete it\":\n * was there a PR at all, and did it close without merging? It fails soft to an empty map.\n */\nclass PrRef {\n number: number;\n state: string;\n\n constructor(prNumber: number, state: string) {\n this.number = prNumber;\n this.state = state;\n }\n}\n\nclass PrLookup {\n merged: Map<string, number>;\n state: Map<string, PrRef>;\n /**\n * Highest merged PR number seen. A branch whose own PR CLOSED UNMERGED below this number means work\n * kept landing after it was abandoned — the \"superseded by a later PR\" signal, obtained without\n * having to guess WHICH PR superseded it.\n */\n latestMergedPr: number;\n\n constructor(merged: Map<string, number>, state: Map<string, PrRef>, latestMergedPr: number = 0) {\n this.merged = merged;\n this.state = state;\n this.latestMergedPr = latestMergedPr;\n }\n}\n\n// Internal: a classification result. `deletable` is the decision; `entry` carries the branch + reason\n// either way (a spared branch still needs its reason recorded, and it has no PR to key off).\nclass Verdict {\n deletable: boolean;\n entry: DeletableBranch;\n\n constructor(deletable: boolean, entry: DeletableBranch) {\n this.deletable = deletable;\n this.entry = entry;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawDeletable {\n branch?: string;\n reason?: string;\n pr?: number;\n // Absent in caches written before the \"ask the human which of these to delete\" remedy existed.\n sha?: string;\n commits?: number;\n prState?: string;\n // Absent before classification existed — revives to the conservative 'never-proposed'.\n classification?: string;\n}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n // Absent before worktree verdicts carried a classification — revives to 'never-proposed'.\n classification?: string;\n // Absent before stale agent locks were recognised — revives to false, i.e. \"there is no lock to\n // clear\", which is the fail-safe direction: the removal fails loudly instead of overriding a lock\n // this release cannot see a reason for.\n unlockBeforeRemove?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n // Only requested by the display-only fetchPrStates call.\n state?: string;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly locks: WorktreeLockVerdicts = new WorktreeLockVerdicts(),\n ) {}\n\n /**\n * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents\n * describe every branch AND every worktree in the repo, so a per-worktree copy was a repo-wide fact\n * stored N times and therefore N times wrong: branch-creation-guard reported \"8 parked local\n * branches\" against an actual 1, reading a copy that predated deletions made from another worktree.\n */\n mergedBranchesPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n /**\n * How old is this cache, and may its numbers be quoted as fact?\n *\n * `now` is a parameter purely so the specs can pin it; every caller passes nothing.\n */\n freshness(cache: MergedBranchesCache, now: number = Date.now()): CacheFreshness {\n const written = Date.parse(cache.timestamp);\n if (Number.isNaN(written)) return new CacheFreshness(true, -1, cache.timestamp);\n const ageMs = Math.max(0, now - written);\n const ageMinutes = Math.floor(ageMs / 60000);\n return new CacheFreshness(ageMs > CACHE_STALE_AFTER_MS, ageMinutes, cache.timestamp);\n }\n\n /**\n * Drop every cached verdict whose subject no longer exists, using only LOCAL, instant git reads.\n *\n * The verdicts themselves are NOT re-derived here — that is the detached refresher's job and it\n * needs the network. This answers a different and much cheaper question: does this branch / this\n * worktree still exist at all? A cache written before a `git branch -D` or a `git worktree remove`\n * keeps naming things that are gone, and a reader that counts those entries reports a repo that\n * does not exist (observed: 8 parked branches asserted against an actual 1) and then blocks on it.\n *\n * Fails toward the cache: if git cannot answer (empty branch list), the cache is returned untouched\n * rather than emptied, since \"git failed\" must never read as \"everything was deleted\".\n */\n reconcile(repoRoot: string, cache: MergedBranchesCache): MergedBranchesCache {\n const live = new Set(this.localBranches(repoRoot));\n const trees = this.worktrees.listWorktrees(repoRoot);\n const livePaths = new Set(trees.map((tree: Worktree): string => tree.path));\n\n const keepBranch = (entry: DeletableBranch): boolean => live.size === 0 || live.has(entry.branch);\n const keepTree = (entry: DeletableWorktree): boolean =>\n trees.length === 0 || livePaths.has(entry.path);\n\n return new MergedBranchesCache(\n cache.timestamp,\n cache.deletable.filter(keepBranch),\n cache.keep.filter(keepBranch),\n cache.worktrees.filter(keepTree),\n );\n }\n\n /**\n * ATOMIC write. Now that this file is shared by every worktree it has N concurrent writers (one\n * detached refresher per agent), and its readers are the guards' BLOCKING path. A plain\n * `writeFileSync` truncates before it writes, so a reader landing in that window gets a torn\n * document — the reader-side retry added in PR #526 cannot rescue a syntactically valid PREFIX.\n * Fixed where it belongs: temp file + `rename()`, which POSIX makes atomic. See AtomicFile.\n *\n * This buys ATOMIC READS, not lost-update protection: two read-modify-write cycles still end\n * last-writer-wins. That is acceptable and deliberate — this is a DERIVED cache, recomputed from\n * `gh` + git by the refresher, and wp-cleanup recomputes rather than trusting it before deleting\n * anything. Nothing here is transactional and no caller may assume it is.\n */\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n this.atomicFile.writeJsonAtomic(this.mergedBranchesPath(repoRoot), cache);\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n const result = this.capture(repoRoot, 'git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/']);\n if (!result.ok || result.out === '') return [];\n return result.out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n computeMergedBranches(repoRoot: string): MergedBranchesCache {\n const merged = this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n let latestMergedPr = 0;\n for (const entry of merged) {\n if (entry.pr > latestMergedPr) latestMergedPr = entry.pr;\n }\n const prs = new PrLookup(byBranch, this.fetchPrStates(repoRoot), latestMergedPr);\n\n const trees = this.worktrees.listWorktrees(repoRoot);\n const holder = new Map<string, string>();\n for (const tree of trees) {\n if (tree.branch !== '') holder.set(tree.branch, tree.path);\n }\n\n const deletable: DeletableBranch[] = [];\n const keep: DeletableBranch[] = [];\n\n for (const branch of this.localBranches(repoRoot)) {\n const verdict = this.classify(repoRoot, branch, prs);\n\n // A branch checked out in ANY worktree (including the branch we are standing on right here)\n // cannot be deleted — git refuses, and since the reap is ONE `git branch -D a b c`, a single\n // such branch would fail the entire command and strand the branches that would have deleted\n // fine. Spare it LOUDLY (into `keep`, with the reason) rather than dropping it silently: the\n // worktree list below is what actually reaps it, and a human should see the connection.\n const heldAt = holder.get(branch);\n if (heldAt !== undefined) {\n keep.push(new DeletableBranch(\n branch,\n `checked out in worktree '${heldAt}' — remove that worktree before deleting the branch`,\n verdict.entry.pr,\n verdict.entry.sha,\n verdict.entry.commits,\n verdict.entry.prState,\n CLASSIFICATION_IN_USE,\n ));\n continue;\n }\n\n if (verdict.deletable) deletable.push(verdict.entry);\n else keep.push(verdict.entry);\n }\n\n const worktrees = this.classifyWorktrees(repoRoot, trees, prs);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proof the branch cap uses — a MERGED PR, its own or that of the branch it\n * snapshots. Nothing else. It is spared when it is LOCKED BY SOMETHING STILL THERE (see\n * lockVerdict), when it is the worktree we are standing in right now (removing your own cwd is not\n * a thing to suggest to an agent), or when its branch is anything short of provably merged —\n * INCLUDING a branch with no commits yet, which is what every worktree looks like while an agent\n * is working in it. `deletable` means PROVABLY dead and nothing weaker; wp-cleanup reaps the\n * zero-commit ones on top of that, but only after checking on disk whether anybody is holding\n * them (see CLASSIFICATION_NO_COMMITS), which is a question these verdicts do not answer.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n prs: PrLookup,\n ): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true,\n CLASSIFICATION_PRUNABLE));\n continue;\n }\n // A dead agent's lock returns null here and the worktree is judged on its branch below,\n // exactly like an unlocked one — with `unlockBeforeRemove` set so the reap clears the lock.\n const staleLock = tree.locked ? this.locks.staleAgentLock(tree) : null;\n if (tree.locked && staleLock === null) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, this.locks.sparedReason(tree), 0, false, CLASSIFICATION_LOCKED));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false, CLASSIFICATION_CURRENT),\n staleLock));\n continue;\n }\n if (tree.branch === '') {\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false,\n CLASSIFICATION_DETACHED), staleLock));\n continue;\n }\n\n // The branch's OWN classification token rides along unchanged. That is what lets wp-cleanup\n // group probably-dead worktrees exactly the way it groups probably-dead branches, instead of\n // inventing a second, parallel notion of \"how dead is this\".\n const verdict = this.classify(repoRoot, tree.branch, prs);\n out.push(this.locks.annotate(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable,\n verdict.entry.classification), staleLock));\n }\n\n return out;\n }\n\n /**\n * Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n *\n * The verdict itself is unchanged. What is new is that EVERY entry now carries its tip SHA, its\n * unique-commit count and its PR state, so a `keep` can be shown to a human as a delete candidate\n * rather than just counted. This runs only in the detached refresher, so the two extra local git\n * calls per branch cost the blocking hook path nothing.\n */\n private classify(repoRoot: string, branch: string, prs: PrLookup): Verdict {\n const sha = this.tipSha(repoRoot, branch);\n const commits = this.commitsAheadOfMain(repoRoot, branch);\n const known = prs.state.get(branch);\n const prState = known ? known.state : '';\n\n const own = prs.merged.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch, `PR #${String(own)} merged`, own, sha, commits, prState || 'MERGED',\n CLASSIFICATION_MERGED_PR));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = prs.merged.get(base);\n if (basePr !== undefined) {\n return new Verdict(true, new DeletableBranch(\n branch,\n `squash-merge backup of '${base}' (PR #${String(basePr)} merged) — its job is done`,\n basePr, sha, commits, prState, CLASSIFICATION_BACKUP_OF_MERGED,\n ));\n }\n // The base has not MERGED — but a snapshot is a copy of its base by construction, so as\n // long as the base still exists this branch is provably not the only copy of anything.\n // Falling through to classifySpared told the human the opposite (\"may be the only copy in\n // existence\"), which is unanswerable-by-design and is why these accumulated.\n //\n // DELETABLE, not merely promptable. Asking about it was still asking a question with only\n // one honest answer — the base branch is right there holding the same work — and every\n // ask lands on a human who is being asked to authorise a delete the tooling can already\n // PROVE is lossless. That is the pile: `wp-start-upsert-pr` mints another snapshot on\n // every run, so the branch cap fills with copies nobody can be bothered to adjudicate.\n // The proof here is as strong as a merged PR (a live sibling ref holds the content), and\n // the reap archives to an `archive/<date>/<branch>` tag and logs a `recover=` first.\n if (this.branchExists(repoRoot, base)) {\n const basePrState = prs.state.get(base);\n const liveness = basePrState\n ? `PR #${String(basePrState.number)} ${basePrState.state}`\n : 'no PR yet';\n return new Verdict(true, new DeletableBranch(\n branch,\n `pre-merge snapshot of '${base}' (${liveness}) — '${base}' still exists and holds ` +\n `this work, so this is a spare copy, not the only one`,\n basePrState ? basePrState.number : 0, sha, commits, prState,\n CLASSIFICATION_BACKUP_OF_LIVE,\n ));\n }\n }\n\n // Zero commits of its own. NOT deletable BY THIS VERDICT — the verdicts answer \"is this\n // provably dead?\", and a husk is not. wp-cleanup reaps it anyway, after asking the one\n // question this service cannot: is anybody HOLDING it (uncommitted files, a live lock, the\n // tree you are standing in)? That check needs the worktree on disk, so it lives there and the\n // verdict stays exactly what it is — see CLASSIFICATION_NO_COMMITS.\n if (commits === 0) {\n return new Verdict(false, new DeletableBranch(\n branch,\n 'no commits of its own — identical to origin/main, so deleting the ref loses a NAME, not a '\n + 'commit. wp-cleanup reaps this by default; a worktree holding it is spared when it has '\n + 'uncommitted files or a live lock, i.e. when somebody is working in it',\n 0, sha, 0, prState, CLASSIFICATION_NO_COMMITS));\n }\n\n return new Verdict(false, this.classifySpared(repoRoot, branch, prs, sha, commits, prState));\n }\n\n /**\n * The three genuinely different reasons a branch survives the deletable proofs, ordered by how\n * confidently a human can say yes to deleting it.\n *\n * The safety posture is UNCHANGED: all three are still SPARED, never auto-deleted. The only thing\n * that changed is that wp-cleanup can now ask a question whose answer is knowable.\n */\n // eslint-disable-next-line @typescript-eslint/max-params\n private classifySpared(\n repoRoot: string, branch: string, prs: PrLookup, sha: string, commits: number, prState: string,\n ): DeletableBranch {\n const known = prs.state.get(branch);\n\n // A PR that CLOSED without merging, in a repo that has merged later PRs, is near-certainly the\n // abandoned first attempt at work that landed under a different number.\n if (known !== undefined && known.state === 'CLOSED' && prs.latestMergedPr > known.number) {\n return new DeletableBranch(\n branch,\n `PR #${String(known.number)} was CLOSED UNMERGED and later PRs (through ` +\n `#${String(prs.latestMergedPr)}) have merged — near-certainly superseded`,\n known.number, sha, commits, prState, CLASSIFICATION_SUPERSEDED,\n );\n }\n\n // `git cherry` compares by PATCH-ID, so it survives cherry-picks and rebases. It does NOT\n // survive a squash — which is exactly why this is a spared verdict and not a deletable proof.\n if (this.contentAlreadyInMain(repoRoot, branch)) {\n return new DeletableBranch(\n branch,\n `all ${String(commits)} commit(s) already have an equivalent in origin/main ` +\n `(git cherry) — content is not unique`,\n known ? known.number : 0, sha, commits, prState, CLASSIFICATION_CONTENT_IN_MAIN,\n );\n }\n\n const why = known\n ? `PR #${String(known.number)} is ${known.state} (not merged); holds ` +\n `${String(commits)} unique commit(s) — a human must decide`\n : `never had a PR; holds ${String(commits)} unique commit(s) that may be the only copy in existence`;\n return new DeletableBranch(\n branch, why, 0, sha, commits, prState, CLASSIFICATION_NEVER_PROPOSED);\n }\n\n /**\n * True when EVERY commit on the branch has a patch-equivalent already in origin/main.\n *\n * `git cherry origin/main <branch>` prints one line per commit: `+ <sha>` = not upstream,\n * `- <sha>` = an equivalent change IS upstream. All-minus means the content landed (typically by\n * cherry-pick or rebase-merge) even though the SHAs differ. Any failure returns false — \"cannot\n * prove the content is in main\" must never read as \"safe\".\n */\n private contentAlreadyInMain(repoRoot: string, branch: string): boolean {\n const result = this.capture(repoRoot, 'git', ['cherry', 'origin/main', branch]);\n if (!result.ok || result.out === '') return false;\n const lines = result.out.split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line !== '');\n if (lines.length === 0) return false;\n return lines.every((line: string): boolean => line.startsWith('-'));\n }\n\n /**\n * Does this local branch exist? Used to prove a snapshot is not the last copy of its work — the\n * base branch being present is that proof. Verifies it is a BRANCH (`refs/heads/`), not just any\n * resolvable rev, so a tag or a stray SHA sharing the name cannot stand in for the base.\n */\n private branchExists(repoRoot: string, branch: string): boolean {\n return this.capture(repoRoot, 'git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]).ok;\n }\n\n // Short tip SHA — the value that makes any delete of this branch reversible.\n private tipSha(repoRoot: string, branch: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--short', branch]);\n return result.ok ? result.out : '';\n }\n\n /**\n * Commits on `branch` that are not on origin/main. Returns -1 (\"assume it has work\") whenever the\n * count cannot be established — an unresolvable origin/main must never read as \"empty branch\".\n */\n private commitsAheadOfMain(repoRoot: string, branch: string): number {\n const result = this.capture(repoRoot, 'git', ['rev-list', '--count', `origin/main..${branch}`]);\n if (!result.ok) return -1;\n const count = Number(result.out);\n return Number.isInteger(count) ? count : -1;\n }\n\n /**\n * The ONE bulk network call. Every merged PR's head branch in a single round trip — no per-branch\n * lookups. Fails SOFT: if `gh` is missing, unauthenticated, or offline we return [], which makes\n * every branch a `keep`. The guard then deletes nothing rather than guessing.\n */\n private fetchMergedPrs(repoRoot: string): MergedBranch[] {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'merged',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName',\n ]);\n if (!result.ok || result.out === '') return [];\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n const out: MergedBranch[] = [];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n if (branch !== '' && pr > 0) out.push(new MergedBranch(branch, pr));\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n /**\n * The DISPLAY-only second lookup: every PR (any state) keyed by head branch, so a spared branch can\n * be shown to a human as \"PR #12 CLOSED (not merged)\" or \"no PR\" instead of an opaque name. Kept\n * SEPARATE from fetchMergedPrs on purpose — folding both into one `--state all` call would let a\n * flood of open/closed PRs push older MERGED ones out of the limit and silently stop reaping real\n * dead branches. Fails soft to an empty map: the verdicts do not depend on it.\n */\n private fetchPrStates(repoRoot: string): Map<string, PrRef> {\n const out = new Map<string, PrRef>();\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list',\n '--state', 'all',\n '--limit', String(MERGED_PR_LOOKUP_LIMIT),\n '--json', 'number,headRefName,state',\n ]);\n if (!result.ok || result.out === '') return out;\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const raw = JSON.parse(result.out) as RawMergedPr[];\n for (const entry of raw) {\n const branch = entry.headRefName ?? '';\n const pr = entry.number ?? 0;\n // `gh` lists newest-first, so the FIRST entry for a branch is its latest PR — keep it.\n if (branch !== '' && pr > 0 && !out.has(branch)) {\n out.set(branch, new PrRef(pr, entry.state ?? ''));\n }\n }\n return out;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return out;\n }\n }\n\n private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch => new DeletableBranch(\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.sha ?? '',\n entry.commits ?? -1,\n entry.prState ?? '',\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n ));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => {\n const verdict = new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n entry.classification ?? CLASSIFICATION_NEVER_PROPOSED,\n );\n verdict.unlockBeforeRemove = entry.unlockBeforeRemove ?? false;\n return verdict;\n });\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
|
package/src/worktrees.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a worktree is being worked in, and the reason in the words an operator gets shown.
|
|
3
|
+
*
|
|
4
|
+
* Data-only (per CLAUDE.md, classes for data). A bare boolean was the first cut and it collapsed
|
|
5
|
+
* "there are uncommitted files here" into "git would not answer me" — two different things to tell
|
|
6
|
+
* somebody who is deciding whether a directory is safe to remove.
|
|
7
|
+
*/
|
|
8
|
+
export declare class WorktreeWorkInFlight {
|
|
9
|
+
held: boolean;
|
|
10
|
+
reason: string;
|
|
11
|
+
constructor(held: boolean, reason: string);
|
|
12
|
+
}
|
|
1
13
|
export declare class Worktree {
|
|
2
14
|
path: string;
|
|
3
15
|
branch: string;
|
|
@@ -57,6 +69,22 @@ export declare class WorktreeService {
|
|
|
57
69
|
* forms rather than confidently printing the wrong one.
|
|
58
70
|
*/
|
|
59
71
|
isLinkedWorktree(root: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Is somebody working in this worktree — and if so, WHY do we say that?
|
|
74
|
+
*
|
|
75
|
+
* THE ONE CHECK THAT MAKES A ZERO-COMMIT WORKTREE UNREAPABLE. A ref with no commits of its own
|
|
76
|
+
* loses nothing when it is deleted — that is a property of the REF. A DIRECTORY is different:
|
|
77
|
+
* every worktree looks exactly like that husk from `git worktree add -b` until its first commit,
|
|
78
|
+
* which is precisely the window an agent is working in. `git status --porcelain` is what tells
|
|
79
|
+
* those two apart, and it is one cheap local spawn per candidate.
|
|
80
|
+
*
|
|
81
|
+
* It returns the REASON rather than a bare boolean because the two ways of being held are not the
|
|
82
|
+
* same sentence to a human: "it has uncommitted files" is a fact about the tree, and "git could
|
|
83
|
+
* not tell me" is a fact about our evidence. Both spare the worktree — unknown must fail SAFE,
|
|
84
|
+
* since the fail-safe direction for deleting a directory is to leave it — but reporting the
|
|
85
|
+
* second as the first tells an operator to go look for edits that are not there.
|
|
86
|
+
*/
|
|
87
|
+
workInFlight(worktreePath: string): WorktreeWorkInFlight;
|
|
60
88
|
/**
|
|
61
89
|
* The worktree record for the tree rooted at `root`, or null when it is not one of git's
|
|
62
90
|
* worktrees (or git could not answer). Callers use it to name the exact directory a
|
package/src/worktrees.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorktreeService = exports.Worktree = void 0;
|
|
3
|
+
exports.WorktreeService = exports.Worktree = exports.WorktreeWorkInFlight = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const child_process_1 = require("child_process");
|
|
6
6
|
const fs = tslib_1.__importStar(require("fs"));
|
|
@@ -25,6 +25,22 @@ const WORKTREE_KEY = 'worktree ';
|
|
|
25
25
|
const BRANCH_KEY = 'branch ';
|
|
26
26
|
const LOCKED_KEY = 'locked ';
|
|
27
27
|
const REFS_HEADS = 'refs/heads/';
|
|
28
|
+
/**
|
|
29
|
+
* Whether a worktree is being worked in, and the reason in the words an operator gets shown.
|
|
30
|
+
*
|
|
31
|
+
* Data-only (per CLAUDE.md, classes for data). A bare boolean was the first cut and it collapsed
|
|
32
|
+
* "there are uncommitted files here" into "git would not answer me" — two different things to tell
|
|
33
|
+
* somebody who is deciding whether a directory is safe to remove.
|
|
34
|
+
*/
|
|
35
|
+
class WorktreeWorkInFlight {
|
|
36
|
+
held;
|
|
37
|
+
reason;
|
|
38
|
+
constructor(held, reason) {
|
|
39
|
+
this.held = held;
|
|
40
|
+
this.reason = reason;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.WorktreeWorkInFlight = WorktreeWorkInFlight;
|
|
28
44
|
// Data-only (per CLAUDE.md, classes for data).
|
|
29
45
|
class Worktree {
|
|
30
46
|
path;
|
|
@@ -127,6 +143,31 @@ let WorktreeService = class WorktreeService {
|
|
|
127
143
|
return false;
|
|
128
144
|
}
|
|
129
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Is somebody working in this worktree — and if so, WHY do we say that?
|
|
148
|
+
*
|
|
149
|
+
* THE ONE CHECK THAT MAKES A ZERO-COMMIT WORKTREE UNREAPABLE. A ref with no commits of its own
|
|
150
|
+
* loses nothing when it is deleted — that is a property of the REF. A DIRECTORY is different:
|
|
151
|
+
* every worktree looks exactly like that husk from `git worktree add -b` until its first commit,
|
|
152
|
+
* which is precisely the window an agent is working in. `git status --porcelain` is what tells
|
|
153
|
+
* those two apart, and it is one cheap local spawn per candidate.
|
|
154
|
+
*
|
|
155
|
+
* It returns the REASON rather than a bare boolean because the two ways of being held are not the
|
|
156
|
+
* same sentence to a human: "it has uncommitted files" is a fact about the tree, and "git could
|
|
157
|
+
* not tell me" is a fact about our evidence. Both spare the worktree — unknown must fail SAFE,
|
|
158
|
+
* since the fail-safe direction for deleting a directory is to leave it — but reporting the
|
|
159
|
+
* second as the first tells an operator to go look for edits that are not there.
|
|
160
|
+
*/
|
|
161
|
+
workInFlight(worktreePath) {
|
|
162
|
+
const result = (0, child_process_1.spawnSync)('git', ['status', '--porcelain'], { cwd: worktreePath, encoding: 'utf8' });
|
|
163
|
+
if (result.status !== 0 || typeof result.stdout !== 'string') {
|
|
164
|
+
return new WorktreeWorkInFlight(true, '`git status` could not report on it, so it is not provably empty');
|
|
165
|
+
}
|
|
166
|
+
if (result.stdout.trim() !== '') {
|
|
167
|
+
return new WorktreeWorkInFlight(true, 'has uncommitted or untracked files');
|
|
168
|
+
}
|
|
169
|
+
return new WorktreeWorkInFlight(false, '');
|
|
170
|
+
}
|
|
130
171
|
/**
|
|
131
172
|
* The worktree record for the tree rooted at `root`, or null when it is not one of git's
|
|
132
173
|
* worktrees (or git could not answer). Callers use it to name the exact directory a
|
package/src/worktrees.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/worktrees.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 { toError } from './to-error';\n\n/**\n * Reading the git worktree list.\n *\n * WHY this exists separately from merged-branches: a worktree is a SECOND budget. Every worktree holds\n * a branch, so if worktree-held branches also counted against the local-branch cap, five worktrees\n * would consume the entire branch budget and no branch could ever be created again. The two are\n * therefore counted apart — held branches against the worktree cap, parked branches against the branch\n * cap — and this service is what tells the two apart.\n *\n * Unlike the merged-PR lookup, everything here is LOCAL and cheap (one `git worktree list`), so it is\n * safe to call on the guard's blocking path.\n */\n\n// `git worktree list --porcelain` emits blank-line-separated records. The first record is always the\n// main worktree. Keys are space-separated; `detached`, `bare` and `locked` may appear bare (no value).\nconst WORKTREE_KEY = 'worktree ';\nconst BRANCH_KEY = 'branch ';\nconst LOCKED_KEY = 'locked ';\nconst REFS_HEADS = 'refs/heads/';\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class Worktree {\n path: string;\n // Short branch name (refs/heads/ stripped). Empty when the worktree is detached or bare.\n branch: string;\n // The primary clone — the one that owns .git. Never counted against the cap, never removable.\n isMain: boolean;\n // git already knows this worktree's directory is gone; `git worktree prune` will clear it.\n prunable: boolean;\n // Somebody ran `git worktree lock`. WHO is a question only `lockReason` can answer.\n locked: boolean;\n /**\n * The `--reason` text verbatim, '' when the lock carried none.\n *\n * Load-bearing, not decoration: the Claude Code agent harness locks every worktree it opens for a\n * subagent and writes a machine-readable reason naming the agent and its pid. Without this field\n * `locked` collapsed a live agent, a dead agent and a human into one verdict, and wp-cleanup\n * reported all three as \"locked by a human\". See agent-worktree-lock.ts.\n */\n lockReason: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n path: string,\n branch: string,\n isMain: boolean,\n prunable: boolean,\n locked: boolean,\n // REQUIRED, with no default. A 5-arg construction would silently mean \"no reason recorded\",\n // which routes straight back to the bug this exists to fix — every agent lock unreadable and\n // its worktree spared forever. A missing argument must be a compile error, not a quiet ''.\n lockReason: string,\n ) {\n this.path = path;\n this.branch = branch;\n this.isMain = isMain;\n this.prunable = prunable;\n this.locked = locked;\n this.lockReason = lockReason;\n }\n}\n\n// Result of a captured git invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeService {\n /**\n * Every worktree, main one first. Fails SOFT to [] — a repo with no worktree support, or a git\n * that errors, must read as \"no worktrees\" so the cap fails OPEN rather than blocking on data we\n * do not have.\n */\n listWorktrees(repoRoot: string): Worktree[] {\n const result = this.capture(repoRoot, ['worktree', 'list', '--porcelain']);\n if (!result.ok || result.out === '') return [];\n return this.parsePorcelain(result.out);\n }\n\n // The linked worktrees — everything except the primary clone. This is what the cap counts.\n linkedWorktrees(repoRoot: string): Worktree[] {\n return this.listWorktrees(repoRoot).filter((tree: Worktree): boolean => !tree.isMain);\n }\n\n /**\n * Branch names checked out in ANY worktree, including the primary clone's own HEAD.\n *\n * Two callers, one reason: git flatly refuses to delete a branch that is checked out somewhere.\n * A held branch must never be proposed for `git branch -D` (the delete would fail and take the\n * whole reap command down with it), and it must not be counted as a parked branch either.\n */\n heldBranches(repoRoot: string): Set<string> {\n const held = new Set<string>();\n for (const tree of this.listWorktrees(repoRoot)) {\n if (tree.branch !== '') held.add(tree.branch);\n }\n return held;\n }\n\n /**\n * Am I standing in a LINKED worktree (as opposed to the primary clone)?\n *\n * This is the question every recovery message needs, because the two trees take different\n * commands: `git checkout main` fatals inside a linked worktree (\"main is already checked out\n * at <primary>\"), and a dead linked worktree is reaped with `git worktree remove`, not\n * `git branch -d`. A guard that cannot tell them apart must print BOTH forms and let the AI\n * guess — which is exactly how an AI ends up running the fatal one.\n *\n * The test is a single `statSync`, no process spawn: git gives a linked worktree a `.git` FILE\n * (a `gitdir:` pointer) where the primary clone has a `.git` DIRECTORY. This runs on the read\n * path, where reads vastly outnumber every other tool call, so the cost matters.\n *\n * It is a CHEAP FAST PATH over the authoritative answer, not a second authority. The authority is\n * `DotWebpieces.gitDirs` — `gitDir !== commonDir`, git's own canonical test, which also handles the\n * layouts a `.git` stat cannot see (`--separate-git-dir`, submodules). This is NOT calling it,\n * deliberately: that costs a `rev-parse` spawn per read. `worktree-identity.spec.ts` asserts the two\n * agree for the primary clone, an in-repo `.claude/worktrees/**` worktree and a sibling worktree\n * outside the repo — so if git ever changes the `.git` layout this fails there, not in the field.\n * Anything that needs to be RIGHT rather than cheap (tree identity, state paths) asks `gitDirs`.\n *\n * Returns FALSE on anything uncertain (no `.git` at all, unreadable, a submodule's `.git` file\n * in a non-worktree checkout). False is the fail-open direction here: callers then print both\n * forms rather than confidently printing the wrong one.\n */\n isLinkedWorktree(root: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return !fs.statSync(path.join(root, '.git')).isDirectory();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n /**\n * The worktree record for the tree rooted at `root`, or null when it is not one of git's\n * worktrees (or git could not answer). Callers use it to name the exact directory a\n * `git worktree remove` has to take — a reap instruction with the wrong path is worse than none.\n */\n currentWorktree(root: string): Worktree | null {\n const resolved = path.resolve(root);\n for (const tree of this.listWorktrees(root)) {\n if (path.resolve(tree.path) === resolved) return tree;\n }\n return null;\n }\n\n /**\n * Parse the porcelain records. A record starts at a `worktree <path>` line and runs to the blank\n * line; the FIRST record is the main worktree (git guarantees the ordering). A `prunable` worktree\n * still appears in the list, which is exactly why it can be reaped.\n */\n private parsePorcelain(out: string): Worktree[] {\n const trees: Worktree[] = [];\n let path = '';\n let branch = '';\n let prunable = false;\n let locked = false;\n let lockReason = '';\n\n const flush = (): void => {\n if (path === '') return;\n trees.push(new Worktree(path, branch, trees.length === 0, prunable, locked, lockReason));\n path = '';\n branch = '';\n prunable = false;\n locked = false;\n lockReason = '';\n };\n\n for (const raw of out.split('\\n')) {\n const line = raw.trim();\n if (line === '') {\n flush();\n } else if (line.startsWith(WORKTREE_KEY)) {\n // A new record begins — flush the previous one in case the blank line was missing.\n flush();\n path = line.slice(WORKTREE_KEY.length).trim();\n } else if (line.startsWith(BRANCH_KEY)) {\n const ref = line.slice(BRANCH_KEY.length).trim();\n branch = ref.startsWith(REFS_HEADS) ? ref.slice(REFS_HEADS.length) : ref;\n } else if (line === 'prunable' || line.startsWith('prunable ')) {\n prunable = true;\n } else if (line === LOCKED_KEY.trim() || line.startsWith(LOCKED_KEY)) {\n locked = true;\n // `locked` alone is a lock with no --reason; `locked <text>` carries the reason verbatim.\n lockReason = line.slice(LOCKED_KEY.length).trim();\n }\n }\n flush();\n\n return trees;\n }\n\n // Run a git command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, args: string[]): CmdCapture {\n const result = spawnSync('git', args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worktrees.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/worktrees.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 { toError } from './to-error';\n\n/**\n * Reading the git worktree list.\n *\n * WHY this exists separately from merged-branches: a worktree is a SECOND budget. Every worktree holds\n * a branch, so if worktree-held branches also counted against the local-branch cap, five worktrees\n * would consume the entire branch budget and no branch could ever be created again. The two are\n * therefore counted apart — held branches against the worktree cap, parked branches against the branch\n * cap — and this service is what tells the two apart.\n *\n * Unlike the merged-PR lookup, everything here is LOCAL and cheap (one `git worktree list`), so it is\n * safe to call on the guard's blocking path.\n */\n\n// `git worktree list --porcelain` emits blank-line-separated records. The first record is always the\n// main worktree. Keys are space-separated; `detached`, `bare` and `locked` may appear bare (no value).\nconst WORKTREE_KEY = 'worktree ';\nconst BRANCH_KEY = 'branch ';\nconst LOCKED_KEY = 'locked ';\nconst REFS_HEADS = 'refs/heads/';\n\n/**\n * Whether a worktree is being worked in, and the reason in the words an operator gets shown.\n *\n * Data-only (per CLAUDE.md, classes for data). A bare boolean was the first cut and it collapsed\n * \"there are uncommitted files here\" into \"git would not answer me\" — two different things to tell\n * somebody who is deciding whether a directory is safe to remove.\n */\nexport class WorktreeWorkInFlight {\n held: boolean;\n reason: string;\n\n constructor(held: boolean, reason: string) {\n this.held = held;\n this.reason = reason;\n }\n}\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class Worktree {\n path: string;\n // Short branch name (refs/heads/ stripped). Empty when the worktree is detached or bare.\n branch: string;\n // The primary clone — the one that owns .git. Never counted against the cap, never removable.\n isMain: boolean;\n // git already knows this worktree's directory is gone; `git worktree prune` will clear it.\n prunable: boolean;\n // Somebody ran `git worktree lock`. WHO is a question only `lockReason` can answer.\n locked: boolean;\n /**\n * The `--reason` text verbatim, '' when the lock carried none.\n *\n * Load-bearing, not decoration: the Claude Code agent harness locks every worktree it opens for a\n * subagent and writes a machine-readable reason naming the agent and its pid. Without this field\n * `locked` collapsed a live agent, a dead agent and a human into one verdict, and wp-cleanup\n * reported all three as \"locked by a human\". See agent-worktree-lock.ts.\n */\n lockReason: string;\n\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n path: string,\n branch: string,\n isMain: boolean,\n prunable: boolean,\n locked: boolean,\n // REQUIRED, with no default. A 5-arg construction would silently mean \"no reason recorded\",\n // which routes straight back to the bug this exists to fix — every agent lock unreadable and\n // its worktree spared forever. A missing argument must be a compile error, not a quiet ''.\n lockReason: string,\n ) {\n this.path = path;\n this.branch = branch;\n this.isMain = isMain;\n this.prunable = prunable;\n this.locked = locked;\n this.lockReason = lockReason;\n }\n}\n\n// Result of a captured git invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class WorktreeService {\n /**\n * Every worktree, main one first. Fails SOFT to [] — a repo with no worktree support, or a git\n * that errors, must read as \"no worktrees\" so the cap fails OPEN rather than blocking on data we\n * do not have.\n */\n listWorktrees(repoRoot: string): Worktree[] {\n const result = this.capture(repoRoot, ['worktree', 'list', '--porcelain']);\n if (!result.ok || result.out === '') return [];\n return this.parsePorcelain(result.out);\n }\n\n // The linked worktrees — everything except the primary clone. This is what the cap counts.\n linkedWorktrees(repoRoot: string): Worktree[] {\n return this.listWorktrees(repoRoot).filter((tree: Worktree): boolean => !tree.isMain);\n }\n\n /**\n * Branch names checked out in ANY worktree, including the primary clone's own HEAD.\n *\n * Two callers, one reason: git flatly refuses to delete a branch that is checked out somewhere.\n * A held branch must never be proposed for `git branch -D` (the delete would fail and take the\n * whole reap command down with it), and it must not be counted as a parked branch either.\n */\n heldBranches(repoRoot: string): Set<string> {\n const held = new Set<string>();\n for (const tree of this.listWorktrees(repoRoot)) {\n if (tree.branch !== '') held.add(tree.branch);\n }\n return held;\n }\n\n /**\n * Am I standing in a LINKED worktree (as opposed to the primary clone)?\n *\n * This is the question every recovery message needs, because the two trees take different\n * commands: `git checkout main` fatals inside a linked worktree (\"main is already checked out\n * at <primary>\"), and a dead linked worktree is reaped with `git worktree remove`, not\n * `git branch -d`. A guard that cannot tell them apart must print BOTH forms and let the AI\n * guess — which is exactly how an AI ends up running the fatal one.\n *\n * The test is a single `statSync`, no process spawn: git gives a linked worktree a `.git` FILE\n * (a `gitdir:` pointer) where the primary clone has a `.git` DIRECTORY. This runs on the read\n * path, where reads vastly outnumber every other tool call, so the cost matters.\n *\n * It is a CHEAP FAST PATH over the authoritative answer, not a second authority. The authority is\n * `DotWebpieces.gitDirs` — `gitDir !== commonDir`, git's own canonical test, which also handles the\n * layouts a `.git` stat cannot see (`--separate-git-dir`, submodules). This is NOT calling it,\n * deliberately: that costs a `rev-parse` spawn per read. `worktree-identity.spec.ts` asserts the two\n * agree for the primary clone, an in-repo `.claude/worktrees/**` worktree and a sibling worktree\n * outside the repo — so if git ever changes the `.git` layout this fails there, not in the field.\n * Anything that needs to be RIGHT rather than cheap (tree identity, state paths) asks `gitDirs`.\n *\n * Returns FALSE on anything uncertain (no `.git` at all, unreadable, a submodule's `.git` file\n * in a non-worktree checkout). False is the fail-open direction here: callers then print both\n * forms rather than confidently printing the wrong one.\n */\n isLinkedWorktree(root: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return !fs.statSync(path.join(root, '.git')).isDirectory();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n /**\n * Is somebody working in this worktree — and if so, WHY do we say that?\n *\n * THE ONE CHECK THAT MAKES A ZERO-COMMIT WORKTREE UNREAPABLE. A ref with no commits of its own\n * loses nothing when it is deleted — that is a property of the REF. A DIRECTORY is different:\n * every worktree looks exactly like that husk from `git worktree add -b` until its first commit,\n * which is precisely the window an agent is working in. `git status --porcelain` is what tells\n * those two apart, and it is one cheap local spawn per candidate.\n *\n * It returns the REASON rather than a bare boolean because the two ways of being held are not the\n * same sentence to a human: \"it has uncommitted files\" is a fact about the tree, and \"git could\n * not tell me\" is a fact about our evidence. Both spare the worktree — unknown must fail SAFE,\n * since the fail-safe direction for deleting a directory is to leave it — but reporting the\n * second as the first tells an operator to go look for edits that are not there.\n */\n workInFlight(worktreePath: string): WorktreeWorkInFlight {\n const result = spawnSync('git', ['status', '--porcelain'], { cwd: worktreePath, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') {\n return new WorktreeWorkInFlight(true,\n '`git status` could not report on it, so it is not provably empty');\n }\n if (result.stdout.trim() !== '') {\n return new WorktreeWorkInFlight(true, 'has uncommitted or untracked files');\n }\n return new WorktreeWorkInFlight(false, '');\n }\n\n /**\n * The worktree record for the tree rooted at `root`, or null when it is not one of git's\n * worktrees (or git could not answer). Callers use it to name the exact directory a\n * `git worktree remove` has to take — a reap instruction with the wrong path is worse than none.\n */\n currentWorktree(root: string): Worktree | null {\n const resolved = path.resolve(root);\n for (const tree of this.listWorktrees(root)) {\n if (path.resolve(tree.path) === resolved) return tree;\n }\n return null;\n }\n\n /**\n * Parse the porcelain records. A record starts at a `worktree <path>` line and runs to the blank\n * line; the FIRST record is the main worktree (git guarantees the ordering). A `prunable` worktree\n * still appears in the list, which is exactly why it can be reaped.\n */\n private parsePorcelain(out: string): Worktree[] {\n const trees: Worktree[] = [];\n let path = '';\n let branch = '';\n let prunable = false;\n let locked = false;\n let lockReason = '';\n\n const flush = (): void => {\n if (path === '') return;\n trees.push(new Worktree(path, branch, trees.length === 0, prunable, locked, lockReason));\n path = '';\n branch = '';\n prunable = false;\n locked = false;\n lockReason = '';\n };\n\n for (const raw of out.split('\\n')) {\n const line = raw.trim();\n if (line === '') {\n flush();\n } else if (line.startsWith(WORKTREE_KEY)) {\n // A new record begins — flush the previous one in case the blank line was missing.\n flush();\n path = line.slice(WORKTREE_KEY.length).trim();\n } else if (line.startsWith(BRANCH_KEY)) {\n const ref = line.slice(BRANCH_KEY.length).trim();\n branch = ref.startsWith(REFS_HEADS) ? ref.slice(REFS_HEADS.length) : ref;\n } else if (line === 'prunable' || line.startsWith('prunable ')) {\n prunable = true;\n } else if (line === LOCKED_KEY.trim() || line.startsWith(LOCKED_KEY)) {\n locked = true;\n // `locked` alone is a lock with no --reason; `locked <text>` carries the reason verbatim.\n lockReason = line.slice(LOCKED_KEY.length).trim();\n }\n }\n flush();\n\n return trees;\n }\n\n // Run a git command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, args: string[]): CmdCapture {\n const result = spawnSync('git', args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
|
|
@@ -225,12 +225,29 @@ flow are not interchangeable.
|
|
|
225
225
|
repairs them on every run. They are server-side — not in `webpieces.config.json`, not on disk — so
|
|
226
226
|
there is no key to configure and nothing for you to remember; if your token lacks repo-admin rights,
|
|
227
227
|
stage ③ prints the single `gh api` command to hand to an owner.
|
|
228
|
-
7. **`pnpm wp-cleanup`** — delete the local branches that are provably dead (merged PR,
|
|
229
|
-
backup of a merged branch
|
|
230
|
-
|
|
231
|
-
verdicts itself, deletes one branch per command,
|
|
228
|
+
7. **`pnpm wp-cleanup`** — delete the local branches and worktrees that are provably dead (merged PR,
|
|
229
|
+
squash-merge backup of a merged branch) plus every **zero-commit husk** — a ref identical to
|
|
230
|
+
`origin/main`, which loses a NAME and not a commit. Run it after the merge lands, or any time the
|
|
231
|
+
branch cap blocks you. It recomputes the verdicts itself, deletes one branch per command, and logs
|
|
232
232
|
every deletion with its pre-delete SHA plus a `recover=` command in
|
|
233
233
|
`.webpieces/logs/branch-mutations.log`. **Use this instead of `git branch -D`.**
|
|
234
|
+
|
|
235
|
+
A husk is spared only when somebody is provably holding it: a worktree with uncommitted or
|
|
236
|
+
untracked files, one locked by a live agent, the tree you are standing in, a detached HEAD — each
|
|
237
|
+
reported with that as its reason. Anything carrying UNIQUE COMMITS is never taken by default; it is
|
|
238
|
+
printed in a numbered, classified block, and you act on it with a flag:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
pnpm wp-cleanup --report # the whole picture, deletes nothing
|
|
242
|
+
pnpm wp-cleanup --delete-branches=all # or =none, or =1,3 by the numbers just printed
|
|
243
|
+
pnpm wp-cleanup --delete-worktrees=1,2
|
|
244
|
+
pnpm wp-cleanup --interactive # prompt even with no tty
|
|
245
|
+
pnpm wp-cleanup --help
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The numbers in a `--delete-*` flag are the numbers printed on the SAME run — wp-cleanup renumbers
|
|
249
|
+
from a fresh scan every time, and a number past the end of the block stops the run rather than
|
|
250
|
+
deleting the wrong ref. An explicit flag always beats the terminal sniff.
|
|
234
251
|
8. **`pnpm wp-push-dev`** — publish a **disposable copy** of this branch so a shared dev environment can
|
|
235
252
|
build it. No PR, no build gate, and your feature branch is never moved. See below.
|
|
236
253
|
9. **`pnpm wp-finish-push-dev`** — finalize a dev composition after you resolved its conflicts. Only
|
|
@@ -350,8 +367,10 @@ worktree still holds (and `wp-cleanup` deliberately spares worktree-held branche
|
|
|
350
367
|
```bash
|
|
351
368
|
git worktree prune && git worktree remove ../<feature-dir> && git branch -D <branch>
|
|
352
369
|
```
|
|
353
|
-
A branch is only ever deleted when it is backed by a **merged PR**, is a
|
|
354
|
-
holds **no commits of its own
|
|
370
|
+
A branch is only ever deleted without being asked about when it is backed by a **merged PR**, is a
|
|
371
|
+
squash-merge backup of one, or holds **no commits of its own** and nobody is holding it (no uncommitted
|
|
372
|
+
files, no live lock, not the tree you are standing in). Anything with unique commits is reported in a
|
|
373
|
+
numbered block for you to take with `--delete-branches=` / `--delete-worktrees=`, and
|
|
355
374
|
`.webpieces/merged-branches.json` carries the per-branch and per-worktree reason either way.
|
|
356
375
|
|
|
357
376
|
With `branch-creation-guard.autoReapMergedBranches: true` you will rarely see this cap at all — the
|