@webpieces/pr-gate 0.4.735 → 0.4.737

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/pr-gate",
3
- "version": "0.4.735",
3
+ "version": "0.4.737",
4
4
  "description": "Gated PR system: 3-point squash-merge, merge validation gate, and red/yellow/green PR dashboard. Standalone scripts, no Nx dependency required.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -16,8 +16,8 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@inversifyjs/binding-decorators": "1.1.5",
19
- "@webpieces/ai-hook-rules": "0.4.735",
20
- "@webpieces/rules-config": "0.4.735",
19
+ "@webpieces/ai-hook-rules": "0.4.737",
20
+ "@webpieces/rules-config": "0.4.737",
21
21
  "inversify": "7.10.4",
22
22
  "reflect-metadata": "0.2.2"
23
23
  },
@@ -1,5 +1,4 @@
1
1
  import { BuildsLog } from '@webpieces/rules-config';
2
- import { CodexGuardPresence } from '@webpieces/ai-hook-rules';
3
2
  import { BuildGateLog } from './build-gate-log';
4
3
  import { StageOutputLog } from './stage-output-log';
5
4
  /**
@@ -14,18 +13,12 @@ export declare class BuildGateOptions {
14
13
  stage: string;
15
14
  constructor(label: string, rerunCommand: string, failureHeadline: string, stage: string);
16
15
  }
17
- /**
18
- * The rule name the guard-presence refusal is reported under, so it is greppable from a transcript the
19
- * same way `too-many-concurrent-builds` is.
20
- */
21
- export declare const CODEX_GUARDS_NEVER_RAN = "codex-guards-never-ran";
22
16
  /** Runs the authoritative nx-affected build gate for wp-review-upsert-pr and wp-finish-upsert-pr. */
