import type { Writable } from 'node:stream'; import { type OutputStyle } from '@williamthorsen/nmr-core'; import type { OutputChannels } from './runner.js'; import type { CheckCacheConfig, NmrConfig } from './types.js'; /** A recorded pass: what ran, on which tree, and what it cost. */ export interface CheckCacheEntry { /** The full cache key the pass was recorded under; a hit is this matching the key computed now. */ key: string; treeHash: string; headSha: string; commandString: string; nmrVersion: string; nodeVersion: string; durationMs: number; /** ISO-8601 instant the pass completed. */ recordedAt: string; /** * The build digest each covered package's output carried when the pass was recorded. Build output is * git-ignored, so the tree hash cannot describe it; comparing these is what separates output built from this * tree from output another tree left behind. */ buildDigests: Record; /** What a skip replays in place of the run it recalls, absent on a pass that retained nothing. */ retention?: Retention; } /** What locates one command's recorded pass at one scope, and the transcript beside it. */ export interface EntryRef { anchorDir: string; command: string; monorepoRoot: string; } /** One command's excerpt, attributed to the scope and the command that produced it. */ export interface ReplayLine { command: string; excerpt: string; scope: string; } /** * What a recalled pass replays, the key certifying the excerpts describe this environment's output, and the * run that last vouched for them. * * A list rather than one excerpt: a composite's entry carries its constituents' lines, and a nested one's are * spliced into its parent's, where the attribution cannot be re-derived from the entry that holds them. * * The witness is what admits a constituent's line into the assembly its parent records: a run writes it when * it records an excerpt, and restamps it when it recalls one and replays it. */ export interface Retention { key: string; replay: ReplayLine[]; runId: string; } /** What nmr's own build has left on disk across the workspace. */ export interface BuildOutputState { /** Covered packages whose output is absent. */ missing: string[]; /** The digest of the inputs each covered package's output was built from, keyed by package name. */ digests: Record; } /** The parts of the running interpreter that can change what a check concludes. */ export interface RuntimeIdentity { arch: string; nodeVersion: string; platform: string; } /** One working tree, observed once per top-level invocation and shared with every process below it. */ export interface TreeSnapshot { hash: string; headSha: string; } /** The interpreter this process is running on, folded into every key and recorded alongside every pass. */ export declare const CURRENT_RUNTIME: RuntimeIdentity; /** Set to `1` to hear why the gate did not skip, or why it is disabled. */ export declare const DEBUG_ENV_VAR = "NMR_DEBUG"; /** * The commands cacheable without configuration: those whose whole contribution is an exit status, and that * reach nothing beyond a checkout and an install. * * Excluded on purpose. `audit` and `prepush` consult a vulnerability database that changes without the tree * (`prepush`'s `ci` constituent still skips while its `audit` always runs). `build` and `compile` carry a cache * of their own. `fix`, `fmt`, `lint`, and `upgrade` mutate the tree they are asked about. `test:all` reaches * whatever the environment supplies. Anything a repo adds here promises exit-status-only semantics through its * whole chain, hooks included. */ export declare const DEFAULT_CACHEABLE_COMMANDS: string[]; /** Set to `1` for the standing equivalent of `--no-cache`: skip the lookup, still record on success. */ export declare const NO_CACHE_ENV_VAR = "NMR_NO_CACHE"; /** Carries one run's identity down the spawned chain, so an entry can name the run that vouched for it. */ export declare const RUN_ID_ENV_VAR = "NMR_RUN_ID"; /** Carries the top-level tree snapshot down the spawned chain, so one invocation hashes the tree once. */ export declare const TREE_SNAPSHOT_ENV_VAR = "NMR_TREE_SNAPSHOT"; /** * Restamps a recalled entry's retention with the run that is replaying it, so an excerpt this run certified * can join the assembly a composite above it records. * * A recall certifies as surely as a recording does: the pass key matched, so the excerpt describes this tree, * and the caller reaches this only where the retention key matched too, so it describes this presentation * environment. Everything else the entry records stands -- the instant and the duration belong to the run that * earned the pass, and a recall must not make it read as later or longer than it was. * * A cache that cannot be written is not worth failing a green run over, so that failure goes to the debug note. */ export declare function certifyRetention(options: { anchorDir: string; command: string; entry: CheckCacheEntry; env: NodeJS.ProcessEnv; monorepoRoot: string; runId: string; stderr: Writable; }): Promise; /** * Folds everything that can change what a command concludes into one key: the tree's content, the command * string that would run, the scope it would run in, nmr's own version, the interpreter, what is installed, and * the environment variables a check can read. A hit is this key matching a recorded one, so an ingredient left * out here is an ingredient that could change while the cache still claims a pass. * * Reports a reason instead of a key when the install fingerprint cannot be read, which disables the gate. */ export declare function computeCacheKey(options: { anchorDir: string; command: string; commandString: string; env: NodeJS.ProcessEnv; monorepoRoot: string; nmrVersion: string; runtime?: RuntimeIdentity; snapshot: TreeSnapshot; }): { ok: true; key: string; } | { ok: false; reason: string; }; /** * Folds what changes a transcript without changing a conclusion onto the pass key: the channel each of the * command's output streams ran on, and the environment variables a tool presents itself through. * * Taking the pass key as an ingredient rather than recomputing its parts is what keeps the two from drifting * apart. The channel kind is what keeps a run at a terminal from replaying a piped recording; folding in raw * TTY-ness instead would be wrong under quiet mode, where the child sees pipes at a terminal and the * transcript is reproducible. */ export declare function computeRetentionKey(options: { channels: OutputChannels; env: NodeJS.ProcessEnv; passKey: string; }): string; /** Renders a snapshot for the environment of every process below this one. */ export declare function encodeTreeSnapshot(snapshot: TreeSnapshot): string; /** * Names a covered package whose output differs between two observations of it, or `undefined` when every * package agrees. A package that has appeared or disappeared between them counts as a disagreement, because * the output the earlier observation describes is not the output the later one found. */ export declare function findStaleBuildOutput(earlier: Record, later: Record): string | undefined; /** * Renders the warning for a `--no-cache` that landed after the command name, where it is an argument to the * command rather than a flag to nmr. Passing it on unchanged is the honest thing to do with an argument, so * the warning is all that separates this from a silently un-bypassed run. */ export declare function formatMisplacedNoCacheWarning(command: string, style: OutputStyle): string; /** * Reports whether a command's passes are recorded at all, which is what separates a command with no recording * from one that could never have had one. * * A hook is excluded here rather than at one caller: it is not a command anyone asks for, so nothing records * it, and a repo naming one in `extraCommands` must not make the gate and a reader of its entries disagree. */ export declare function isCacheableCommand(checkCache: CheckCacheConfig | undefined, command: string): boolean; /** * Reads the state of the build output nmr's own build covers. Build output is git-ignored, so the tree hash * says nothing about it: a `ci` whose `build` constituent is cached would otherwise skip on a tree whose `dist` * had been deleted, or whose `dist` was compiled from a different tree, and hand back a green exit over a * repository that cannot run. * * A package whose `build` or `compile` is overridden emits somewhere this does not know about, so it is left * out rather than made a permanent miss. */ export declare function readBuildOutputState(monorepoRoot: string, config: NmrConfig): Promise; /** * Reads the entry recorded for one command at one scope, or `undefined` when there is none to trust. * * Retention is vouched for separately from the pass it rides on: an excerpt of a shape this cannot read is * dropped, leaving a pass that skips cleanly and reports its verdict alone. Voiding the pass instead would * cost a full run to avoid a line nobody would have printed. */ export declare function readCheckCacheEntry(options: { anchorDir: string; command: string; monorepoRoot: string; }): Promise; /** * Reads the whole output one recorded pass retained, or `undefined` where it retained none. * * Held to nothing on its own: the entry beside it is what says which tree the bytes describe, and a caller * that has not matched the entry's key is reading a transcript of some other tree. */ export declare function readTranscript(ref: EntryRef): Promise; /** * Makes the transcript beside one entry be exactly what this pass retained, removing what an earlier pass * left when this one retained nothing. * * A composite retains nothing of its own, so without the removal a leaf's transcript would stand beside an * entry that never produced it, and `--log` would date another run's bytes by this one's instant. */ export declare function recordTranscript(ref: EntryRef, transcript: string | undefined): Promise; /** Removes every recorded pass for a monorepo, or for a standalone package outside one. */ export declare function removeCheckCache(scopeDir: string): Promise; /** * Merges a repo's `checkCache` configuration into the default set. Extending rather than replacing means * declaring one command cannot silently drop the defaults; excluding is how a repo retires a name whose chain * turned out to do more than report an exit status. */ export declare function resolveCacheableCommands(checkCache: CheckCacheConfig | undefined): Set; /** * Resolves the identity of the run this invocation belongs to: the one an ancestor nmr process passed down, * and otherwise a fresh one, this invocation being where the run starts. * * Unbounded where the tree snapshot is bounded by a HEAD comparison: a process that outlives its run hands a * stale identity to the invocations it later makes, and what keeps that harmless is the tree hash every * constituent entry is held to before its excerpt joins an assembly. */ export declare function resolveRunId(env: NodeJS.ProcessEnv): string; /** * Resolves the tree snapshot this invocation gates on: the one a parent nmr process already took, when there * is one, and otherwise a fresh hash of the working tree. Reports a reason instead when no snapshot can be * had, which disables the gate. * * The monorepo root must be the git toplevel. A repository holding the monorepo inside a subdirectory has * content outside it that the checks may still read, and a hash covering more than the monorepo would move * for edits that cannot affect it. */ export declare function resolveTreeSnapshot(options: { monorepoRoot: string; env: NodeJS.ProcessEnv; }): { ok: true; snapshot: TreeSnapshot; } | { ok: false; reason: string; }; /** Records a pass, replacing whatever this command last recorded at this scope. */ export declare function writeCheckCacheEntry(options: { anchorDir: string; command: string; entry: CheckCacheEntry; monorepoRoot: string; }): Promise; /** * Writes a note explaining a gate decision, but only when `NMR_DEBUG=1`. The gate is silent by default: a * reason to run is not news, and a line per invocation explaining why nothing was skipped would bury the * output of the command that did run. */ export declare function writeDebugNote(message: string, env: NodeJS.ProcessEnv, stderr: Writable): void;