export interface CommandResult { /** The process's exit code, or `null` when a signal killed it. */ readonly code: number | null; readonly signal: NodeJS.Signals | null; readonly stdout: string; readonly stderr: string; /** * Whether the stream hit `MAX_OUTPUT_BYTES` and lost its tail. * * Reported rather than left to be inferred from the marker in the text: a * caller that parses the output needs to know the difference between "this * program emits garbage" and "we cut its output in half", and only this file * knows which happened. */ readonly stdoutTruncated: boolean; readonly stderrTruncated: boolean; /** * Whether the streams were read to their end before this result was produced. * * Normally true: the result is built when the pipes close, so everything the * command wrote is here. It is false only on the backstop path below — the * process has exited but something still holds its pipe open, so we answer * with what was buffered rather than never answering at all. * * A SEPARATE field from `stdoutTruncated` rather than a second way to set it, * because the two have different causes and different remedies, and the caller * acts on the difference: `stdoutTruncated` means the output exceeded * `MAX_OUTPUT_BYTES` and the fix is to ask for less (see * `lint-intent-annotations.ts`'s "narrow the run with `paths`"), which would * be advice about the wrong problem here. This tail was lost to a leaked file * descriptor and asking for less output would not change it. * * Where that difference is acted on, so this stays checkable rather than * merely asserted: `parseReport` in `lint-intent-annotations.ts` consults this * on both of its failure branches — `undrainedPipe` there names the * pipe-holder instead of blaming the linter or `SPECGUARD_LINT_COMMAND`, and * is ordered after the truncation branch for the run that manages both. Note * it is consulted only where something is already missing: false here does NOT * mean the result is incomplete, only that the tail was not waited for, so a * run whose last undrained byte was a newline is an ordinary success. */ readonly outputDrained: boolean; } export interface RunCommandOptions { readonly cwd?: string | undefined; readonly timeoutMs?: number | undefined; /** * Extra guidance appended when the program is not on PATH. * * Supplied by the caller because only the caller knows which env var * configures ITS command. A generic helper that hard-codes one tool's variable * name starts lying the moment a second tool spawns something else. */ readonly notFoundHint?: string | undefined; } export type RunCommand = (argv: readonly string[], options?: RunCommandOptions) => Promise; /** Beyond this, a wrapped command's output is truncated rather than buffered. */ export declare const MAX_OUTPUT_BYTES: number; /** Default ceiling on how long a wrapped command may run. */ export declare const DEFAULT_COMMAND_TIMEOUT_MS = 120000; /** * How long `close` may lag `exit` before the result is produced without it. * * `exit` and `close` are two events, not one: `close` additionally waits for * every writer of the child's stdout/stderr pipes to let go, and a process that * is not the child can be holding them. Long enough that the ordinary * milliseconds-apart drain is never cut short; short enough that a leaked * descriptor costs a truncated tail instead of a call that never returns. */ export declare const EXIT_CLOSE_GRACE_MS = 1000; /** * The pids of every run currently spawned and not yet reaped. * * Exported for the teardown handler's diagnostics. A PROJECTION, not the set: * it drops entries whose spawn never produced a process, because a `undefined` * pid is not something a diagnostic can name or a caller can signal. * * That filter makes it the WRONG observer for asserting the set does not leak — * see `outstandingRunCount`. */ export declare function outstandingRunPids(): readonly number[]; /** * How many runs are registered, counting those whose spawn produced no pid. * * The unfiltered companion to `outstandingRunPids`, and the one a leak test must * use. The distinction is not pedantic: the spawn-failure path registers a child * whose `pid` is `undefined`, so it is invisible to `outstandingRunPids` BY * EXACTLY THE PROPERTY THAT MAKES IT A LEAK. A test asserting that path through * the pid projection holds whether or not the deregistration happens, and would * stay green if a refactor dropped it. * * The leak is memory-only — `killRun` returns `false` for an undefined pid, so a * stranded entry is skipped by the drain rather than mis-signalled — but it is * one entry per failed spawn for the life of the server, and it is only ever * observable from outside. Hence a reader of the set itself. */ export declare function outstandingRunCount(): number; /** * Kills every run still in flight, and answers how many it signalled. * * The teardown path. SYNCHRONOUS AND UNBOUNDED BY NOTHING — it sends signals and * returns, it never waits for a child to die. That is deliberate: this runs from * a SIGINT/SIGTERM handler, where anything that waits is something that can hang * the shutdown it was supposed to perform. SIGKILL is not refusable, so there is * no acknowledgement worth waiting for. * * It reuses `killRun` rather than re-deriving the kill, so the group-vs-child * fallback and the spawn-failure guard have exactly one implementation. The set * is snapshotted before iterating because `killRun` can drive an `exit` that * mutates it. */ export declare function killOutstandingRuns(): number; /** * Runs a program with an argument LIST, never through a shell. * * `spawn` without `shell: true` is the load-bearing detail: tool arguments * arrive from a model, so a file path containing `; rm -rf …` has to be a * path that does not exist rather than a command. There is no escaping to get * right because nothing is ever parsed as syntax. * * A non-zero exit is NOT an error here. `specguard-lint` uses its exit code as * a three-valued verdict — 0 clean, 1 malformed annotations, 2 the tool could * not do its job — so deciding what a code means is the caller's job and this * function reports it. What IS an error is the process never running (missing * binary) or never finishing (timeout): both leave the caller with no verdict * at all, which is the one outcome that must not be mistaken for a clean run. */ export declare const runCommand: RunCommand;