23
17
  export declare class BuildAffected {
24
18
  private readonly buildLog;
25
19
  private readonly buildsLog;
26
20
  private readonly stageConsole;
27
- private readonly guardPresence;
28
- constructor(buildLog: BuildGateLog, buildsLog: BuildsLog, stageConsole: StageOutputLog, guardPresence: CodexGuardPresence);
21
+ constructor(buildLog: BuildGateLog, buildsLog: BuildsLog, stageConsole: StageOutputLog);
29
22
  /**
30
23
  * Resolve the exact build command this gate will run: the project's configured
31
24
  * PrGateConfig.buildCommand, or the default affected-ci command when none is set.
@@ -47,26 +40,6 @@ export declare class BuildAffected {
47
40
  * because a log file was busy.
48
41
  */
49
42
  runBuildGate(repoRoot: string, opts: BuildGateOptions): Promise<void>;
50
- /**
51
- * Refuse when this is a Codex session in which NOT ONE guard has run.
52
- *
53
- * ─── Why it lives at the build, and why in THIS method ───────────────────────────────────────────
54
- * `runBuildGate` is the single chokepoint every build reaches — `wp-build`, stage ② and stage ③ —
55
- * so one call here is the whole wiring. It runs BEFORE the command is resolved and before the
56
- * ledger row opens, because a session that was never guarded should cost nothing, not a full build
57
- * followed by a refusal to believe it.
58
- *
59
- * The build is the right MOMENT because it is where a session's work is first claimed as verified.
60
- * An unguarded Codex session that never builds has produced nothing anyone is about to trust; one
61
- * that builds is asking for exactly that trust, on the strength of guards that never ran.
62
- *
63
- * ─── Safe to call unconditionally ────────────────────────────────────────────────────────────────
64
- * Outside a Codex session `check()` is a no-op that returns ok — Claude Code sessions, CI, and a
65
- * human at a terminal all pass straight through, which is why this needs no flag and no config key
66
- * to sit on the shared path. The cures come from the verdict as `Option`s; this method contributes
67
- * the throw and nothing else, so the two halves cannot drift into two different stories.
68
- */
69
- private assertGuardsRan;
70
43
  /**
71
44
  * The failure text: this gate's own headline, then the pointer at the log the build already wrote.
72
45
  *
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BuildAffected = exports.CODEX_GUARDS_NEVER_RAN = exports.BuildGateOptions = void 0;
3
+ exports.BuildAffected = exports.BuildGateOptions = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const rules_config_1 = require("@webpieces/rules-config");
6
- const ai_hook_rules_1 = require("@webpieces/ai-hook-rules");
7
6
  const inversify_1 = require("inversify");
8
7
  const build_gate_log_1 = require("./build-gate-log");
9
8
  const stage_output_log_1 = require("./stage-output-log");
@@ -33,22 +32,15 @@ class BuildGateOptions {
33
32
  }
34
33
  }
35
34
  exports.BuildGateOptions = BuildGateOptions;
36
- /**
37
- * The rule name the guard-presence refusal is reported under, so it is greppable from a transcript the
38
- * same way `too-many-concurrent-builds` is.
39
- */
40
- exports.CODEX_GUARDS_NEVER_RAN = 'codex-guards-never-ran';
41
35
  /** Runs the authoritative nx-affected build gate for wp-review-upsert-pr and wp-finish-upsert-pr. */
42
36
  let BuildAffected = class BuildAffected {
43
37
  buildLog;
44
38
  buildsLog;
45
39
  stageConsole;
46
- guardPresence;
47
- constructor(buildLog, buildsLog, stageConsole, guardPresence) {
40
+ constructor(buildLog, buildsLog, stageConsole) {
48
41
  this.buildLog = buildLog;
49
42
  this.buildsLog = buildsLog;
50
43
  this.stageConsole = stageConsole;
51
- this.guardPresence = guardPresence;
52
44
  }
53
45
  /**
54
46
  * Resolve the exact build command this gate will run: the project's configured
@@ -74,7 +66,6 @@ let BuildAffected = class BuildAffected {
74
66
  * because a log file was busy.
75
67
  */
76
68
  async runBuildGate(repoRoot, opts) {
77
- this.assertGuardsRan(repoRoot);
78
69
  const buildCommand = this.resolveBuildCommand(repoRoot);
79
70
  // TWO lines on the happy path — the command, then the result. The old framing spent a banner and a
80
71
  // paragraph explaining how to reproduce a build that was about to pass anyway; that explanation is
@@ -100,31 +91,6 @@ let BuildAffected = class BuildAffected {
100
91
  throw new rules_config_1.CliExitError(buildCode, this.failureText(opts, buildCommand, logPath));
101
92
  this.stageConsole.say(this.buildLog.successMessage(logPath));
102
93
  }
103
- /**
104
- * Refuse when this is a Codex session in which NOT ONE guard has run.
105
- *
106
- * ─── Why it lives at the build, and why in THIS method ───────────────────────────────────────────
107
- * `runBuildGate` is the single chokepoint every build reaches — `wp-build`, stage ② and stage ③ —
108
- * so one call here is the whole wiring. It runs BEFORE the command is resolved and before the
109
- * ledger row opens, because a session that was never guarded should cost nothing, not a full build
110
- * followed by a refusal to believe it.
111
- *
112
- * The build is the right MOMENT because it is where a session's work is first claimed as verified.
113
- * An unguarded Codex session that never builds has produced nothing anyone is about to trust; one
114
- * that builds is asking for exactly that trust, on the strength of guards that never ran.
115
- *
116
- * ─── Safe to call unconditionally ────────────────────────────────────────────────────────────────
117
- * Outside a Codex session `check()` is a no-op that returns ok — Claude Code sessions, CI, and a
118
- * human at a terminal all pass straight through, which is why this needs no flag and no config key
119
- * to sit on the shared path. The cures come from the verdict as `Option`s; this method contributes
120
- * the throw and nothing else, so the two halves cannot drift into two different stories.
121
- */
122
- assertGuardsRan(repoRoot) {
123
- const verdict = this.guardPresence.check(repoRoot);
124
- if (verdict.ok)
125
- return;
126
- throw new rules_config_1.RuleFailError(exports.CODEX_GUARDS_NEVER_RAN, verdict.reason, undefined, undefined, [...verdict.cures]);
127
- }
128
94
  /**
129
95
  * The failure text: this gate's own headline, then the pointer at the log the build already wrote.
130
96
  *
@@ -144,7 +110,6 @@ exports.BuildAffected = BuildAffected = tslib_1.__decorate([
144
110
  (0, inversify_1.injectable)(inversify_1.bindingScopeValues.Singleton),
145
111
  tslib_1.__metadata("design:paramtypes", [build_gate_log_1.BuildGateLog,
146
112
  rules_config_1.BuildsLog,
147
- stage_output_log_1.StageOutputLog,
148
- ai_hook_rules_1.CodexGuardPresence])
113
+ stage_output_log_1.StageOutputLog])
149
114
  ], BuildAffected);
150
115
  //# sourceMappingURL=build-affected.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-affected.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/build-affected.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadAndValidate, CliExitError, DEFAULT_BUILD_COMMAND, BuildsLog, RuleFailError,\n} from '@webpieces/rules-config';\nimport { CodexGuardPresence, GuardPresenceVerdict } from '@webpieces/ai-hook-rules';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { BuildGateLog } from './build-gate-log';\nimport { StageOutputLog } from './stage-output-log';\n\n// Single source of truth for RUNNING the build gate. `wp-start-upsert-pr` runs NO build; stage ②\n// (`wp-review-upsert-pr`) runs it authoritatively before any reviewer is spawned, and stage ③\n// (`wp-finish-upsert-pr`) re-runs it only when HEAD has moved since. nx `affected` only rebuilds\n// changed projects. The fallback command itself lives in @webpieces/rules-config as\n// DEFAULT_BUILD_COMMAND — whole-repo-build-guard prints the same string, and one definition is what\n// keeps the refusal message naming the build that actually runs.\n\n\n/**\n * The caller-supplied framing for the build gate (label, re-run command, failure headline, stage id). Kept\n * as a parameter object so runBuildGate stays agnostic of who invokes it. A class (not an object literal)\n * per the codebase's data-structure convention.\n */\nexport class BuildGateOptions {\n label: string; // section header shown above the gate\n rerunCommand: string; // command the AI re-runs after fixing the build\n failureHeadline: string; // first line printed on failure\n // WHICH stage's gate this is — REVIEW_STAGE, FINISH_STAGE or BUILD_STAGE. Required, with no default:\n // it decides the captured log's filename, and a default would silently make two stages share one file.\n stage: string;\n\n constructor(label: string, rerunCommand: string, failureHeadline: string, stage: string) {\n this.label = label;\n this.rerunCommand = rerunCommand;\n this.failureHeadline = failureHeadline;\n this.stage = stage;\n }\n}\n\n/**\n * The rule name the guard-presence refusal is reported under, so it is greppable from a transcript the\n * same way `too-many-concurrent-builds` is.\n */\nexport const CODEX_GUARDS_NEVER_RAN = 'codex-guards-never-ran';\n\n/** Runs the authoritative nx-affected build gate for wp-review-upsert-pr and wp-finish-upsert-pr. */\n@injectable(bindingScopeValues.Singleton)\nexport class BuildAffected {\n constructor(\n private readonly buildLog: BuildGateLog,\n private readonly buildsLog: BuildsLog,\n private readonly stageConsole: StageOutputLog,\n private readonly guardPresence: CodexGuardPresence,\n ) {}\n\n /**\n * Resolve the exact build command this gate will run: the project's configured\n * PrGateConfig.buildCommand, or the default affected-ci command when none is set.\n */\n resolveBuildCommand(repoRoot: string): string {\n const configured = loadAndValidate(repoRoot).prGate.buildCommand;\n return configured !== undefined && configured.trim() !== '' ? configured : DEFAULT_BUILD_COMMAND;\n }\n\n /**\n * Run the configured build gate with consistent framing, throwing CliExitError(buildCode) on\n * failure so the bin's main()/runMain owns the exit. THE single source of truth for running a build:\n * wp-build, wp-review-upsert-pr and wp-finish-upsert-pr all call THIS (only the BuildGateOptions\n * differ), and there is no other way to reach the spawn.\n *\n * ─── EVERY BUILD ON THIS MACHINE IS LEDGERED FROM HERE ───────────────────────────────────────────\n * A START row goes into `~/.webpieces/builds.log` before the spawn and a DONE row after it, in a\n * `finally` so a throw on the failure path still closes the pair. `opts.stage` — `build` | `review` |\n * `finish` — IS the caller id the row records; there is deliberately no second `caller` field, since\n * two spellings of one fact is the shim `.claude/rules/no-backwards-compat.md` rejects.\n *\n * The ledger is best-effort by construction (see BuildsLog): it can never throw, so no build can die\n * because a log file was busy.\n */\n async runBuildGate(repoRoot: string, opts: BuildGateOptions): Promise<void> {\n this.assertGuardsRan(repoRoot);\n const buildCommand = this.resolveBuildCommand(repoRoot);\n // TWO lines on the happy path — the command, then the result. The old framing spent a banner and a\n // paragraph explaining how to reproduce a build that was about to pass anyway; that explanation is\n // only useful when the build FAILS, so it now lives solely on the failure path below.\n //\n // `say`, so it survives a stage that is capturing its own console (see StageOutputLog): the\n // command being run and the result of running it are exactly what a caller watching a long build\n // needs to see live.\n this.stageConsole.say(`\\n${opts.label}: ${buildCommand}\\n`);\n const logPath = this.buildLog.pathFor(repoRoot, opts.stage);\n const ticket = this.buildsLog.start(opts.stage, repoRoot);\n let buildCode = 1;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: the DONE row must be written whether the\n // build passed, failed, or the spawn itself blew up; the throw is re-raised untouched below\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n buildCode = await this.buildLog.run(repoRoot, buildCommand, logPath);\n } finally {\n this.buildsLog.finish(ticket, buildCode);\n }\n if (buildCode !== 0) throw new CliExitError(buildCode, this.failureText(opts, buildCommand, logPath));\n this.stageConsole.say(this.buildLog.successMessage(logPath));\n }\n\n /**\n * Refuse when this is a Codex session in which NOT ONE guard has run.\n *\n * ─── Why it lives at the build, and why in THIS method ───────────────────────────────────────────\n * `runBuildGate` is the single chokepoint every build reaches — `wp-build`, stage ② and stage ③ —\n * so one call here is the whole wiring. It runs BEFORE the command is resolved and before the\n * ledger row opens, because a session that was never guarded should cost nothing, not a full build\n * followed by a refusal to believe it.\n *\n * The build is the right MOMENT because it is where a session's work is first claimed as verified.\n * An unguarded Codex session that never builds has produced nothing anyone is about to trust; one\n * that builds is asking for exactly that trust, on the strength of guards that never ran.\n *\n * ─── Safe to call unconditionally ────────────────────────────────────────────────────────────────\n * Outside a Codex session `check()` is a no-op that returns ok — Claude Code sessions, CI, and a\n * human at a terminal all pass straight through, which is why this needs no flag and no config key\n * to sit on the shared path. The cures come from the verdict as `Option`s; this method contributes\n * the throw and nothing else, so the two halves cannot drift into two different stories.\n */\n private assertGuardsRan(repoRoot: string): void {\n const verdict: GuardPresenceVerdict = this.guardPresence.check(repoRoot);\n if (verdict.ok) return;\n throw new RuleFailError(\n CODEX_GUARDS_NEVER_RAN, verdict.reason, undefined, undefined, [...verdict.cures]);\n }\n\n /**\n * The failure text: this gate's own headline, then the pointer at the log the build already wrote.\n *\n * There is no second, streamed variant any more. Capturing used to be behind an EXPERIMENTAL\n * machine-local opt-in, which meant the refusal an agent read depended on a file almost nobody had —\n * and the un-captured branch's advice was \"run the build again yourself\", which is the single most\n * expensive thing an agent can be told. Reading a FILE is now the only answer this gate gives.\n */\n private failureText(opts: BuildGateOptions, buildCommand: string, logPath: string): string {\n return `\\n❌ ${opts.failureHeadline}\\n` +\n this.buildLog.failureMessage(buildCommand, logPath) +\n `Fix what that log shows, then re-run ${opts.rerunCommand}.\\n`;\n }\n}\n"]}
1
+ {"version":3,"file":"build-affected.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/pr-gate/src/scripts/workflow/build-affected.ts"],"names":[],"mappings":";;;;AAAA,0DAEiC;AACjC,yCAA2D;AAC3D,qDAAgD;AAChD,yDAAoD;AAEpD,iGAAiG;AACjG,8FAA8F;AAC9F,iGAAiG;AACjG,oFAAoF;AACpF,oGAAoG;AACpG,iEAAiE;AAGjE;;;;GAIG;AACH,MAAa,gBAAgB;IACzB,KAAK,CAAS,CAAY,sCAAsC;IAChE,YAAY,CAAS,CAAK,gDAAgD;IAC1E,eAAe,CAAS,CAAE,gCAAgC;IAC1D,qGAAqG;IACrG,uGAAuG;IACvG,KAAK,CAAS;IAEd,YAAY,KAAa,EAAE,YAAoB,EAAE,eAAuB,EAAE,KAAa;QACnF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;CACJ;AAdD,4CAcC;AAED,qGAAqG;AAE9F,IAAM,aAAa,GAAnB,MAAM,aAAa;IAED;IACA;IACA;IAHrB,YACqB,QAAsB,EACtB,SAAoB,EACpB,YAA4B;QAF5B,aAAQ,GAAR,QAAQ,CAAc;QACtB,cAAS,GAAT,SAAS,CAAW;QACpB,iBAAY,GAAZ,YAAY,CAAgB;IAC9C,CAAC;IAEJ;;;OAGG;IACH,mBAAmB,CAAC,QAAgB;QAChC,MAAM,UAAU,GAAG,IAAA,8BAAe,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;QACjE,OAAO,UAAU,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,oCAAqB,CAAC;IACrG,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,YAAY,CAAC,QAAgB,EAAE,IAAsB;QACvD,MAAM,YAAY,GAAG,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACxD,mGAAmG;QACnG,mGAAmG;QACnG,sFAAsF;QACtF,EAAE;QACF,4FAA4F;QAC5F,iGAAiG;QACjG,qBAAqB;QACrB,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,KAAK,KAAK,YAAY,IAAI,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAC1D,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,oGAAoG;QACpG,4FAA4F;QAC5F,8DAA8D;QAC9D,IAAI,CAAC;YACD,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;QACzE,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,SAAS,KAAK,CAAC;YAAE,MAAM,IAAI,2BAAY,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QACtG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACjE,CAAC;IAED;;;;;;;OAOG;IACK,WAAW,CAAC,IAAsB,EAAE,YAAoB,EAAE,OAAe;QAC7E,OAAO,OAAO,IAAI,CAAC,eAAe,IAAI;YAClC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY,EAAE,OAAO,CAAC;YACnD,wCAAwC,IAAI,CAAC,YAAY,KAAK,CAAC;IACvE,CAAC;CACJ,CAAA;AArEY,sCAAa;wBAAb,aAAa;IADzB,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGN,6BAAY;QACX,wBAAS;QACN,iCAAc;GAJxC,aAAa,CAqEzB","sourcesContent":["import {\n loadAndValidate, CliExitError, DEFAULT_BUILD_COMMAND, BuildsLog,\n} from '@webpieces/rules-config';\nimport { injectable, bindingScopeValues } from 'inversify';\nimport { BuildGateLog } from './build-gate-log';\nimport { StageOutputLog } from './stage-output-log';\n\n// Single source of truth for RUNNING the build gate. `wp-start-upsert-pr` runs NO build; stage ②\n// (`wp-review-upsert-pr`) runs it authoritatively before any reviewer is spawned, and stage ③\n// (`wp-finish-upsert-pr`) re-runs it only when HEAD has moved since. nx `affected` only rebuilds\n// changed projects. The fallback command itself lives in @webpieces/rules-config as\n// DEFAULT_BUILD_COMMAND — whole-repo-build-guard prints the same string, and one definition is what\n// keeps the refusal message naming the build that actually runs.\n\n\n/**\n * The caller-supplied framing for the build gate (label, re-run command, failure headline, stage id). Kept\n * as a parameter object so runBuildGate stays agnostic of who invokes it. A class (not an object literal)\n * per the codebase's data-structure convention.\n */\nexport class BuildGateOptions {\n label: string; // section header shown above the gate\n rerunCommand: string; // command the AI re-runs after fixing the build\n failureHeadline: string; // first line printed on failure\n // WHICH stage's gate this is — REVIEW_STAGE, FINISH_STAGE or BUILD_STAGE. Required, with no default:\n // it decides the captured log's filename, and a default would silently make two stages share one file.\n stage: string;\n\n constructor(label: string, rerunCommand: string, failureHeadline: string, stage: string) {\n this.label = label;\n this.rerunCommand = rerunCommand;\n this.failureHeadline = failureHeadline;\n this.stage = stage;\n }\n}\n\n/** Runs the authoritative nx-affected build gate for wp-review-upsert-pr and wp-finish-upsert-pr. */\n@injectable(bindingScopeValues.Singleton)\nexport class BuildAffected {\n constructor(\n private readonly buildLog: BuildGateLog,\n private readonly buildsLog: BuildsLog,\n private readonly stageConsole: StageOutputLog,\n ) {}\n\n /**\n * Resolve the exact build command this gate will run: the project's configured\n * PrGateConfig.buildCommand, or the default affected-ci command when none is set.\n */\n resolveBuildCommand(repoRoot: string): string {\n const configured = loadAndValidate(repoRoot).prGate.buildCommand;\n return configured !== undefined && configured.trim() !== '' ? configured : DEFAULT_BUILD_COMMAND;\n }\n\n /**\n * Run the configured build gate with consistent framing, throwing CliExitError(buildCode) on\n * failure so the bin's main()/runMain owns the exit. THE single source of truth for running a build:\n * wp-build, wp-review-upsert-pr and wp-finish-upsert-pr all call THIS (only the BuildGateOptions\n * differ), and there is no other way to reach the spawn.\n *\n * ─── EVERY BUILD ON THIS MACHINE IS LEDGERED FROM HERE ───────────────────────────────────────────\n * A START row goes into `~/.webpieces/builds.log` before the spawn and a DONE row after it, in a\n * `finally` so a throw on the failure path still closes the pair. `opts.stage` — `build` | `review` |\n * `finish` — IS the caller id the row records; there is deliberately no second `caller` field, since\n * two spellings of one fact is the shim `.claude/rules/no-backwards-compat.md` rejects.\n *\n * The ledger is best-effort by construction (see BuildsLog): it can never throw, so no build can die\n * because a log file was busy.\n */\n async runBuildGate(repoRoot: string, opts: BuildGateOptions): Promise<void> {\n const buildCommand = this.resolveBuildCommand(repoRoot);\n // TWO lines on the happy path — the command, then the result. The old framing spent a banner and a\n // paragraph explaining how to reproduce a build that was about to pass anyway; that explanation is\n // only useful when the build FAILS, so it now lives solely on the failure path below.\n //\n // `say`, so it survives a stage that is capturing its own console (see StageOutputLog): the\n // command being run and the result of running it are exactly what a caller watching a long build\n // needs to see live.\n this.stageConsole.say(`\\n${opts.label}: ${buildCommand}\\n`);\n const logPath = this.buildLog.pathFor(repoRoot, opts.stage);\n const ticket = this.buildsLog.start(opts.stage, repoRoot);\n let buildCode = 1;\n // webpieces-disable no-unmanaged-exceptions -- chokepoint: the DONE row must be written whether the\n // build passed, failed, or the spawn itself blew up; the throw is re-raised untouched below\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n buildCode = await this.buildLog.run(repoRoot, buildCommand, logPath);\n } finally {\n this.buildsLog.finish(ticket, buildCode);\n }\n if (buildCode !== 0) throw new CliExitError(buildCode, this.failureText(opts, buildCommand, logPath));\n this.stageConsole.say(this.buildLog.successMessage(logPath));\n }\n\n /**\n * The failure text: this gate's own headline, then the pointer at the log the build already wrote.\n *\n * There is no second, streamed variant any more. Capturing used to be behind an EXPERIMENTAL\n * machine-local opt-in, which meant the refusal an agent read depended on a file almost nobody had —\n * and the un-captured branch's advice was \"run the build again yourself\", which is the single most\n * expensive thing an agent can be told. Reading a FILE is now the only answer this gate gives.\n */\n private failureText(opts: BuildGateOptions, buildCommand: string, logPath: string): string {\n return `\\n❌ ${opts.failureHeadline}\\n` +\n this.buildLog.failureMessage(buildCommand, logPath) +\n `Fix what that log shows, then re-run ${opts.rerunCommand}.\\n`;\n }\n}\n"]}