import { CommandScanner } from '../command-scan'; import { ShellSegmentScan } from './shell-segment-scan'; /** * Decides the one question `build-output-pipe-guard` asks: does this command BOUND the output of a * webpieces command that already writes its output to a file? * * ─── The three commands, and why only these three ────────────────────────────────────────────────── * `wp-build`, `wp-review-upsert-pr` and `wp-finish-upsert-pr` are the commands that RUN A BUILD. Each * one redirects the build's full stdout+stderr to a log file, prints a ~10-second heartbeat while it * runs, and ends with a `FullLog :` pointer at the file. Nothing else in the `wp-*` family both takes * minutes and writes a log, so nothing else belongs on this list — a guard that also refused * `pnpm wp-cleanup | tail` would be refusing something with no cure. * * ─── What is a hit ───────────────────────────────────────────────────────────────────────────────── * BLOCKED a PIPE out of the command — `pnpm wp-build | tail -50`, `… 2>&1 | grep error`, `| tee`. * A pipe is the measured hazard and it does not depend on WHAT it feeds: the pipeline's * reader (`tail`, `head`, `grep`, `wc`) withholds every byte until the writer EXITS, so the * heartbeat never reaches the terminal, the harness sees a command silent for 600 seconds, * and it kills a full build. Measured in this repo's own call log: 85 piped `wp-*` calls, * 42 of them on one of these three commands. * BLOCKED a stdout REDIRECT to a file — `pnpm wp-build > /tmp/out.log`. Same silence, and it also * makes a SECOND copy of a log the command already wrote. * * ALLOWED the bare command, which is the whole cure. * ALLOWED `2>&1` on its own — it rewires fds and buffers nothing. `ShellSegmentScan.redirectsToFile` * owns that carve-out, and it is shared rather than re-spelled here. * ALLOWED every other command, piped or not. This guard is blocklist-shaped and narrow on purpose. */ /** The commands whose output already goes to a file, so bounding it can only ever lose information. */ export declare const LOGGED_BUILD_COMMANDS: readonly string[]; /** How the output was bounded. The two shapes read differently in a refusal, so they are named. */ export declare const BOUND_BY_PIPE = "pipe"; export declare const BOUND_BY_REDIRECT = "redirect"; /** One bounded invocation: WHICH command, and HOW its output was bounded. Data-only (per CLAUDE.md). */ export declare class BoundedOutputHit { command: string; shape: string; constructor(command: string, shape: string); } export declare class BuildOutputPipeScan { private readonly scanner; private readonly segments; constructor(scanner: CommandScanner, segments: ShellSegmentScan); /** The first bounded logging command in `command`, or null when there is none. */ firstHit(command: string): BoundedOutputHit | null; private loggingCommandIn; }