@webpieces/rules-config 0.4.391 → 0.4.392

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":"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","sourcesContent":["import { provideSingleton } from './di';\nimport { injectable } from 'inversify';\n\nimport { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { ConfigFile } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateMatchRulesSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { MatchRuleConfig } from './match-rules-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data).\n */\nexport class LoadedConfig {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly matchRules: readonly MatchRuleConfig[],\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n 'pr-creation-guard': 'pr-creation-or-push-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). `@provideSingleton` + injects {@link ConfigFile} so it appears\n * in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\nexport class ConfigLoader {\n constructor(private readonly configFile: ConfigFile) {}\n\n /**\n * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`\n * block, and throws one InformAiError listing every error. When no config file is found it returns\n * lenient empties/defaults (matching prior no-file behavior).\n */\n // webpieces-disable max-lines-new-methods -- the single load+validate pass is one cohesive method\n loadAndValidate(cwd: string): LoadedConfig {\n const configPath = this.configFile.findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = this.configFile.readRawConfig(configPath);\n const rulesSection = this.normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = this.normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches\n // by each rule's own `scope`). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(this.formatConfigErrorsBanner(errors));\n }\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n this.applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, this.mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = this.buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = this.parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = this.parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n }\n\n // Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n // that surface them in their fix hints. Only fills a gap — an explicit per-guard override wins.\n private applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n ): void {\n const prCreation = rules['pr-creation-or-push-guard'];\n if (prCreation && prCreation['upsertPrCommand'] === undefined) {\n prCreation['upsertPrCommand'] = commands.upsertPr;\n }\n const mergeInProgress = rules['merge-in-progress-guard'];\n if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {\n mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;\n }\n }\n\n // webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\n private mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n ): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n }\n\n // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths.\n // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n private parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n }\n\n // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[].\n // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\n private parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n return raw as MatchRuleConfig[];\n }\n\n private buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n ): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n }\n\n private normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n }\n\n // Assemble the validation-failure banner. Most of these errors are version skew, not bad config.\n private formatConfigErrorsBanner(errors: string[]): string {\n return (\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n') +\n `\\n\\n👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\\n` +\n ` 1. Run \\`pnpm install\\`. It is ALWAYS allowed through the guard (installer bypass), even ` +\n `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +\n `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +\n `node_modules here was never re-installed), so the running validator doesn't know the newer ` +\n `rule names/values yet. \\`pnpm install\\` syncs node_modules to the pinned version.\\n` +\n ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\\n` +\n ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +\n `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`\n );\n }\n}\n\n// Temporary migration delegator — consumers migrate to injecting ConfigLoader over follow-up PRs,\n// then this free function is removed. The logic now lives in the injected ConfigLoader class.\nconst configLoaderSvc = new ConfigLoader(new ConfigFile());\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ConfigLoader; removed once all 118 consumers inject it\nexport function loadAndValidate(cwd: string): LoadedConfig {\n return configLoaderSvc.loadAndValidate(cwd);\n}\n"]}
1
+ {"version":3,"file":"load-config.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-config.ts"],"names":[],"mappings":";;;AAmNA,0CAEC;;AArND,yCAA2D;AAE3D,uDAAwE;AACxE,+CAA2C;AAC3C,mDAA+C;AAC/C,6DAAoD;AACpD,uDAAkD;AAElD,mCAA0E;AAC1E,uDAAgK;AAEhK,iEAA8D;AAE9D;;;GAGG;AACH,MAAa,YAAY;IAGR;IACA;IACA;IACA;IACA;IACA;IACA;IARb,yDAAyD;IACzD,YACa,QAAwB,EACxB,WAAiC,EACjC,QAAwB,EACxB,MAAoB,EACpB,YAA0B,EAC1B,UAAsC,EACtC,UAAyB;QANzB,aAAQ,GAAR,QAAQ,CAAgB;QACxB,gBAAW,GAAX,WAAW,CAAsB;QACjC,aAAQ,GAAR,QAAQ,CAAgB;QACxB,WAAM,GAAN,MAAM,CAAc;QACpB,iBAAY,GAAZ,YAAY,CAAc;QAC1B,eAAU,GAAV,UAAU,CAA4B;QACtC,eAAU,GAAV,UAAU,CAAe;IACnC,CAAC;CACP;AAXD,oCAWC;AAED,qGAAqG;AACrG,oGAAoG;AACpG,qGAAqG;AACrG,MAAM,uBAAuB,GAAqC;IAC9D,kBAAkB,EAAE,gBAAgB;IACpC,mBAAmB,EAAE,2BAA2B;CACnD,CAAC;AAKF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IACQ;IAA7B,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEvD;;;;OAIG;IACH,kGAAkG;IAClG,eAAe,CAAC,GAAW;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,EAAE,CAAC;YACd,MAAM,aAAa,GAAG,IAAA,qCAAmB,EAAC,SAAS,CAAC,CAAC;YACrD,OAAO,IAAI,YAAY,CACnB,IAAI,sBAAc,CAAC,IAAI,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAClD,IAAI,2CAAoB,EAAE,EAC1B,aAAa,EACb,aAAa,CAAC,MAAM,EACpB,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,EACxB,EAAE,EACF,IAAI,CACP,CAAC;QACN,CAAC;QAED,MAAM,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QACjE,MAAM,YAAY,GAAG,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QAC9E,MAAM,iBAAiB,GAAG,IAAI,CAAC,uBAAuB,CAAC,cAAc,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;QACxF,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QAE/C,8FAA8F;QAC9F,iEAAiE;QACjE,MAAM,aAAa,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,iBAAiB,EAAE,CAAC;QAEhE,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,IAAI,EAAE,CAAC;QAE/C,MAAM,MAAM,GAAG;YACX,GAAG,IAAA,yCAAuB,EAAC,aAAa,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;YAC9D,GAAG,IAAA,0CAAwB,EAAC,YAAY,EAAE,iBAAiB,CAAC;YAC5D,GAAG,IAAA,yCAAuB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC;YACjE,GAAG,IAAA,sCAAoB,EAAC,cAAc,CAAC,YAAY,CAAC;YACpD,GAAG,IAAA,2CAAyB,EAAC,cAAc,CAAC,aAAa,CAAC,CAAC;SAC9D,CAAC;QACF,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,+BAAa,CAAC,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,qCAAmB,EAAC,cAAc,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QAC5E,IAAI,CAAC,oBAAoB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAEnD,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QACpE,MAAM,WAAW,GAAG,IAAI,GAAG,EAA8B,CAAC;QAC1D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;YACzB,GAAG,MAAM,CAAC,IAAI,CAAC,4BAAY,CAAC;YAC5B,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC;SAChC,CAAC,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAC9B,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,4BAAY,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,sBAAc,CAAC,WAAW,EAAE,uBAAuB,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEhG,MAAM,WAAW,GAAG,IAAI,CAAC,yBAAyB,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;QACzE,MAAM,UAAU,GAAG,IAAI,CAAC,eAAe,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAC;QAEvE,OAAO,IAAI,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACpH,CAAC;IAED,mGAAmG;IACnG,gGAAgG;IACxF,oBAAoB;IACxB,mEAAmE;IACnE,KAA8C,EAC9C,QAAwB;QAExB,MAAM,UAAU,GAAG,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACtD,IAAI,UAAU,IAAI,UAAU,CAAC,iBAAiB,CAAC,KAAK,SAAS,EAAE,CAAC;YAC5D,UAAU,CAAC,iBAAiB,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC;QACtD,CAAC;QACD,MAAM,eAAe,GAAG,KAAK,CAAC,yBAAyB,CAAC,CAAC;QACzD,IAAI,eAAe,IAAI,eAAe,CAAC,sBAAsB,CAAC,KAAK,SAAS,EAAE,CAAC;YAC3E,eAAe,CAAC,sBAAsB,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC;QACrE,CAAC;IACL,CAAC;IAED,kFAAkF;IAC1E,SAAS;IACb,wDAAwD;IACxD,QAA6C;IAC7C,wDAAwD;IACxD,YAAiD;QAEjD,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,0BAAkB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,0BAAkB,CAAC,YAA4B,CAAC,CAAC;QAC3E,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,0BAAkB,CAAC,QAAuB,CAAC,CAAC;QAE1E,iEAAiE;QACjE,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACrE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC7E,OAAO,IAAI,0BAAkB,CAAC,MAAqB,CAAC,CAAC;IACzD,CAAC;IAED,oFAAoF;IACpF,wFAAwF;IAChF,iBAAiB,CAAC,GAAY;QAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,iCAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACnG,4FAA4F;QAC5F,MAAM,CAAC,GAAG,GAA+B,CAAC;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7F,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,IAAI,iCAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,oFAAoF;IACpF,oGAAoG;IAC5F,eAAe,CAAC,GAAY;QAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACnC,OAAO,GAAwB,CAAC;IACpC,CAAC;IAEO,yBAAyB;IAC7B,4FAA4F;IAC5F,QAAiD,EACjD,QAAkB;QAElB,MAAM,KAAK,GAAG,IAAI,2CAAoB,EAAE,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtC,4EAA4E;YAC3E,KAAiC,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;QACD,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IAEO,uBAAuB,CAAC,OAAuB;QACnD,MAAM,GAAG,GAAmB,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,GAAG,CAAC;IACf,CAAC;IAED,iGAAiG;IACzF,wBAAwB,CAAC,MAAgB;QAC7C,OAAO,CACH,6BAA6B,MAAM,CAAC,MAAM,iDAAiD;YAC3F,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACtC,yFAAyF;YACzF,6FAA6F;YAC7F,2FAA2F;YAC3F,sFAAsF;YACtF,6FAA6F;YAC7F,qFAAqF;YACrF,sGAAsG;YACtG,gGAAgG;YAChG,uFAAuF,CAC1F,CAAC;IACN,CAAC;CACJ,CAAA;AA7JY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAEI,wBAAU;GAD1C,YAAY,CA6JxB;AAED,kGAAkG;AAClG,8FAA8F;AAC9F,MAAM,eAAe,GAAG,IAAI,YAAY,CAAC,IAAI,wBAAU,EAAE,CAAC,CAAC;AAE3D,2IAA2I;AAC3I,SAAgB,eAAe,CAAC,GAAW;IACvC,OAAO,eAAe,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC","sourcesContent":["import { injectable, bindingScopeValues } from 'inversify';\n\nimport { buildCommandsConfig, CommandsConfig } from './commands-config';\nimport { ConfigFile } from './config-file';\nimport { defaultRules } from './default-rules';\nimport { ExcludePaths } from './exclude-hook-paths';\nimport { InformAiError } from './inform-ai-error';\nimport { PrGateConfig } from './pr-gate-config';\nimport { ResolvedConfig, ResolvedRuleConfig, RuleOptions } from './types';\nimport { validateCommandsSection, validateExcludePaths, validateMatchRulesSection, validateSectionPlacement, validateWebpiecesConfig } from './validate-config';\nimport { MatchRuleConfig } from './match-rules-config';\nimport { WebpiecesRulesConfig } from './WebpiecesRulesConfig';\n\n/**\n * Everything a consumer might need from webpieces.config.json, produced from ONE parse + ONE\n * validation pass. Data-only (per CLAUDE.md, classes for data).\n */\nexport class LoadedConfig {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly resolved: ResolvedConfig,\n readonly rulesConfig: WebpiecesRulesConfig,\n readonly commands: CommandsConfig,\n readonly prGate: PrGateConfig,\n readonly excludePaths: ExcludePaths,\n readonly matchRules: readonly MatchRuleConfig[],\n readonly configPath: string | null,\n ) {}\n}\n\n// Config keys that were renamed. A project's webpieces.config.json may still use the OLD key (it can\n// legitimately lag the published rules-config by a release), so normalize any deprecated key to its\n// canonical name BEFORE validation/placement/loading — every downstream consumer then sees one name.\nconst DEPRECATED_RULE_ALIASES: Readonly<Record<string, string>> = {\n 'pr-merge-cleanup': 'pr-merge-guard',\n 'pr-creation-guard': 'pr-creation-or-push-guard',\n};\n\n// webpieces-disable no-any-unknown -- opaque per-rule option bags from consumer JSON, validated later\ntype RuleSectionMap = Record<string, Record<string, unknown>>;\n\n/**\n * The single load+validate entry point for ALL consumers (ai-hook-rules, code-rules,\n * nx-webpieces-rules, pr-gate scripts). `@injectable(bindingScopeValues.Singleton)` + injects {@link ConfigFile} so it appears\n * in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class ConfigLoader {\n constructor(private readonly configFile: ConfigFile) {}\n\n /**\n * Reads webpieces.config.json once, validates BOTH the `rules` map and the top-level `pr-gate`\n * block, and throws one InformAiError listing every error. When no config file is found it returns\n * lenient empties/defaults (matching prior no-file behavior).\n */\n // webpieces-disable max-lines-new-methods -- the single load+validate pass is one cohesive method\n loadAndValidate(cwd: string): LoadedConfig {\n const configPath = this.configFile.findConfigFile(cwd);\n if (!configPath) {\n const emptyCommands = buildCommandsConfig(undefined);\n return new LoadedConfig(\n new ResolvedConfig(new Map(), new Set(), [], null),\n new WebpiecesRulesConfig(),\n emptyCommands,\n emptyCommands.prGate,\n new ExcludePaths([], []),\n [],\n null,\n );\n }\n\n const consumerConfig = this.configFile.readRawConfig(configPath);\n const rulesSection = this.normalizeDeprecatedKeys(consumerConfig.rules || {});\n const hookGuardsSection = this.normalizeDeprecatedKeys(consumerConfig.hookGuards || {});\n const legacyPrGate = consumerConfig['pr-gate'];\n\n // rules + hookGuards are validated/loaded as one flat name→config map (the runtime dispatches\n // by each rule's own `scope`). Placement is enforced separately.\n const overrideRules = { ...rulesSection, ...hookGuardsSection };\n\n const rulesDir = consumerConfig.rulesDir ?? [];\n\n const errors = [\n ...validateWebpiecesConfig(overrideRules, rulesDir.length > 0),\n ...validateSectionPlacement(rulesSection, hookGuardsSection),\n ...validateCommandsSection(consumerConfig.commands, legacyPrGate),\n ...validateExcludePaths(consumerConfig.excludePaths),\n ...validateMatchRulesSection(consumerConfig['match-rules']),\n ];\n if (errors.length > 0) {\n throw new InformAiError(this.formatConfigErrorsBanner(errors));\n }\n const commands = buildCommandsConfig(consumerConfig.commands, legacyPrGate);\n this.applyCommandDefaults(overrideRules, commands);\n\n const userConfiguredRuleNames = new Set(Object.keys(overrideRules));\n const mergedRules = new Map<string, ResolvedRuleConfig>();\n const allRuleNames = new Set([\n ...Object.keys(defaultRules),\n ...Object.keys(overrideRules),\n ]);\n for (const name of allRuleNames) {\n mergedRules.set(name, this.mergeRule(defaultRules[name], overrideRules[name]));\n }\n const resolved = new ResolvedConfig(mergedRules, userConfiguredRuleNames, rulesDir, configPath);\n\n const rulesConfig = this.buildWebpiecesRulesConfig(overrideRules, rulesDir);\n const excludePaths = this.parseExcludePaths(consumerConfig.excludePaths);\n const matchRules = this.parseMatchRules(consumerConfig['match-rules']);\n\n return new LoadedConfig(resolved, rulesConfig, commands, commands.prGate, excludePaths, matchRules, configPath);\n }\n\n // Inject the canonical command strings (from the `commands` section) as the DEFAULT for the guards\n // that surface them in their fix hints. Only fills a gap — an explicit per-guard override wins.\n private applyCommandDefaults(\n // webpieces-disable no-any-unknown -- opaque merged rule/guard map\n rules: Record<string, Record<string, unknown>>,\n commands: CommandsConfig,\n ): void {\n const prCreation = rules['pr-creation-or-push-guard'];\n if (prCreation && prCreation['upsertPrCommand'] === undefined) {\n prCreation['upsertPrCommand'] = commands.upsertPr;\n }\n const mergeInProgress = rules['merge-in-progress-guard'];\n if (mergeInProgress && mergeInProgress['mergeCompleteCommand'] === undefined) {\n mergeInProgress['mergeCompleteCommand'] = commands.mergeComplete;\n }\n }\n\n // webpieces-disable no-any-unknown -- merging opaque option bags from config JSON\n private mergeRule(\n // webpieces-disable no-any-unknown -- opaque option bag\n baseRule: Record<string, unknown> | undefined,\n // webpieces-disable no-any-unknown -- opaque option bag\n overrideRule: Record<string, unknown> | undefined,\n ): ResolvedRuleConfig {\n if (!baseRule && !overrideRule) return new ResolvedRuleConfig({ mode: 'OFF' });\n if (!baseRule) return new ResolvedRuleConfig(overrideRule! as RuleOptions);\n if (!overrideRule) return new ResolvedRuleConfig(baseRule as RuleOptions);\n\n // webpieces-disable no-any-unknown -- building merged option bag\n const merged: Record<string, unknown> = {};\n for (const key of Object.keys(baseRule)) merged[key] = baseRule[key];\n for (const key of Object.keys(overrideRule)) merged[key] = overrideRule[key];\n return new ResolvedRuleConfig(merged as RuleOptions);\n }\n\n // Parse the (already-validated) raw excludePaths block into the typed ExcludePaths.\n // webpieces-disable no-any-unknown -- `raw` is opaque consumer JSON until narrowed here\n private parseExcludePaths(raw: unknown): ExcludePaths {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return new ExcludePaths([], []);\n // webpieces-disable no-any-unknown -- validateExcludePaths already proved both are string[]\n const s = raw as Record<string, string[]>;\n const rules = Array.isArray(s['rules']) ? s['rules'].filter(p => typeof p === 'string') : [];\n const guards = Array.isArray(s['guards']) ? s['guards'].filter(p => typeof p === 'string') : [];\n return new ExcludePaths(rules, guards);\n }\n\n // Parse the (already-validated) raw match-rules array into typed MatchRuleConfig[].\n // webpieces-disable no-any-unknown -- validated array; each entry cast to the typed MatchRuleConfig\n private parseMatchRules(raw: unknown): MatchRuleConfig[] {\n if (!Array.isArray(raw)) return [];\n return raw as MatchRuleConfig[];\n }\n\n private buildWebpiecesRulesConfig(\n // webpieces-disable no-any-unknown -- JSON values are opaque until assigned to typed fields\n rawRules: Record<string, Record<string, unknown>>,\n rulesDir: string[],\n ): WebpiecesRulesConfig {\n const typed = new WebpiecesRulesConfig();\n for (const key of Object.keys(rawRules)) {\n // webpieces-disable no-any-unknown -- dynamic key assignment to typed class\n (typed as Record<string, unknown>)[key] = rawRules[key];\n }\n typed.rulesDir = rulesDir;\n return typed;\n }\n\n private normalizeDeprecatedKeys(section: RuleSectionMap): RuleSectionMap {\n const out: RuleSectionMap = {};\n for (const key of Object.keys(section)) {\n out[DEPRECATED_RULE_ALIASES[key] ?? key] = section[key];\n }\n return out;\n }\n\n // Assemble the validation-failure banner. Most of these errors are version skew, not bad config.\n private formatConfigErrorsBanner(errors: string[]): string {\n return (\n `webpieces.config.json has ${errors.length} validation error(s) — fix ALL, then retry:\\n\\n` +\n errors.map(e => ` • ${e}`).join('\\n') +\n `\\n\\n👉 FIX ORDER (do NOT start by deleting keys — that usually deletes VALID config):\\n` +\n ` 1. Run \\`pnpm install\\`. It is ALWAYS allowed through the guard (installer bypass), even ` +\n `while this config is invalid. This is the #1 cause: your installed @webpieces guard is a ` +\n `release BEHIND webpieces.config.json (a dep bump updated the config + lockfile, but ` +\n `node_modules here was never re-installed), so the running validator doesn't know the newer ` +\n `rule names/values yet. \\`pnpm install\\` syncs node_modules to the pinned version.\\n` +\n ` 2. Retry your command. If the errors are gone, you're DONE — do not touch webpieces.config.json.\\n` +\n ` 3. ONLY if an error survives a fresh install is it a genuine typo / removed / renamed rule. ` +\n `Then edit webpieces.config.json (edits to it are ALWAYS allowed) to fix each • above.`\n );\n }\n}\n\n// Temporary migration delegator — consumers migrate to injecting ConfigLoader over follow-up PRs,\n// then this free function is removed. The logic now lives in the injected ConfigLoader class.\nconst configLoaderSvc = new ConfigLoader(new ConfigFile());\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to ConfigLoader; removed once all 118 consumers inject it\nexport function loadAndValidate(cwd: string): LoadedConfig {\n return configLoaderSvc.loadAndValidate(cwd);\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.
3
- * `@provideSingleton` so it can be injected and appear in the rules-config DI design.
3
+ * `@injectable(bindingScopeValues.Singleton)` so it can be injected and appear in the rules-config DI design.
4
4
  */
5
5
  export declare class TemplateWriter {
6
6
  loadTemplate(name: string): string;
@@ -7,13 +7,12 @@ exports.writeTemplate = writeTemplate;
7
7
  const tslib_1 = require("tslib");
8
8
  const fs = tslib_1.__importStar(require("fs"));
9
9
  const path = tslib_1.__importStar(require("path"));
10
- const di_1 = require("./di");
11
10
  const inversify_1 = require("inversify");
12
11
  const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
13
12
  const DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';
14
13
  /**
15
14
  * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.
16
- * `@provideSingleton` so it can be injected and appear in the rules-config DI design.
15
+ * `@injectable(bindingScopeValues.Singleton)` so it can be injected and appear in the rules-config DI design.
17
16
  */
18
17
  let TemplateWriter = class TemplateWriter {
19
18
  loadTemplate(name) {
@@ -35,8 +34,7 @@ let TemplateWriter = class TemplateWriter {
35
34
  };
36
35
  exports.TemplateWriter = TemplateWriter;
37
36
  exports.TemplateWriter = TemplateWriter = tslib_1.__decorate([
38
- (0, di_1.provideSingleton)(),
39
- (0, inversify_1.injectable)()
37
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
40
38
  ], TemplateWriter);
41
39
  // Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.
42
40
  const templateWriterSvc = new TemplateWriter();
@@ -1 +1 @@
1
- {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":";;;AAqCA,oCAEC;AAED,wDAMC;AAED,sCAMC;;AAvDD,+CAAyB;AACzB,mDAA6B;AAC7B,6BAAwC;AACxC,yCAAuC;AAEvC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC9D,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAEtD;;;GAGG;AAGI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB,YAAY,CAAC,IAAY;QACrB,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;IAED,sBAAsB,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QAClG,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QAChC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAED,aAAa,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QACzF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AAlBY,wCAAc;yBAAd,cAAc;IAF1B,IAAA,qBAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,cAAc,CAkB1B;AAED,qGAAqG;AACrG,MAAM,iBAAiB,GAAG,IAAI,cAAc,EAAE,CAAC;AAE/C,SAAgB,YAAY,CAAC,IAAY;IACrC,OAAO,iBAAiB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,sBAAsB,CAClC,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,iBAAiB,CAAC,sBAAsB,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC/E,CAAC;AAED,SAAgB,aAAa,CACzB,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,OAAO,iBAAiB,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from './di';\nimport { injectable } from 'inversify';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\nconst DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';\n\n/**\n * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.\n * `@provideSingleton` so it can be injected and appear in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\nexport class TemplateWriter {\n loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n }\n\n writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): void {\n const dest = path.join(workspaceRoot, instructDir, name);\n if (fs.existsSync(dest)) return;\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n }\n\n writeTemplate(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): string {\n const dest = path.join(workspaceRoot, instructDir, name);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n return dest;\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.\nconst templateWriterSvc = new TemplateWriter();\n\nexport function loadTemplate(name: string): string {\n return templateWriterSvc.loadTemplate(name);\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): void {\n templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): string {\n return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);\n}\n"]}
1
+ {"version":3,"file":"load-template.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/load-template.ts"],"names":[],"mappings":";;;AAmCA,oCAEC;AAED,wDAMC;AAED,sCAMC;;AArDD,+CAAyB;AACzB,mDAA6B;AAC7B,yCAA2D;AAE3D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC9D,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAEtD;;;GAGG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB,YAAY,CAAC,IAAY;QACrB,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;IAED,sBAAsB,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QAClG,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO;QAChC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IAED,aAAa,CAAC,aAAqB,EAAE,IAAY,EAAE,cAAsB,oBAAoB;QACzF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QACzD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AAlBY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,cAAc,CAkB1B;AAED,qGAAqG;AACrG,MAAM,iBAAiB,GAAG,IAAI,cAAc,EAAE,CAAC;AAE/C,SAAgB,YAAY,CAAC,IAAY;IACrC,OAAO,iBAAiB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,sBAAsB,CAClC,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,iBAAiB,CAAC,sBAAsB,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC/E,CAAC;AAED,SAAgB,aAAa,CACzB,aAAqB,EACrB,IAAY,EACZ,cAAsB,oBAAoB;IAE1C,OAAO,iBAAiB,CAAC,aAAa,CAAC,aAAa,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'templates');\nconst DEFAULT_INSTRUCT_DIR = '.webpieces/instruct-ai';\n\n/**\n * Writes the AI-facing instruct-ai template docs under `<workspaceRoot>/.webpieces/instruct-ai/`.\n * `@injectable(bindingScopeValues.Singleton)` so it can be injected and appear in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class TemplateWriter {\n loadTemplate(name: string): string {\n return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf-8');\n }\n\n writeTemplateIfMissing(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): void {\n const dest = path.join(workspaceRoot, instructDir, name);\n if (fs.existsSync(dest)) return;\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n }\n\n writeTemplate(workspaceRoot: string, name: string, instructDir: string = DEFAULT_INSTRUCT_DIR): string {\n const dest = path.join(workspaceRoot, instructDir, name);\n fs.mkdirSync(path.dirname(dest), { recursive: true });\n fs.writeFileSync(dest, this.loadTemplate(name), 'utf-8');\n return dest;\n }\n}\n\n// Temporary migration delegators — consumers migrate to injecting TemplateWriter over follow-up PRs.\nconst templateWriterSvc = new TemplateWriter();\n\nexport function loadTemplate(name: string): string {\n return templateWriterSvc.loadTemplate(name);\n}\n\nexport function writeTemplateIfMissing(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): void {\n templateWriterSvc.writeTemplateIfMissing(workspaceRoot, name, instructDir);\n}\n\nexport function writeTemplate(\n workspaceRoot: string,\n name: string,\n instructDir: string = DEFAULT_INSTRUCT_DIR,\n): string {\n return templateWriterSvc.writeTemplate(workspaceRoot, name, instructDir);\n}\n"]}
@@ -21,7 +21,7 @@ export declare class MainSyncLock {
21
21
  }
22
22
  /**
23
23
  * Reads/writes the main-sync cache + lock and computes the slow "is my branch healthy vs origin/main?"
24
- * status. `@provideSingleton` so it's injectable and drawn in the rules-config DI design.
24
+ * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in the rules-config DI design.
25
25
  */
26
26
  export declare class MainSyncStatusService {
27
27
  mainSyncStatusPath(repoRoot: string): string;
@@ -18,7 +18,6 @@ const tslib_1 = require("tslib");
18
18
  const child_process_1 = require("child_process");
19
19
  const fs = tslib_1.__importStar(require("fs"));
20
20
  const path = tslib_1.__importStar(require("path"));
21
- const di_1 = require("./di");
22
21
  const inversify_1 = require("inversify");
23
22
  const constants_1 = require("./constants");
24
23
  const to_error_1 = require("./to-error");
@@ -76,7 +75,7 @@ class MainSyncLock {
76
75
  exports.MainSyncLock = MainSyncLock;
77
76
  /**
78
77
  * Reads/writes the main-sync cache + lock and computes the slow "is my branch healthy vs origin/main?"
79
- * status. `@provideSingleton` so it's injectable and drawn in the rules-config DI design.
78
+ * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in the rules-config DI design.
80
79
  */
81
80
  let MainSyncStatusService = class MainSyncStatusService {
82
81
  mainSyncStatusPath(repoRoot) {
@@ -286,8 +285,7 @@ let MainSyncStatusService = class MainSyncStatusService {
286
285
  };
287
286
  exports.MainSyncStatusService = MainSyncStatusService;
288
287
  exports.MainSyncStatusService = MainSyncStatusService = tslib_1.__decorate([
289
- (0, di_1.provideSingleton)(),
290
- (0, inversify_1.injectable)()
288
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
291
289
  ], MainSyncStatusService);
292
290
  // Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.
293
291
  const mainSyncSvc = new MainSyncStatusService();
@@ -1 +1 @@
1
- {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from './di';\nimport { injectable } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n // An OPEN (not merged) PR tracking this branch, if any — '' = none or not-yet-known. Advisory.\n // Kept OUT of the positional constructor (a defaulted field) so existing call sites don't churn.\n openPr: string = '';\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher\n// process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.\nexport class MainSyncLock {\n state: string;\n started: number;\n pid: number;\n\n constructor(state: string, started: number, pid: number = 0) {\n this.state = state;\n this.started = started;\n this.pid = pid;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n openPr?: string;\n}\n\ninterface RawLock {\n state?: string;\n started?: number;\n pid?: number;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n/**\n * Reads/writes the main-sync cache + lock and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@provideSingleton` so it's injectable and drawn in the rules-config DI design.\n */\n@provideSingleton()\n@injectable()\nexport class MainSyncStatusService {\n mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n }\n\n mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = this.mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n const status = new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n status.openPr = raw.openPr ?? '';\n return status;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = this.mainSyncStatusPath(repoRoot);\n this.ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n }\n\n readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = this.mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n }\n\n // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.\n isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n }\n\n // True when another refresher is actively running and we should NOT start a second one.\n isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = this.readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (this.isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return this.isProcessAlive(lock.pid);\n }\n\n inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n }\n\n finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs. Never run on the hook's blocking path.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = this.gitBranch(repoRoot);\n const mergedPr = this.detectMergedPr(repoRoot, branch);\n const openPr = this.detectOpenPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n // Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n private ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n }\n\n // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.\n private isProcessAlive(pid: number): boolean {\n if (pid <= 0) return true;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n return !error.message.includes('ESRCH');\n }\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n\n // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n // Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists.\n private detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = this.capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n }\n\n // An OPEN PR tracking this branch, if any. Best-effort/advisory.\n private detectOpenPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = this.capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'open', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncLockPath(repoRoot: string): string { return mainSyncSvc.mainSyncLockPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null { return mainSyncSvc.readMainSyncLock(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock { return mainSyncSvc.inProcessLock(now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function finishedLock(started: number): MainSyncLock { return mainSyncSvc.finishedLock(started); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
1
+ {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n // An OPEN (not merged) PR tracking this branch, if any — '' = none or not-yet-known. Advisory.\n // Kept OUT of the positional constructor (a defaulted field) so existing call sites don't churn.\n openPr: string = '';\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher\n// process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.\nexport class MainSyncLock {\n state: string;\n started: number;\n pid: number;\n\n constructor(state: string, started: number, pid: number = 0) {\n this.state = state;\n this.started = started;\n this.pid = pid;\n }\n}\n\n// Raw JSON shapes for the cast at the parse boundary.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n openPr?: string;\n}\n\ninterface RawLock {\n state?: string;\n started?: number;\n pid?: number;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n/**\n * Reads/writes the main-sync cache + lock and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in the rules-config DI design.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class MainSyncStatusService {\n mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n }\n\n mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = this.mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n const status = new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n status.openPr = raw.openPr ?? '';\n return status;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = this.mainSyncStatusPath(repoRoot);\n this.ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n }\n\n readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = this.mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n }\n\n // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.\n isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n }\n\n // True when another refresher is actively running and we should NOT start a second one.\n isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = this.readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (this.isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return this.isProcessAlive(lock.pid);\n }\n\n inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n }\n\n finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs. Never run on the hook's blocking path.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = this.gitBranch(repoRoot);\n const mergedPr = this.detectMergedPr(repoRoot, branch);\n const openPr = this.detectOpenPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n // Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n private ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n }\n\n // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.\n private isProcessAlive(pid: number): boolean {\n if (pid <= 0) return true;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n return !error.message.includes('ESRCH');\n }\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n\n // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n // Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists.\n private detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = this.capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n }\n\n // An OPEN PR tracking this branch, if any. Best-effort/advisory.\n private detectOpenPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = this.capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'open', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncLockPath(repoRoot: string): string { return mainSyncSvc.mainSyncLockPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null { return mainSyncSvc.readMainSyncLock(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock { return mainSyncSvc.inProcessLock(now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function finishedLock(started: number): MainSyncLock { return mainSyncSvc.finishedLock(started); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
@@ -5,7 +5,6 @@ const tslib_1 = require("tslib");
5
5
  const child_process_1 = require("child_process");
6
6
  const fs = tslib_1.__importStar(require("fs"));
7
7
  const path = tslib_1.__importStar(require("path"));
8
- const di_1 = require("./di");
9
8
  const inversify_1 = require("inversify");
10
9
  const constants_1 = require("./constants");
11
10
  const to_error_1 = require("./to-error");
@@ -308,8 +307,7 @@ let MergedBranchesService = class MergedBranchesService {
308
307
  };
309
308
  exports.MergedBranchesService = MergedBranchesService;
310
309
  exports.MergedBranchesService = MergedBranchesService = tslib_1.__decorate([
311
- (0, di_1.provideSingleton)(),
312
- (0, inversify_1.injectable)(),
310
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
313
311
  tslib_1.__metadata("design:paramtypes", [worktrees_1.WorktreeService])
314
312
  ], MergedBranchesService);
315
313
  //# sourceMappingURL=merged-branches.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { provideSingleton } from './di';\nimport { injectable } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n// A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n\n constructor(branch: string, reason: string, pr: number) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n\n constructor(path: string, branch: string, reason: string, pr: number, deletable: boolean) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\n\n// 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}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n}\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@provideSingleton()\n@injectable()\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(private readonly worktrees: WorktreeService = new WorktreeService()) {}\n\n mergedBranchesPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n const cachePath = this.mergedBranchesPath(repoRoot);\n fs.mkdirSync(path.dirname(cachePath), { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\\n');\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n const result = this.capture(repoRoot, 'git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/']);\n if (!result.ok || result.out === '') return [];\n return result.out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n computeMergedBranches(repoRoot: string): MergedBranchesCache {\n const merged = this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n\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, byBranch);\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 ));\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, byBranch);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proofs the branch cap uses (merged PR, backup-of-merged, or zero commits of\n * its own). It is spared when it is LOCKED (a human said \"do not touch\"), when it is the worktree we\n * are standing in right now (removing your own cwd is not a thing to suggest to an agent), or when\n * its branch still holds unmerged work.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n byBranch: Map<string, number>,\n ): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true));\n continue;\n }\n if (tree.locked) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'locked by a human — do not touch', 0, false));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false));\n continue;\n }\n if (tree.branch === '') {\n out.push(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false));\n continue;\n }\n\n const verdict = this.classify(repoRoot, tree.branch, byBranch);\n out.push(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable));\n }\n\n return out;\n }\n\n // Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n private classify(repoRoot: string, branch: string, byBranch: Map<string, number>): Verdict {\n const own = byBranch.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(branch, `PR #${String(own)} merged`, own));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = byBranch.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,\n ));\n }\n }\n\n // The one git-local signal that squash-merge CANNOT corrupt: a branch with zero commits of its\n // own holds no work, so deleting it can lose nothing. (Squash breaks patch-id and ancestry, so\n // \"are these commits in main?\" is unanswerable from git — but \"are there any commits at all?\"\n // is exact.) These are the husks left behind by branching and then never committing.\n if (this.commitsAheadOfMain(repoRoot, branch) === 0) {\n return new Verdict(true, new DeletableBranch(branch, 'no commits of its own — identical to origin/main', 0));\n }\n\n return new Verdict(false, new DeletableBranch(branch, 'no merged PR found — a human must decide', 0));\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 private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch =>\n new DeletableBranch(entry.branch ?? '', entry.reason ?? '', entry.pr ?? 0));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n ));\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
1
+ {"version":3,"file":"merged-branches.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/merged-branches.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\nimport { Worktree, WorktreeService } from './worktrees';\n\n/**\n * The \"which local branches are dead?\" cache.\n *\n * WHY a cache, and why a STALE one is correct: cleanup is eventual, never urgent. A branch that\n * merged 30 seconds ago simply survives until the next refresh — nobody cares. So the (slow, network)\n * merged-PR lookup runs in the DETACHED background refresher, and the branch-creation-guard only READS\n * this file on its blocking path. Staleness is the whole point: it is what keeps the guard fast.\n *\n * WHY the GitHub PR API and not git: the repo squash-merges, which destroys BOTH commit ancestry and\n * patch-id. `git branch --merged` and `git cherry` therefore report merged branches as unmerged\n * (observed: deanhiller/config-overhaul, PR #188, merged — yet its patch is absent from main). A MERGED\n * PR is the only trustworthy signal, and ONE bulk `gh pr list --state merged` answers for every branch.\n */\n\nconst MERGED_BRANCHES_FILE = 'merged-branches.json';\n\n// How many merged PRs to pull in the single bulk lookup. Branches older than this window are\n// vanishingly unlikely to still be checked out locally; if one is, it lands in `keep` and a human\n// decides — the fail-safe direction.\nconst MERGED_PR_LOOKUP_LIMIT = 100;\n\n// Suffixes the squash-merge tooling appends to a feature branch when it snapshots it mid-sync\n// (base → baseSquash / basewp2 / basePreMerge3). GitHub has never seen these SHAs, so no PR will ever\n// name them — they can only be reaped by stripping back to the base branch they were cloned from.\nconst BACKUP_SUFFIX = /(?:Squash|PreMerge\\d*|wp\\d+)$/;\n\n// Data-only (per CLAUDE.md, classes for data).\nexport class MergedBranch {\n branch: string;\n pr: number;\n\n constructor(branch: string, pr: number) {\n this.branch = branch;\n this.pr = pr;\n }\n}\n\n// A local branch and the verdict on it. `pr` is 0 when no merged PR backs the verdict (a `keep`).\nexport class DeletableBranch {\n branch: string;\n reason: string;\n pr: number;\n\n constructor(branch: string, reason: string, pr: number) {\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n }\n}\n\n/**\n * A worktree and the verdict on it. Carries `path` (what `git worktree remove` takes) AND `branch`\n * (what `git branch -D` takes afterwards) because reaping a worktree is always those two steps, in\n * that order — git refuses to delete a branch that is still checked out somewhere.\n */\nexport class DeletableWorktree {\n path: string;\n branch: string;\n reason: string;\n pr: number;\n deletable: boolean;\n\n constructor(path: string, branch: string, reason: string, pr: number, deletable: boolean) {\n this.path = path;\n this.branch = branch;\n this.reason = reason;\n this.pr = pr;\n this.deletable = deletable;\n }\n}\n\n/**\n * `deletable` is PRECOMPUTED so the consumer just deletes the list — no re-deriving, no judgement\n * call at block time. `keep` carries the branches we refuse to touch (no merged PR found), each with\n * its reason, so a human can see what was spared and why.\n *\n * `worktrees` is the parallel verdict list for the SECOND budget (see worktrees.ts): every linked\n * worktree, each flagged deletable or not. Both budgets are reaped from this one cache file.\n */\nexport class MergedBranchesCache {\n timestamp: string;\n deletable: DeletableBranch[];\n keep: DeletableBranch[];\n worktrees: DeletableWorktree[];\n\n constructor(\n timestamp: string,\n deletable: DeletableBranch[],\n keep: DeletableBranch[],\n worktrees: DeletableWorktree[] = [],\n ) {\n this.timestamp = timestamp;\n this.deletable = deletable;\n this.keep = keep;\n this.worktrees = worktrees;\n }\n}\n\n// 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}\n\ninterface RawWorktree {\n path?: string;\n branch?: string;\n reason?: string;\n pr?: number;\n deletable?: boolean;\n}\n\ninterface RawCache {\n timestamp?: string;\n deletable?: RawDeletable[];\n keep?: RawDeletable[];\n // Absent in caches written by releases before the worktree cap existed — revives to [], which\n // makes the worktree cap fail OPEN on a stale file rather than hard-failing the guard.\n worktrees?: RawWorktree[];\n}\n\ninterface RawMergedPr {\n number?: number;\n headRefName?: string;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n@injectable(bindingScopeValues.Singleton)\nexport class MergedBranchesService {\n // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached\n // refresher) keep working, while inversify still injects the singleton when resolved from a container.\n constructor(private readonly worktrees: WorktreeService = new WorktreeService()) {}\n\n mergedBranchesPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MERGED_BRANCHES_FILE);\n }\n\n // Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN.\n readMergedBranches(repoRoot: string): MergedBranchesCache | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const cachePath = this.mergedBranchesPath(repoRoot);\n if (!fs.existsSync(cachePath)) return null;\n const raw = JSON.parse(fs.readFileSync(cachePath, 'utf8')) as RawCache;\n return new MergedBranchesCache(\n raw.timestamp ?? '',\n this.reviveList(raw.deletable),\n this.reviveList(raw.keep),\n this.reviveWorktrees(raw.worktrees),\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMergedBranches(repoRoot: string, cache: MergedBranchesCache): void {\n const cachePath = this.mergedBranchesPath(repoRoot);\n fs.mkdirSync(path.dirname(cachePath), { recursive: true });\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2) + '\\n');\n }\n\n /**\n * Every local branch except `main`. Uses `git for-each-ref`, NOT `git branch` — the latter is a\n * porcelain command whose output the branch-creation-guard's own regex mistakes for a branch\n * CREATION, so the guard would block the cleanup it just demanded.\n */\n localBranches(repoRoot: string): string[] {\n const result = this.capture(repoRoot, 'git', ['for-each-ref', '--format=%(refname:short)', 'refs/heads/']);\n if (!result.ok || result.out === '') return [];\n return result.out\n .split('\\n')\n .map((line: string): string => line.trim())\n .filter((line: string): boolean => line.length > 0 && line !== 'main');\n }\n\n /**\n * The SLOW path, run only inside the detached refresher. ONE bulk `gh` call, then a purely local\n * classification. Never run on the hook's blocking path.\n */\n computeMergedBranches(repoRoot: string): MergedBranchesCache {\n const merged = this.fetchMergedPrs(repoRoot);\n const byBranch = new Map<string, number>();\n for (const entry of merged) byBranch.set(entry.branch, entry.pr);\n\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, byBranch);\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 ));\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, byBranch);\n return new MergedBranchesCache(new Date().toISOString(), deletable, keep, worktrees);\n }\n\n /**\n * Verdicts for the worktree budget. The main worktree is excluded outright — it is the primary\n * clone and is not a thing you can remove.\n *\n * A worktree is deletable when its directory is already gone (`prunable`), or when its branch is\n * dead by the very same proofs the branch cap uses (merged PR, backup-of-merged, or zero commits of\n * its own). It is spared when it is LOCKED (a human said \"do not touch\"), when it is the worktree we\n * are standing in right now (removing your own cwd is not a thing to suggest to an agent), or when\n * its branch still holds unmerged work.\n */\n private classifyWorktrees(\n repoRoot: string,\n trees: Worktree[],\n byBranch: Map<string, number>,\n ): DeletableWorktree[] {\n const out: DeletableWorktree[] = [];\n\n for (const tree of trees) {\n if (tree.isMain) continue;\n\n if (tree.prunable) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'its directory is gone — `git worktree prune` clears it', 0, true));\n continue;\n }\n if (tree.locked) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'locked by a human — do not touch', 0, false));\n continue;\n }\n if (tree.path === repoRoot) {\n out.push(new DeletableWorktree(\n tree.path, tree.branch, 'you are standing in it', 0, false));\n continue;\n }\n if (tree.branch === '') {\n out.push(new DeletableWorktree(\n tree.path, '', 'detached HEAD — no branch to check, so a human must decide', 0, false));\n continue;\n }\n\n const verdict = this.classify(repoRoot, tree.branch, byBranch);\n out.push(new DeletableWorktree(\n tree.path, tree.branch, verdict.entry.reason, verdict.entry.pr, verdict.deletable));\n }\n\n return out;\n }\n\n // Verdict for one branch: its own merged PR, else the base it was backed up from, else empty.\n private classify(repoRoot: string, branch: string, byBranch: Map<string, number>): Verdict {\n const own = byBranch.get(branch);\n if (own !== undefined) {\n return new Verdict(true, new DeletableBranch(branch, `PR #${String(own)} merged`, own));\n }\n\n const base = branch.replace(BACKUP_SUFFIX, '');\n if (base !== branch && base.length > 0) {\n const basePr = byBranch.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,\n ));\n }\n }\n\n // The one git-local signal that squash-merge CANNOT corrupt: a branch with zero commits of its\n // own holds no work, so deleting it can lose nothing. (Squash breaks patch-id and ancestry, so\n // \"are these commits in main?\" is unanswerable from git — but \"are there any commits at all?\"\n // is exact.) These are the husks left behind by branching and then never committing.\n if (this.commitsAheadOfMain(repoRoot, branch) === 0) {\n return new Verdict(true, new DeletableBranch(branch, 'no commits of its own — identical to origin/main', 0));\n }\n\n return new Verdict(false, new DeletableBranch(branch, 'no merged PR found — a human must decide', 0));\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 private reviveList(raw: RawDeletable[] | undefined): DeletableBranch[] {\n if (!raw) return [];\n return raw.map((entry: RawDeletable): DeletableBranch =>\n new DeletableBranch(entry.branch ?? '', entry.reason ?? '', entry.pr ?? 0));\n }\n\n private reviveWorktrees(raw: RawWorktree[] | undefined): DeletableWorktree[] {\n if (!raw) return [];\n return raw.map((entry: RawWorktree): DeletableWorktree => new DeletableWorktree(\n entry.path ?? '',\n entry.branch ?? '',\n entry.reason ?? '',\n entry.pr ?? 0,\n entry.deletable ?? false,\n ));\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n}\n"]}
package/src/repo-root.js CHANGED
@@ -4,7 +4,6 @@ exports.RepoRootFinder = exports.INSTRUCT_AI_DIR = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const child_process_1 = require("child_process");
6
6
  const path = tslib_1.__importStar(require("path"));
7
- const di_1 = require("./di");
8
7
  const inversify_1 = require("inversify");
9
8
  const config_file_1 = require("./config-file");
10
9
  const constants_1 = require("./constants");
@@ -64,7 +63,6 @@ let RepoRootFinder = class RepoRootFinder {
64
63
  };
65
64
  exports.RepoRootFinder = RepoRootFinder;
66
65
  exports.RepoRootFinder = RepoRootFinder = tslib_1.__decorate([
67
- (0, di_1.provideSingleton)(),
68
- (0, inversify_1.injectable)()
66
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
69
67
  ], RepoRootFinder);
70
68
  //# sourceMappingURL=repo-root.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"repo-root.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/repo-root.ts"],"names":[],"mappings":";;;;AAAA,iDAA0C;AAC1C,mDAA6B;AAC7B,6BAAwC;AACxC,yCAAuC;AAEvC,+CAA+C;AAC/C,2CAAgD;AAEhD,oGAAoG;AACpG,0FAA0F;AAC7E,QAAA,eAAe,GAAG,GAAG,6BAAiB,cAAc,CAAC;AAElE;;;;;;;;;GASG;AAGI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB;;;;;;;OAOG;IACH,eAAe,CAAC,QAAgB;QAC5B,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QACrC,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,QAAgB,EAAE,OAAe;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAe,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,gGAAgG;IAChG,WAAW,CAAC,QAAgB,EAAE,OAAe;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,gGAAgG;IAChG,6FAA6F;IAC7F,8FAA8F;IACtF,WAAW,CAAC,GAAW;QAC3B,MAAM,CAAC,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;CACJ,CAAA;AAxCY,wCAAc;yBAAd,cAAc;IAF1B,IAAA,qBAAgB,GAAE;IAClB,IAAA,sBAAU,GAAE;GACA,cAAc,CAwC1B","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport { provideSingleton } from './di';\nimport { injectable } from 'inversify';\n\nimport { findConfigFile } from './config-file';\nimport { WEBPIECES_TMP_DIR } from './constants';\n\n// The single instruct-ai home under `.webpieces/`. Kept here (not just in load-template) so callers\n// building AI-facing messages can render the ABSOLUTE doc path from a resolved repo root.\nexport const INSTRUCT_AI_DIR = `${WEBPIECES_TMP_DIR}/instruct-ai`;\n\n/**\n * Resolves the single repo root that owns the `.webpieces/` tree, and renders absolute paths beneath\n * it.\n *\n * `.webpieces/` MUST live at the repo root — never in a CWD-dependent subdirectory. A tool invoked\n * from a subdir (a nested package, a `services/*` app) that naively joined `.webpieces` onto its own\n * cwd would scatter stray `.webpieces` trees across the tree — the exact bug this prevents. Every\n * writer of `.webpieces/...` (logs, instruct-ai docs, sync cache, merge/pr state) MUST anchor its\n * path here rather than at `process.cwd()`.\n */\n@provideSingleton()\n@injectable()\nexport class RepoRootFinder {\n /**\n * The repo root for `startDir`. Resolution order (first hit wins):\n * 1. Directory holding webpieces.config.json — the webpieces workspace root, and the exact\n * anchor the hook runner already uses. Walks UP from startDir, so a subdir resolves to root.\n * 2. git toplevel (`git rev-parse --show-toplevel`) — the repo root when no config is present\n * yet (e.g. the installer runs before webpieces.config.json exists).\n * 3. `startDir` — last resort (git unavailable / not a repo / no config). Best-effort only.\n */\n resolveRepoRoot(startDir: string): string {\n const configPath = findConfigFile(startDir);\n if (configPath !== null) return path.dirname(configPath);\n const gitRoot = this.gitToplevel(startDir);\n if (gitRoot !== null) return gitRoot;\n return startDir;\n }\n\n /**\n * Absolute path to an instruct-ai doc under `repoRoot`. Hand THIS to the AI in a violation/fix\n * message — never a bare `.webpieces/instruct-ai/...` relative path, which an AI whose cwd is a\n * subdirectory would resolve against the wrong directory and fail to open.\n */\n instructAiDocPath(repoRoot: string, docName: string): string {\n return path.join(repoRoot, INSTRUCT_AI_DIR, docName);\n }\n\n /** Absolute instruct-ai doc path resolved directly from `startDir` (resolveRepoRoot + join). */\n docPathFrom(startDir: string, docName: string): string {\n return this.instructAiDocPath(this.resolveRepoRoot(startDir), docName);\n }\n\n // git repo root of `cwd`, or null when cwd is not in a git repo. `status !== 0` is the EXPECTED\n // \"not a repo\" value (spawnSync does not throw on non-zero exit), so we never swallow a real\n // failure with try/catch — a genuine git crash still surfaces. Mirrors runner.ts:gitToplevel.\n private gitToplevel(cwd: string): string | null {\n const r = spawnSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });\n if (r.status !== 0) return null;\n const root = (r.stdout ?? '').trim();\n return root !== '' ? root : null;\n }\n}\n"]}
1
+ {"version":3,"file":"repo-root.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/repo-root.ts"],"names":[],"mappings":";;;;AAAA,iDAA0C;AAC1C,mDAA6B;AAC7B,yCAA2D;AAE3D,+CAA+C;AAC/C,2CAAgD;AAEhD,oGAAoG;AACpG,0FAA0F;AAC7E,QAAA,eAAe,GAAG,GAAG,6BAAiB,cAAc,CAAC;AAElE;;;;;;;;;GASG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAc;IACvB;;;;;;;OAOG;IACH,eAAe,CAAC,QAAgB;QAC5B,MAAM,UAAU,GAAG,IAAA,4BAAc,EAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC;QACrC,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,iBAAiB,CAAC,QAAgB,EAAE,OAAe;QAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,uBAAe,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,gGAAgG;IAChG,WAAW,CAAC,QAAgB,EAAE,OAAe;QACzC,OAAO,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAED,gGAAgG;IAChG,6FAA6F;IAC7F,8FAA8F;IACtF,WAAW,CAAC,GAAW;QAC3B,MAAM,CAAC,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9F,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrC,CAAC;CACJ,CAAA;AAxCY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;GAC5B,cAAc,CAwC1B","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { findConfigFile } from './config-file';\nimport { WEBPIECES_TMP_DIR } from './constants';\n\n// The single instruct-ai home under `.webpieces/`. Kept here (not just in load-template) so callers\n// building AI-facing messages can render the ABSOLUTE doc path from a resolved repo root.\nexport const INSTRUCT_AI_DIR = `${WEBPIECES_TMP_DIR}/instruct-ai`;\n\n/**\n * Resolves the single repo root that owns the `.webpieces/` tree, and renders absolute paths beneath\n * it.\n *\n * `.webpieces/` MUST live at the repo root — never in a CWD-dependent subdirectory. A tool invoked\n * from a subdir (a nested package, a `services/*` app) that naively joined `.webpieces` onto its own\n * cwd would scatter stray `.webpieces` trees across the tree — the exact bug this prevents. Every\n * writer of `.webpieces/...` (logs, instruct-ai docs, sync cache, merge/pr state) MUST anchor its\n * path here rather than at `process.cwd()`.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class RepoRootFinder {\n /**\n * The repo root for `startDir`. Resolution order (first hit wins):\n * 1. Directory holding webpieces.config.json — the webpieces workspace root, and the exact\n * anchor the hook runner already uses. Walks UP from startDir, so a subdir resolves to root.\n * 2. git toplevel (`git rev-parse --show-toplevel`) — the repo root when no config is present\n * yet (e.g. the installer runs before webpieces.config.json exists).\n * 3. `startDir` — last resort (git unavailable / not a repo / no config). Best-effort only.\n */\n resolveRepoRoot(startDir: string): string {\n const configPath = findConfigFile(startDir);\n if (configPath !== null) return path.dirname(configPath);\n const gitRoot = this.gitToplevel(startDir);\n if (gitRoot !== null) return gitRoot;\n return startDir;\n }\n\n /**\n * Absolute path to an instruct-ai doc under `repoRoot`. Hand THIS to the AI in a violation/fix\n * message — never a bare `.webpieces/instruct-ai/...` relative path, which an AI whose cwd is a\n * subdirectory would resolve against the wrong directory and fail to open.\n */\n instructAiDocPath(repoRoot: string, docName: string): string {\n return path.join(repoRoot, INSTRUCT_AI_DIR, docName);\n }\n\n /** Absolute instruct-ai doc path resolved directly from `startDir` (resolveRepoRoot + join). */\n docPathFrom(startDir: string, docName: string): string {\n return this.instructAiDocPath(this.resolveRepoRoot(startDir), docName);\n }\n\n // git repo root of `cwd`, or null when cwd is not in a git repo. `status !== 0` is the EXPECTED\n // \"not a repo\" value (spawnSync does not throw on non-zero exit), so we never swallow a real\n // failure with try/catch — a genuine git crash still surfaces. Mirrors runner.ts:gitToplevel.\n private gitToplevel(cwd: string): string | null {\n const r = spawnSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], { encoding: 'utf8' });\n if (r.status !== 0) return null;\n const root = (r.stdout ?? '').trim();\n return root !== '' ? root : null;\n }\n}\n"]}
@@ -9,7 +9,7 @@ export declare class ReviewJson {
9
9
  filesToReview: string[];
10
10
  constructor(title: string, riskScore: number, riskLevel: string, riskEmoji: string, summary: string, violations: string[], risks: string[], filesToReview: string[]);
11
11
  }
12
- /** Locates + loads/validates the AI-authored review.json. `@provideSingleton` so it's drawn in the design. */
12
+ /** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */
13
13
  export declare class ReviewJsonService {
14
14
  prDirFor(repoRoot: string, featureName: string): string;
15
15
  reviewJsonPath(repoRoot: string, featureName: string): string;
@@ -8,7 +8,6 @@ exports.loadReviewJson = loadReviewJson;
8
8
  const tslib_1 = require("tslib");
9
9
  const fs = tslib_1.__importStar(require("fs"));
10
10
  const path = tslib_1.__importStar(require("path"));
11
- const di_1 = require("./di");
12
11
  const inversify_1 = require("inversify");
13
12
  const constants_1 = require("./constants");
14
13
  const inform_ai_error_1 = require("./inform-ai-error");
@@ -38,7 +37,7 @@ class ReviewJson {
38
37
  exports.ReviewJson = ReviewJson;
39
38
  const RISK_LEVELS = ['green', 'yellow', 'red'];
40
39
  const EMOJI_FOR_LEVEL = { green: '🟢', yellow: '🟡', red: '🔴' };
41
- /** Locates + loads/validates the AI-authored review.json. `@provideSingleton` so it's drawn in the design. */
40
+ /** Locates + loads/validates the AI-authored review.json. `@injectable(bindingScopeValues.Singleton)` so it's drawn in the design. */
42
41
  let ReviewJsonService = class ReviewJsonService {
43
42
  // The per-feature PR working dir: `.webpieces/pr-review/<feature>`.
44
43
  prDirFor(repoRoot, featureName) {
@@ -126,8 +125,7 @@ let ReviewJsonService = class ReviewJsonService {
126
125
  };
127
126
  exports.ReviewJsonService = ReviewJsonService;
128
127
  exports.ReviewJsonService = ReviewJsonService = tslib_1.__decorate([
129
- (0, di_1.provideSingleton)(),
130
- (0, inversify_1.injectable)()
128
+ (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton)
131
129
  ], ReviewJsonService);
132
130
  // Temporary migration delegators to ReviewJsonService — removed once consumers inject it.
133
131
  const reviewJsonSvc = new ReviewJsonService();