import type { SearchResult, TotemConfig } from '@mmnto/totem'; import type { ExemptionShared } from '../exemptions/exemption-schema.js'; import { type ShieldFinding, type ShieldStructuredVerdict } from './shield-templates.js'; export { MAX_DIFF_CHARS, SHIELD_LEARN_SYSTEM_PROMPT, STRUCTURAL_SYSTEM_PROMPT, } from './shield-templates.js'; interface RetrievedContext { specs: SearchResult[]; sessions: SearchResult[]; code: SearchResult[]; lessons: SearchResult[]; } export declare function buildFileContext(changedFiles: string[], cwd: string, maxLines: number, maxChars: number): Promise; export declare function assemblePrompt(diff: string, changedFiles: string[], context: RetrievedContext, systemPrompt: string, smartHints?: string[], fileContext?: string, generatedArtifactSummary?: string): string; export declare function assembleStructuralPrompt(diff: string, changedFiles: string[], systemPrompt: string, smartHints?: string[], fileContext?: string, generatedArtifactSummary?: string): string; export declare function parseVerdict(content: string): { pass: boolean; reason: string; } | null; export type StructuredVerdictExtractionLayer = 'xml' | 'fence' | 'bare-json'; export type StructuredVerdictExtractionFailureCause = 'empty-output' | 'no-candidate' | 'invalid-json' | 'schema-invalid'; export interface StructuredVerdictExtractionAttempt { layer: StructuredVerdictExtractionLayer; cause: Extract; /** Bounded validation summaries; candidate/output text is never retained here. */ issues?: string[]; } export type StructuredVerdictExtractionResult = { ok: true; verdict: ShieldStructuredVerdict; layer: StructuredVerdictExtractionLayer; } | { ok: false; cause: StructuredVerdictExtractionFailureCause; attempts: StructuredVerdictExtractionAttempt[]; }; /** * Three-layer JSON extraction with bounded, candidate-free failure diagnostics. * A schema-invalid candidate takes precedence over malformed candidates because * it is the most semantically advanced failure reached by the cascade. */ export declare function extractStructuredVerdictDetailed(content: string): StructuredVerdictExtractionResult; /** * Compatibility wrapper for callers that only need the parsed verdict. * Returns null when all XML, fenced, and bare-JSON layers fail. */ export declare function extractStructuredVerdict(content: string): ShieldStructuredVerdict | null; /** * Deterministic pass/fail based on findings. * CRITICAL = fail, WARN/INFO = pass with advisory. */ export declare function computeVerdict(verdict: ShieldStructuredVerdict): { pass: boolean; reason: string; }; /** * Human-readable output for stderr. * Groups findings by severity (CRITICAL → WARN → INFO) with colored header. */ export declare function formatVerdictForDisplay(verdict: ShieldStructuredVerdict, pass: boolean): string; /** * Pure content-hash computation for the reviewed-source flag (Prop 304 R2, * codex fold 1). Hashes all tracked source-file objects whose extension is in * `extensions` — the extension-scoped tracked-source content hash that * authorizes an agent push. NO writes: neither the cache flag nor the * canonical `review-extensions.txt` refresh happen here, so a caller can * compute the hash BEFORE invoking the reviewer and stamp it only if the tree * is unchanged afterward — closing the mid-run authorization race. * * This is a DIFFERENT hash domain from `diffScope.diffHash` (the masked * review-payload identity); the two bind different state and are never equal. * * Returns the hex sha256, or `null` when there are no tracked source files (or * the git plumbing is unavailable — the flag is a best-effort hook * convenience, so failures are swallowed rather than thrown). * * The `extensions` parameter drives which file types are hashed. Defaults to * the historical hardcoded set for backward compatibility with callers that * predate #1527. The set must be pre-validated (see * `ReviewSourceExtensionSchema` in core); values are passed as `git ls-files` * glob arguments via safeExec and the regex refinement is the shell-injection * boundary. */ export declare function computeReviewedContentHash(cwd: string, configRoot?: string, extensions?: readonly string[]): Promise; /** * Stamp `/cache/.reviewed-content-hash` with EXACTLY the supplied * hash — never recomputes (Prop 304 R2, codex fold 1). Also refreshes the * canonical `review-extensions.txt` so the bash pre-push hook keys off the * same extension set (#1527). Best-effort; a write failure is non-fatal (the * flag is a PreToolUse-hook convenience). The caller owns hash provenance: * pass the pre-fan hash so the stamp authorizes the exact tree that was * reviewed, not whatever the tree happens to be at stamp time. */ export declare function writeReviewedContentHashValue(precomputedHash: string, cwd: string, totemDir: string, configRoot?: string, extensions?: readonly string[]): Promise; /** * Write the .reviewed-content-hash flag on PASS. * Uses a content hash of tracked source files (not Git SHA) so the flag * survives commits, amends, and rebases. Only breaks when source files change. * * Now a thin compose of the pure computer + explicit writer (Prop 304 R2): it * hashes the CURRENT tree and stamps it. Retained at its original signature for * the no-changes stamp (an empty diff opens no mid-run LLM window) and * `recordShieldOverride`, where there is no drift race to guard. * * NO LONGER used by the deterministic skip paths (all-non-code / filtered-empty / * all-generated). Those drop the entire diff without examining it, so they are * non-reviews and must not stamp — see `NO_STAMP_NOTICE` (mmnto-ai/totem#2466). * The LLM review path does NOT use this — it * captures the hash pre-fan and compare-and-stamps in `shieldCommand` / * `handleVerdictResult` so a mid-review edit can never be authorized. */ export declare function writeReviewedContentHash(cwd: string, totemDir: string, configRoot?: string, extensions?: readonly string[]): Promise; /** * Record a shield override: append the override event to the Trap Ledger * AND stamp the reviewed-content-hash so the push-gate hook unblocks. * * mmnto-ai/totem#1716: prior to this helper the override branch only wrote the ledger * entry; the missing stamp left the contributor stuck behind the push-gate * with a tribal-knowledge `git reset --soft HEAD~1 && totem review --staged` * workaround. Override is a legitimate completion path (with logged * justification) and must produce the same cache state as a passing review. * * LEGACY SINGLE-LANE ONLY: this stamps the CURRENT tree hash (a recompute). The * multi-lane fan must never use it — its long LLM window makes a mid-run edit real, * so the fan goes through {@link recordShieldOverrideWithExpectedHash}, which binds * the PRE-FAN hash and refuses to stamp a tree that no longer matches it (Prop 304 * rev-5 item 1). */ export declare function recordShieldOverride(params: { override: string; cwd: string; totemDir: string; configRoot?: string; sourceExtensions?: readonly string[]; }): Promise; /** {@link recordShieldOverrideWithExpectedHash} parameters. */ export interface ShieldOverrideWithExpectedHashParams { /** The trap-ledgered justification. */ override: string; cwd: string; totemDir: string; configRoot?: string; sourceExtensions?: readonly string[]; /** * The PRE-FAN content hash the stamp must bind — the exact tree the lanes reviewed. * `null` means there was no tracked source to authorize (the fan's legacy no-op * case): the override is still ledgered, nothing is stamped. */ expectedContentHash: string | null; /** * Injectable current-tree re-hasher (test seam). Defaults to * `computeReviewedContentHash(cwd, configRoot, sourceExtensions)` — the SAME * computation that produced `expectedContentHash` pre-fan. */ computeCurrentHash?: () => Promise; } /** * Ledger + EXPLICIT-HASH override primitive (Prop 304 rev-5 item 1 — codex critical). * * `--override` on the fan path must never stamp an UNREVIEWED tree: the fan's one * post-fan compare happens before verdict assembly, so an edit landing after that * compare but before the stamp would — under `recordShieldOverride`'s current-tree * recompute — be stamped as reviewed. This primitive closes that window: * * 1. The override event is ALWAYS appended to the Trap Ledger (the operator's * justification is auditable whether or not a stamp lands). * 2. IMMEDIATELY ADJACENT to the stamp write, the current tree hash is recomputed * once more and compared to the caller's PRE-FAN `expectedContentHash`. * 3. Match ⇒ stamp EXACTLY `expectedContentHash` via the explicit writer (never a * recompute value). Mismatch ⇒ LOUD refusal, no stamp — the ledger records the * override WITHOUT a stamp, and the return value says so. * * Returns `{ stamped }` so the caller can report honestly. */ export declare function recordShieldOverrideWithExpectedHash(params: ShieldOverrideWithExpectedHashParams): Promise<{ stamped: boolean; }>; export type ShieldFormat = 'text' | 'sarif' | 'json'; export interface ShieldOptions { raw?: boolean; out?: string; model?: string; fresh?: boolean; staged?: boolean; /** Explicit ref range for `git diff` (mmnto-ai/totem#1717). Bypasses implicit fallback chain. */ diff?: string; /** * Force the branch-vs-base (push-gate) diff scope (mmnto-ai/totem#2091). * Mutually exclusive with `staged` and `diff`. */ branch?: boolean; /** * Explicit base branch name for the forced branch-vs-base scope * (mmnto-ai/totem#2091). Implies `branch`; resolved via `getGitBranchDiff`'s * origin-preference logic (mmnto-ai/totem#2054). */ base?: string; mode?: 'standard' | 'structural'; learn?: boolean; yes?: boolean; override?: string; suppress?: string[]; autoCapture?: boolean; /** * Pre-flight deterministic-rule estimator (mmnto-ai/totem#1714). When * true, `shieldCommand` short-circuits to `runEstimate` in * `shield-estimate.ts`: same diff-resolution chain as the LLM review * path, then `runCompiledRules` against `compiled-rules.json`, then * return — no orchestrator, no embedder, no LanceDB. Output is labeled * `[Estimate]` (`ESTIMATE_DISPLAY_TAG`) instead of `[Review]` so log * lines unmistakably read as a forecast. Mutually incompatible with * `--learn`, `--auto-capture`, `--override`, `--suppress`, `--fresh`, * `--mode`, and `--raw` — these only apply to the LLM path; combining * them throws `TotemConfigError CONFIG_INVALID`. */ estimate?: boolean; /** * Pattern-history overlay opt-out (mmnto-ai/totem#1731). Default `true` * (enabled) when undefined; opt out via `--no-history`. Only effective * with `--estimate`; silently ignored on the LLM path. Commander * auto-inverts the negative flag, so the user-facing surface is * `--no-history` and this field receives `false` when the flag is set. */ history?: boolean; /** * Explicit round-chain override for the multi-lane fan (Prop 304 R2, * mmnto-ai/totem#2106). A prior verdict's content hash: the next round links * to it (its round + 1). A lineage mismatch warns and proceeds (honoring the * explicit intent). Only meaningful when `review.lanes` is configured and the * fan path runs; ignored on the legacy single-lane path. */ continues?: string; /** * Non-zero exit opt-in for the multi-lane fan (Prop 304 R2 / Gate G5). `'critical'` * or `'warn'`: when the fan round has findings at/above that severity OR is not * cache-eligible, the fan exits via `SHIELD_FAILED`. Absent ⇒ the fan defaults to * sensor exit 0 (a findings-bearing verdict never gates a naive CI consumer without * this opt-in). `--override` converts a `--fail-on` failure to a pass. Ignored on the * legacy single-lane path (which keeps its own labeled-compat-debt exit contract). */ failOn?: 'critical' | 'warn'; /** * Executable covariate transport (Prop 304 rev-5 item 4). Read-only, zero-LLM: * resolves the CURRENT lineage exactly as the review fan does, loads the latest * verdict artifact for it, and prints the core-owned covariate line to stdout. * No verdict for the lineage ⇒ loud sensor message, exit 0. Short-circuits before * any LLM/engine work; nothing is stamped or written. Admission-aware since * mmnto-ai/totem#2473: a not-applicable current state resolves the exact-identity * admission record instead of the verdict store. */ covariate?: boolean; /** * Declared disposition→exit mapping for gate wiring (mmnto-ai/totem#2473 ruling * item 2 — the regenerated managed pre-push hook's form). The flag IS the * wiring's declared mapping (ADR-109: the engine states verdicts, the wrapper * maps disposition → exit): known not-applicable admissions → 0 (an explicit * coverage pass, disclosed), completed rounds → 0 (findings are report-only in * hook context, #2551), hard failures → nonzero (unchanged), an UNKNOWN * disposition → nonzero fail-closed (the one exit-semantic difference from the * bare sensor). Contradictory with `--fail-on` (hook context asserts * findings-report-only; a severity gate asserts the opposite). */ gate?: boolean; } export declare function learnFromVerdict(verdictContent: string, diff: string, options: ShieldOptions, config: TotemConfig, cwd: string, configRoot?: string): Promise; /** @internal — exported for testing */ export declare function captureObservationRules(findings: ShieldFinding[], cwd: string, config: TotemConfig, configRoot: string | undefined): Promise; /** * The pure result of reviewing one lane's raw model output: the extracted * verdict, the exemption-filtered findings, and conformance — no display, * cache, or throw side effects. This is the seam the multi-lane fan (a later * slice) calls once per lane. */ export interface LaneOutcome { /** * The extracted structured verdict, or `null` when the model output was not * extractable by the shared cascade (malformed / unstructured). The null is * a distinguishable abstention signal — never a throw — so a fan lane can * record it as `abstained` instead of aborting the whole run. */ structuredVerdict: ShieldStructuredVerdict | null; /** Actionable findings after the exemption filter — drives pass/fail. */ filteredFindings: ShieldFinding[]; /** * Exempted findings, downgraded to INFO by the exemption filter. Surfaced so * the single-lane display can still show them; never counted toward pass. */ exemptedFindings: ShieldFinding[]; /** CRITICAL-free after exemptions ⇒ `true`. Also `false` for the null case. */ pass: boolean; } /** * Pure per-lane outcome derivation (Prop 304 R2 — codex fold 3). Runs the * single shared `extractStructuredVerdict` cascade, applies the exemption * filter, and computes conformance, with NO display / cache / throw side * effects. Unextractable output surfaces as `structuredVerdict: null` (a * distinguishable abstention) rather than a throw, so a fan lane can record it * as `abstained`. * * `shared` exemptions are passed IN (not read from disk) to keep this * side-effect-free; the caller owns exemption I/O and any `--suppress` * mutation before invoking. */ export declare function deriveLaneOutcome(content: string, shared: ExemptionShared): Promise; /** * Two-hash-domains authorization fix (Prop 304 R2, codex fold 1). On a PASS, * re-hash the CURRENT tracked-source tree and compare to the `preFanContentHash` * captured before the reviewer ran. A mismatch means a mid-review edit landed: * the verdict is bound to a tree that no longer exists on disk, so refuse to * stamp — and say so loudly. On an unchanged tree, stamp EXACTLY the pre-fan * hash (never a recompute) via the explicit writer. * * A `null` pre-fan hash means there were no tracked source files (or git * plumbing was unavailable) before the fan; the legacy path wrote nothing in * that case either, so this is a no-op — preserving prior behavior. */ export declare function stampReviewedContentHashIfTreeUnchanged(preFanContentHash: string | null, cwd: string, config: TotemConfig, configRoot: string | undefined): Promise; interface IncrementalResult { eligible: boolean; reason?: string; deltaDiff?: string; changedFiles?: string[]; linesChanged?: number; } export declare function evaluateIncrementalEligibility(cwd: string, totemDir: string, configRoot?: string): Promise; /** The resolved-diff subset admission consumes (structural — avoids a git.ts type export). */ interface AdmissionDiffResult { diff: string; changedFiles: string[]; source: 'explicit-range' | 'staged' | 'uncommitted' | 'branch-vs-base'; base?: string; head?: string; selectorForm?: string; } /** * The resolver's discriminated no-changes shape (git.ts `DiffForReviewEmpty`): * the RESOLVED terminal scope that produced no diff. The `no-diff` record * binds THIS scope — never a synthetic `source: 'none'` that discards what the * resolver already resolved (codex conformance note 1 on mmnto-ai/totem#2473). */ interface AdmissionDiffEmpty { empty: true; source: 'explicit-range' | 'staged' | 'uncommitted' | 'branch-vs-base'; base?: string; head?: string; selectorForm?: string; } export interface AdmissionEvaluationInput { /** * `null` is the legacy/caller-without-scope shape (records `source: 'none'`); * production always passes the resolver's result or its discriminated empty. */ diffResult: AdmissionDiffResult | AdmissionDiffEmpty | null; /** The REQUESTED selector expression — the empty arms' selector fallback identity. */ requestedSelector: string; cwd: string; config: TotemConfig; /** Suppress payload-prep logging (the read-only `--covariate` re-derivation). */ quiet: boolean; } export type AdmissionOutcome = { status: 'admitted'; diff: string; changedFiles: string[]; filteredDiff: string; filteredFiles: string[]; generatedArtifactSummary: string | undefined; scope: import('@mmnto/totem').AdmissionScope; inputHash: string; projectionPolicyHash: string; } | { status: 'not-applicable'; reason: import('@mmnto/totem').NotApplicableReason; scope: import('@mmnto/totem').AdmissionScope; inputHash: string; projectionPolicyHash: string; skippedFileCount: number; /** For the dim stderr listing only — never persisted (count-only record). */ skippedFiles: string[]; }; /** The requested selector expression, recorded as the no-diff arm's scope identity. */ export declare function requestedSelectorForm(options: ShieldOptions): string; /** * The closed execution-payload type the review path consumes (codex * conformance note 3 on mmnto-ai/totem#2473): payload SELECTION is a * post-admission step — a revalidated reviewable delta, or the admitted * full-scope payload — and applicability can never change underneath the * fan (the admitted verdict stands regardless of which payload is selected). */ export interface ExecutionPayload { diff: string; changedFiles: string[]; filteredDiff: string; filteredFiles: string[]; generatedArtifactSummary: string | undefined; } /** * Select the execution payload for an ADMITTED run: the incremental delta when * it is eligible AND re-projects to something reviewable, else the admitted * full-scope payload. A non-reviewable delta (docs-only since the last pass) * falls back — it can never demote the run to a skip, because admission * already admitted the full scope. Pure selection; the caller owns logging. */ export declare function selectExecutionPayload(admitted: Extract, incremental: IncrementalResult, cwd: string, quiet: boolean): Promise<{ payload: ExecutionPayload; narrowed: boolean; deltaFallbackReason?: string; }>; /** The effective selection policy whose projection produced the admission outcome. */ export declare function buildProjectionPolicy(config: TotemConfig, cwd: string): Promise; /** * The closed admission evaluator (mmnto-ai/totem#2473 ruling item 1). Either * an admitted payload (the exact fan inputs) or `not-applicable` with the * exact record inputs. Read-only — the caller owns record emission. */ export declare function evaluateAdmission(input: AdmissionEvaluationInput): Promise; /** * Disposition→exit mapping for the admission phase. Bare review and `--gate` * both map every KNOWN not-applicable reason to exit 0 (the ruled * no-nonzero-by-default shape). The difference is the unknown arm: `--gate` is * a DECLARED mapping, so an unknown disposition fails CLOSED (nonzero via the * supplied error ctor) while the bare sensor warns and stays 0. */ export declare function resolveNotApplicableExit(reason: string, gate: boolean, errCtor: new (code: 'SHIELD_FAILED', message: string, hint: string) => Error): 0; export declare function shieldCommand(options: ShieldOptions): Promise; //# sourceMappingURL=shield.d.ts.map