import type { BashContext, Violation } from '../types'; import { BashRuleBase, EmptyRuleConfig } from '../rule-base'; import { FixHint } from '../fix-hint'; /** * Blocks a Bash command that PIPES or REDIRECTS the output of `pnpm wp-build`, * `pnpm wp-review-upsert-pr` or `pnpm wp-finish-upsert-pr`, and hands back the bare command plus a * `grep` of the log those commands already write. * * ─── The incident, measured ──────────────────────────────────────────────────────────────────────── * The Claude Code harness backgrounds — and then kills — a foreground Bash command that has produced * no output for 600 seconds. Those three commands defend against that already: the build's output goes * to a FILE, and the console gets a heartbeat roughly every ten seconds plus a `FullLog :` pointer. * * A PIPE deletes that defence. `pnpm wp-review-upsert-pr 2>&1 | tail -50` withholds every byte until * the writer exits, because that is what `tail` is — so the terminal sees NOTHING for the length of a * full build, the watchdog fires, and the work is thrown away. The last fleet audit attributed ≥100 * minutes of wall time to those kills across 8 agents, and this repo's primary tree alone holds 85 * piped `wp-*` calls, 42 of them on a build-running command (`wp-review-upsert-pr` 22, * `wp-finish-upsert-pr` 17, `wp-build` 3). * * A `> file` redirect is the same silence, and additionally writes a second copy of a log the command * already wrote. * * ─── Why the ORDER of the fix matters, and why this half came second ─────────────────────────────── * Agents pipe these commands for a REASON: stage ② and stage ③ used to print everything they did. So * the output was shrunk FIRST — both stages now capture their verbose body to a log and keep only the * lines an agent must act on (see `StageOutputLog` in @webpieces/pr-gate). Blocking the pipe before * that would have traded a watchdog kill for a flooded context, and an agent would have routed around * it with `> file` — which is why the redirect is on the blocklist too. * * ─── It acts UNCONDITIONALLY, and has NO config key ──────────────────────────────────────────────── * Like `commit-message-substitution-guard`, and for the same two reasons. * * It has no key because a NEW key under `hookGuards` is a key every consumer must ADD or have every * Bash call blocked on upgrade (fault Y) — that shipped once already, with `whole-repo-build-guard`, * and took upgrading consumers' shells down for a feature nobody had asked for. Loading here, outside * the config-driven set, makes the config-sync check structurally unable to see it. * * It needs no switch because there is nothing for one to rescue: the cure is available for every * input, is strictly better than what was blocked, and can never itself match this guard. Running the * command bare gives you a heartbeat instead of silence and a log you can `grep` any number of times * instead of a fixed 50 lines you have to re-run a build to change. */ export declare class BuildOutputPipeGuardRule extends BashRuleBase { constructor(); private readonly scan; readonly description: string; get fixHint(): FixHint; check(ctx: BashContext): readonly Violation[]; private allow; private block; private message; private what; private truncate; private logDecision; }