/** * Generated-file guard (change: add-generated-file-guard). * * Agents hand-edit generated code, vendored code, and lockfiles. The edit is erased by the next * generator run, and the file the agent should have edited is one hop away and never shown. This * module answers two questions about a file, deterministically and offline: * * 1. Is it generated, vendored, or a lockfile — and by which rule? A closed, ordered rule set: * a recognized header marker in the first lines, a `.gitattributes` linguist attribute, a * lockfile name, then the existing excluded-path patterns. The first rule that matches wins * and is recorded. * 2. Where is its source, and what command regenerates it? Only what a header or a * config-invoked entry (a go-generate directive, a package.json script, a Makefile recipe, a * CI run step) names LITERALLY. Anything else is `unknown` — never guessed. * * Consumers: `orient` and `blast_radius` mark generated files in scope; `verify_change` reports the * `generated-file-edited` finding; the loop hook surfaces (add-agent-loop-hook-surfaces) can call * {@link classifyGeneratedFiles} before a write. Nothing here runs a generator or blocks an edit. */ import type { GovernanceFinding } from './mcp-handlers/enforcement-policy.js'; import type { ChangedFile } from '../../types/index.js'; /** How far into a file the header markers are looked for. */ export declare const HEADER_SCAN_LINES = 40; export declare const HEADER_SCAN_BYTES: number; /** * The closed list of generator header markers. A header that matches none of these is NOT * detected; the list is published so a team can see what is and is not recognized. */ export declare const GENERATED_HEADER_MARKERS: ReadonlyArray<{ id: string; description: string; test: (line: string, lead: RegExp) => boolean; }>; /** The lockfile names the guard recognizes, each with the manifest(s) it is generated from. */ export declare const LOCKFILE_MANIFESTS: Readonly>; export type GeneratedRule = 'header-marker' | 'gitattributes' | 'lockfile' | 'path-pattern'; export interface GeneratedFileFact { /** Path relative to the analyzed root, forward slashes. */ file: string; /** Which rule of the closed, ordered set matched first. */ rule: GeneratedRule; /** What matched within that rule: a marker id, an attribute, a lockfile name, or a path pattern. */ matched: string; kind: 'generated' | 'vendored' | 'lockfile'; /** The source file(s), as named literally by a header or a config entry; else `unknown`. */ generatedFrom: string[] | 'unknown'; /** The literal command from the config entry that writes this file; else `unknown`. */ regenerateWith: string | 'unknown'; /** Where `generatedFrom` / `regenerateWith` were read, e.g. `go:generate in gen.go:3`. */ evidence?: string[]; /** The config file holding `regenerateWith` (editing it counts as editing the source side). */ configFile?: string; /** Header-named sources that were reported as written because no repository file is at that path. */ sourceNotFound?: string[]; /** A path-pattern match is weaker evidence than a marker, attribute, or lockfile name. */ certainty: 'declared' | 'path-pattern'; } export interface ClassifyResult { facts: Map; /** Rules that could not run (e.g. not a git repository), so a quiet result is not read as "none". */ boundaries: string[]; } /** Longest repository-derived string a fact carries into agent context. */ export declare const MAX_EMITTED_TEXT = 512; export declare const WITHHELD_TEXT = "[withheld: reads like an instruction]"; /** * Repository text bound for agent context (a command, a header literal, a script name): bounded, * credential-redacted, control characters removed, and withheld when it reads like an instruction. * * Sanitizing runs BEFORE the screen and the screened text is what is returned — the order * {@link screenServedText} exists to enforce. Whole escape SEQUENCES go first * ({@link stripTerminalEscapeSequences}): dropping the ESC alone left `[31m` welded to the next * word, which is enough to walk an imperative past a word-boundary-anchored rule. */ export declare function emittedText(text: string): string; /** * The first header marker that matches, by the closed list's order. `file` (optional) selects the * comment syntax: in a Markdown or text file only an HTML comment counts, never a `#` heading. */ export declare function matchHeaderMarker(head: string, file?: string): string | null; /** Source paths a header names literally. Trailing sentence punctuation is dropped. */ export declare function headerSources(head: string, file?: string): string[]; /** * Classify files (paths relative to `root`) by the closed, ordered rule set and resolve each * generated file's source and regenerate command. Files that match no rule are absent from the * result. Best-effort per file: an unreadable file simply skips the header rule. */ export declare function classifyGeneratedFiles(rootDir: string, files: readonly string[], opts?: { raw?: boolean; }): Promise; /** Split a shell line into simple commands and tokens. Quotes are honored; nothing is expanded. */ export declare function tokenizeCommand(command: string): string[][]; /** The output paths and input candidates one simple command names. */ export declare function commandPaths(tokens: string[]): { outputs: string[]; inputs: string[]; }; /** go-generate directives: prefix test and trim, no regex — a directive line can be any length. */ export declare function goGenerateCommands(text: string): Array<{ command: string; line: number; }>; /** `scripts` of a package.json, by name in code-unit order; none when it does not parse. */ export declare function packageScripts(text: string): Array<{ name: string; command: string; }>; /** `run:` steps of a GitHub Actions workflow with their raw working directory (step, job, then workflow default). */ export declare function workflowRuns(text: string): Array<{ jobId: string; step: number; command: string; wd: unknown; }>; /** Every command a config file holds, parsed by its kind; null for a file that is not a config source. */ export declare function commandsInConfig(configFile: string, text: string): string[] | null; /** * Recipes of a Makefile with backslash continuations joined. A rule line (`a b: deps`, * `%.pb.go: %.proto`) opens a target; a variable assignment (`X := y`, `X ?= y`) does not. */ export declare function makefileRecipes(text: string): Array<{ target: string; command: string; line: number; }>; export interface ChangedPath { path: string; status: 'added' | 'modified' | 'deleted' | 'renamed'; /** False for a pure rename (no lines added or removed); unset means the content changed. */ contentChanged?: boolean; } /** * The `generated-file-edited` findings for one diff. Pure: `facts` comes from * {@link classifyGeneratedFiles} over the diff's files. * * Fires for a MODIFIED (or renamed) file only — a newly added generated file is what a generator * run produces, and a deleted one is not an edit. A generated file fires unless its known source, * or the config file holding its regenerate command, changed in the same diff; a file with an * unknown source fires with that said. A lockfile fires unless a manifest of its ecosystem at or * below its directory changed (a workspace member's manifest counts). */ export declare function generatedFileEditedFindings(changed: readonly ChangedPath[], facts: ReadonlyMap, /** Generated files whose regenerate command changed in the diff; absent: any edit to the config file counts. */ commandChanged?: ReadonlySet): GovernanceFinding[]; export interface DiffGuardResult { /** Generated, vendored, and lockfile facts for the diff's files. */ facts: GeneratedFileFact[]; findings: GovernanceFinding[]; boundaries: string[]; } /** * Classify the files a diff touches (lockfiles included) and project the `generated-file-edited` * findings. Paths are re-framed from the repository root onto `root`; files outside `root` are * out of scope. */ export declare function guardDiff(root: string, baseRef: string, prefetched?: readonly ChangedFile[]): Promise; //# sourceMappingURL=generated-file-guard.d.ts.map