/**
 * crossModelReviewer — Step B of the tiered development process.
 *
 * Re-platforms the `/spec-converge` external "cross-model" reviewer onto the
 * agent's own installed `codex` CLI. The external pass that the skill used to
 * describe as running "via the /crossreview pattern" (a never-built
 * placeholder) is now a grounded mechanism: detect whether a supported reviewer
 * framework is installed + authed, assemble the cross-model reviewer prompt
 * (spec + referenced context, bounded to a budget), and run it THROUGH the
 * existing `CodexCliIntelligenceProvider` (the factory with
 * `framework: 'codex-cli'`, model `capable` → GPT-tier).
 *
 * Design invariants (see docs/specs/codex-crossreview-stepB-spec.md):
 *   - Detection is a pure function with injectable inputs (no real spawns in
 *     unit tests). It is SIGNAL-ONLY — it never throws and never blocks.
 *   - Reviewer invocation reuses the provider (its scratch-dir clean-notepad,
 *     env allowlist, `--skip-git-repo-check`, and the account-global circuit
 *     breaker the factory wraps it in). The ONLY new spawn-adjacent code is
 *     prompt assembly + result parsing.
 *   - Every failure mode routes toward internal-only convergence or a captured
 *     raw finding — never a stall. `unavailable` (no framework) is distinct
 *     from `degraded` (framework present, this call failed) and
 *     `skipped-abbreviated` (author chose the fast path).
 *
 * codex is the FIRST supported framework; gemini-cli is the SECOND (Piece 3 of
 * docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md — cross-model convergence
 * hardening). The registry (`SUPPORTED_REVIEWER_FRAMEWORKS`) remains the single
 * seam for further frameworks. Adding a framework is one registry entry + one
 * `id`-union extension — no skill change.
 *
 * Piece 3 additions (all signal-only, never-throw, same invariants as above):
 *   - `detectGeminiReviewer` + the gemini registry entry (family diversity).
 *   - `detectAllCrossModelReviewers` — collect EVERY available framework, not
 *     just the first match, so the skill runs one external pass per family.
 *   - `isConcreteReviewerModel` — the fail-loud model canary: a tier word
 *     ('capable', 'fast', …) falling through model resolution degrades the
 *     review LOUDLY instead of silently selecting a dead reviewer.
 *   - `hashSpecReviewableBody` — delta-gating: externals re-run only when the
 *     spec's reviewable body (frontmatter stripped) actually changed.
 *   - `recordFrameworkActivationObservation` / `wasNonClaudeFrameworkActiveWithin`
 *     — the durable standing-framework baseline: activation is judged against
 *     a lookback window of recorded observations, not a just-in-time reading,
 *     so a just-before-converge framework deactivation cannot exempt a spec.
 *   - `TRUSTED_REVIEWER_FRAMEWORKS` — the provider allowlist (no spec egress
 *     to untrusted/custom endpoints).
 */
import { type IntelligenceFramework } from './intelligenceProviderFactory.js';
/**
 * Per-call timeout for a cross-model spec review. A reasoning review of a
 * full spec is far heavier than the provider's 30s judgment-call default, so
 * Step B bumps it to 120s (spec §2).
 */
export declare const REVIEW_TIMEOUT_MS = 120000;
/**
 * Total context budget (spec + referenced docs) inlined into the reviewer
 * prompt. codex runs in an empty read-only scratch dir with no repo access,
 * so referenced context MUST be inlined; this bounds the prompt size (spec §2).
 * The spec is always included in full; referenced context fills the remainder
 * and is truncated (with a loud note) if it overflows.
 */
export declare const CONTEXT_BUDGET_BYTES: number;
/**
 * Deterministic priority ordering for referenced context (spec §2, F4).
 *
 * When the 60KB budget can't hold every referenced doc, truncation MUST be
 * deterministic — the same spec + same docs always drop the same docs — so a
 * review is reproducible and the "what got dropped" note is stable. The
 * constitutional / lessons docs are the highest-value context for a reviewer
 * (they're what the lessons-aware internal reviewer reads), so they are kept
 * FIRST; everything else keeps the spec-declared link order (the order the
 * caller passed the docs in, which is the order they appear in the spec).
 *
 * A doc whose path contains one of these substrings sorts ahead of the rest,
 * in THIS order. Ties (and all non-priority docs) preserve the caller's order
 * via a stable sort.
 */
export declare const CONTEXT_PRIORITY_SUBSTRINGS: readonly string[];
/**
 * Order referenced context deterministically: constitutional/lessons docs
 * first (per CONTEXT_PRIORITY_SUBSTRINGS), then the caller's spec-declared link
 * order for the rest. A stable sort on the priority rank achieves both — equal
 * ranks keep their input order. Pure; never mutates the input.
 */
export declare function orderContextDeterministically(context: readonly ReferencedContextDoc[]): ReferencedContextDoc[];
/**
 * Reasons a supported reviewer framework is unavailable. Mirrors the
 * Rule-1 / auth-probe vocabulary so a report can render a specific
 * remediation.
 */
export type CrossModelUnavailableReason = 'codex-not-installed' | 'codex-not-authed' | 'codex-auth-apikey-forbidden' | 'gemini-not-installed' | 'gemini-not-authed' | 'no-supported-framework';
export interface CrossModelDetectionResult {
    available: boolean;
    /** Present when available; the framework id that will run the review. */
    framework?: IntelligenceFramework;
    /** Present when available; the concrete model the review resolves to. */
    model?: string;
    /** Present when unavailable; a specific machine-readable reason. */
    reason?: CrossModelUnavailableReason;
}
/**
 * Injectable inputs for `detectCrossModelReviewer` so the detection logic is
 * unit-testable without real spawns or a real `~/.codex/auth.json`.
 */
export interface CrossModelDetectInputs {
    /**
     * Path to the codex binary if detected, else null. Defaults to
     * `detectCodexPath()` (PATH + asdf/nvm-shim resolution).
     */
    codexPathDetected?: string | null;
    /**
     * Path to the codex auth.json. Defaults to
     * `${CODEX_HOME || ~/.codex}/auth.json`.
     */
    authJsonPath?: string;
    /** Process env (for the Rule-1 OPENAI_API_KEY probe). Defaults to process.env. */
    env?: NodeJS.ProcessEnv;
    /** Clock injection for the Rule-1 killswitch sunset check. */
    now?: Date;
    /**
     * Path to the gemini binary if detected, else null. Defaults to
     * `detectGeminiPath()` (PATH + known-location resolution).
     */
    geminiPathDetected?: string | null;
    /**
     * Path to the gemini CLI's cached OAuth credentials. Defaults to
     * `${GEMINI_HOME || ~/.gemini}/oauth_creds.json`.
     */
    geminiOauthCredsPath?: string;
}
/**
 * Detect a codex reviewer. Returns `{ available: true, framework, model }`
 * iff ALL of: codex binary detected, OAuth `access_token` present, Rule-1
 * clean (no raw API key in env or auth.json). Any miss → a specific reason.
 *
 * Pure-ish: all external inputs are injectable. With no inputs it probes the
 * real host. It NEVER throws.
 */
export declare function detectCodexReviewer(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
/**
 * Detect a gemini reviewer (Piece 3 — the second family in the registry).
 * Returns `{ available: true, framework, model }` iff BOTH of: gemini binary
 * detected, cached OAuth credentials present (`access_token` or
 * `refresh_token`). Any miss → a specific reason.
 *
 * Pure-ish: all external inputs are injectable (mirrors
 * `detectCodexReviewer`). With no inputs it probes the real host. It NEVER
 * throws.
 */
export declare function detectGeminiReviewer(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
export interface ReviewerResult {
    /** Outcome class for the cross-model pass. */
    status: 'ok' | 'degraded' | 'unavailable';
    /** The framework that ran (or would have run). */
    framework?: IntelligenceFramework;
    /** Concrete model used. */
    model?: string;
    /** Parsed verdict, when the review returned. */
    verdict?: ReviewVerdict;
    /** Structured findings (one record), folded alongside internal reviewers. */
    findings?: ReviewFinding[];
    /** A reason string for degraded/unavailable outcomes. */
    reason?: string;
    /** The flag string that gets written to frontmatter + the report banner. */
    flag: string;
}
export type ReviewVerdict = 'CLEAN' | 'MINOR ISSUES' | 'SERIOUS ISSUES' | 'UNKNOWN';
export interface ReviewFinding {
    /** Reviewer tag, e.g. 'cross-model:codex-cli:gpt-5.5'. */
    reviewer: string;
    verdict: ReviewVerdict;
    /** The findings body (verbatim text the reviewer produced). */
    body: string;
    /** True when the reply could not be parsed into a verdict (captured raw). */
    unstructured?: boolean;
}
interface SupportedReviewerFramework {
    /** Extend this union (in the type below) to add a framework. */
    id: IntelligenceFramework;
    /** Detection — does this framework's reviewer have what it needs to run? */
    detect(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
    /**
     * Run the review: assemble already done by the caller; this builds the
     * provider, evaluates the prompt, and parses the result. Returns a
     * ReviewerResult (never throws — failures map to a degraded result).
     */
    review(args: ReviewerInvokeArgs): Promise<ReviewerResult>;
}
export interface ReviewerInvokeArgs {
    /** The fully-assembled cross-model reviewer prompt (prompt + spec + context). */
    promptText: string;
    /** Per-call timeout. */
    timeoutMs: number;
    /**
     * Optional provider override — tests inject a stub so no real codex spawn
     * happens. Production passes nothing and the factory builds the real one.
     */
    providerOverride?: {
        evaluate(prompt: string, options?: {
            model?: 'fast' | 'balanced' | 'capable';
            timeoutMs?: number;
        }): Promise<string>;
    };
    /**
     * Optional detection override — `runCrossModelReview` passes the detection
     * it already computed (so review never re-probes the host), and tests inject
     * synthetic detections (e.g. a tier-word model to exercise the canary).
     * Absent → the entry runs its own real-host detect, as before (back-compat).
     */
    detectionOverride?: CrossModelDetectionResult;
}
/**
 * Fail-loud model canary (Piece 3). A cross-model review must run on a
 * CONCRETE model id — never a bare tier word that fell through a
 * tier→model resolution map (the `resolveModelForFramework` fall-through
 * failure class: the literal string 'capable' is not a model, and silently
 * passing it selects a dead reviewer). Returns false for undefined/empty
 * strings and for bare tier words (case-insensitive). Both registry entries
 * check this BEFORE invoking the provider and degrade LOUDLY
 * (`model-resolution-canary`) on a failure.
 */
export declare function isConcreteReviewerModel(model: string | undefined): boolean;
/**
 * The supported-reviewer registry. codex first — the order IS the preference
 * order. gemini second (Piece 3). Further frameworks land here as later
 * registry entries. NOTE: the registry only ever carries first-party OAuth
 * CLI adapters — see `TRUSTED_REVIEWER_FRAMEWORKS` below.
 */
export declare const SUPPORTED_REVIEWER_FRAMEWORKS: SupportedReviewerFramework[];
/**
 * Trusted-provider allowlist (Piece 3 — no spec egress to untrusted
 * endpoints). The registry only ever carries FIRST-PARTY OAuth CLI adapters:
 * the full spec text is handed to the reviewer model, so it must NEVER be
 * sent to a custom/base-URL endpoint an operator (or attacker) pointed a
 * framework at. The pi-cli multi-provider case is deliberately EXCLUDED from
 * cross-model review for exactly this reason — its provider may be a custom
 * endpoint. A framework id outside this list is refused by the script
 * wrapper (`--family`) with reason `untrusted-framework`.
 */
export declare const TRUSTED_REVIEWER_FRAMEWORKS: readonly string[];
/** Is `id` on the trusted first-party reviewer allowlist? */
export declare function isTrustedReviewerFramework(id: string): boolean;
/**
 * Walk the registry in preference order and return the FIRST available
 * framework's detection result (back-compat single-reviewer entry point —
 * the multi-family collection is `detectAllCrossModelReviewers`). If none is
 * available, returns the preference-leader's specific reason (codex today) so
 * the report can render a concrete remediation, rather than the generic
 * `no-supported-framework`.
 *
 * SIGNAL-ONLY: never throws, never blocks. A `false` simply routes the skill
 * to the internal-only fallback (spec §4).
 */
export declare function detectCrossModelReviewer(inputs?: CrossModelDetectInputs): CrossModelDetectionResult;
/**
 * Collect EVERY available reviewer framework, in registry preference order
 * (Piece 3 — family diversity: GPT and Gemini catch different failure
 * classes, so the externals pass runs one review PER available family, not
 * first-match-only). Returns an empty array when none is available.
 *
 * SIGNAL-ONLY: never throws, never blocks.
 */
export declare function detectAllCrossModelReviewers(inputs?: CrossModelDetectInputs): CrossModelDetectionResult[];
/**
 * Map a provider rejection into a coarse `degraded` reason. The provider
 * surfaces timeouts, non-zero exits, and (via the circuit breaker) rate
 * limits as thrown Errors; we classify on the message text the same way the
 * rate-limit classifier does.
 */
export declare function classifyReviewFailure(err: unknown): string;
/**
 * Parse a reviewer reply into a structured finding. The prompt mandates a
 * `Verdict: CLEAN | MINOR ISSUES | SERIOUS ISSUES` line + a findings list. If
 * the verdict line is unparseable (or the reply is blank), the whole reply is
 * captured as one raw "unstructured external review — read manually" finding
 * (never dropped, never thrown, never zero).
 */
export declare function parseReviewerReply(raw: string, reviewerTag: string): ReviewFinding;
export interface ReferencedContextDoc {
    /** Repo-relative path used as the `--- CONTEXT: <path> ---` header. */
    path: string;
    /** The doc's contents (already read by the caller). */
    content: string;
}
export interface AssemblePromptInputs {
    /** Contents of skills/spec-converge/templates/reviewer-cross-model.md. */
    reviewerTemplate: string;
    /** The full spec markdown. */
    specMarkdown: string;
    /** Repo-relative spec path, substituted for {SPEC_PATH} in the template. */
    specPath: string;
    /** Referenced architectural context docs (same set internal reviewers see). */
    context?: ReferencedContextDoc[];
    /** Total budget in bytes. Defaults to CONTEXT_BUDGET_BYTES. */
    budgetBytes?: number;
}
export interface AssembledPrompt {
    /** The final prompt string fed to the provider. */
    promptText: string;
    /** True when referenced context had to be truncated to fit the budget. */
    truncated: boolean;
    /** Byte size of the assembled prompt. */
    bytes: number;
}
/**
 * Assemble the cross-model reviewer prompt: the reviewer template (with
 * `{SPEC_PATH}` substituted) + the full spec + as much referenced context as
 * fits the budget. codex runs with NO repo access, so context is inlined under
 * `--- CONTEXT: <path> ---` headers. The spec is ALWAYS included in full; if
 * referenced docs overflow the budget, they are truncated and a loud
 * truncation note is added so the reviewer knows its view was partial (a
 * silently-truncated review is a trap; a disclosed-partial one is still signal).
 *
 * Truncation is DETERMINISTIC (spec §2, F4): referenced docs are ordered by
 * `orderContextDeterministically` (constitutional/lessons docs first, then the
 * spec-declared link order) BEFORE the budget walk, so the same spec + docs
 * always drop the same docs. When a doc is fully or partially dropped, the
 * truncation note NAMES the affected docs (which were partial, which were fully
 * omitted) — a reviewer must know exactly which context it could not see, not
 * just that "something" was cut.
 */
export declare function assembleReviewerPrompt(inputs: AssemblePromptInputs): AssembledPrompt;
/**
 * The discrete cross-model review outcome states the report + frontmatter
 * record. Distinct so "you have no cross-model reviewer" reads differently
 * from "your reviewer was rate-limited this round" from "you chose the fast
 * path" from "the framework was present but NOT ONE round ever succeeded".
 *
 * `degraded-all-rounds` (spec §2/§4, F2) is the SPEC-LEVEL aggregate: a single
 * round's `degraded` lives on a ReviewerResult, but convergence runs many
 * rounds and the spec gets ONE final `cross-model-review:` value. When a
 * framework was present every round but ZERO rounds produced a successful
 * external pass (all degraded), the final flag is `degraded-all-rounds` —
 * treated as loud as `unavailable`, because the spec converged having never
 * once received a real external opinion. This must surface at SPEC level, not
 * hide in per-round notes.
 */
export type CrossModelFlagStatus = 'available' | 'unavailable' | 'degraded' | 'degraded-all-rounds' | 'skipped-abbreviated';
export interface CrossModelFlag {
    status: CrossModelFlagStatus;
    /** The `cross-model-review:` frontmatter value. */
    flag: string;
    /** Optional `cross-model-review-reason:` value. */
    reason?: string;
}
/**
 * Build the fallback flag for the unavailable / skipped / degraded-all-rounds
 * states. (The available and per-round degraded flags come back on the
 * ReviewerResult.) Centralizes the exact strings the frontmatter writer +
 * report banner consume.
 *
 * `degraded-all-rounds` is the spec-level aggregate the skill writes when a
 * framework was present but no round ever succeeded (see
 * `aggregateRoundOutcomes`).
 */
export declare function buildCrossModelFlag(status: 'unavailable' | 'skipped-abbreviated' | 'degraded-all-rounds', reason?: string): CrossModelFlag;
/**
 * Aggregate per-round cross-model outcomes into the ONE final spec-level flag
 * (spec §2/§4, F2). Convergence runs multiple rounds; each round yields a
 * `ReviewerResult` (`ok` / `degraded` / `unavailable`). The skill collects the
 * per-round statuses and calls this to decide what `write-convergence-tag.mjs`
 * stamps:
 *
 *   - `skipped-abbreviated` if the author opted out (passed explicitly) — wins
 *     over everything, since no external pass was attempted by choice.
 *   - `codex-cli:<model>` (the LAST successful round's flag) if ANY round got a
 *     real external pass — one genuine outside opinion is enough to say the spec
 *     received cross-model review.
 *   - `degraded-all-rounds` if a framework was present every round but ZERO
 *     rounds succeeded (all degraded) — as loud as `unavailable`.
 *   - `unavailable` if no framework was ever available (all rounds unavailable).
 *
 * Returns the `{ flag, reason }` the tag writer + report banner consume.
 */
export declare function aggregateRoundOutcomes(rounds: ReviewerResult[], opts?: {
    skippedAbbreviated?: boolean;
}): CrossModelFlag;
/**
 * The high-level entry the skill driver calls: detect, and if available run
 * the first available framework's reviewer with the assembled prompt;
 * otherwise return the `unavailable` flag. NEVER throws, NEVER blocks.
 *
 * `assembled` is produced by `assembleReviewerPrompt`. `detectInputs` and
 * `providerOverride` exist for tests; production omits them.
 */
export declare function runCrossModelReview(args: {
    assembled: AssembledPrompt;
    timeoutMs?: number;
    detectInputs?: CrossModelDetectInputs;
    providerOverride?: ReviewerInvokeArgs['providerOverride'];
}): Promise<ReviewerResult>;
/**
 * Hash the spec's REVIEWABLE body (Piece 3 delta-gating): sha256 hex of the
 * spec text with the leading YAML frontmatter block stripped and line endings
 * normalized (\r\n → \n). Frontmatter is excluded so tag-writes
 * (`review-convergence`, `approved: true`, cross-model flags) and other
 * metadata edits do NOT change the hash — externals re-run only when the
 * content a reviewer would actually read changed. The skill runs externals on
 * round 1 and on any round where this hash differs from the last external
 * pass's hash; an unchanged round records a skip-with-logged-note.
 */
export declare function hashSpecReviewableBody(specText: string): string;
/**
 * One recorded observation of which reviewer frameworks were available at a
 * moment in time. Appended (JSONL) to
 * `<stateDir>/state/framework-activation-history.jsonl` by the script
 * wrapper's `--detect-only --state-dir` path on every detection.
 */
export interface FrameworkActivationObservation {
    /** ISO timestamp; defaults to now. */
    ts?: string;
    /** framework id → was it available/active at observation time. */
    frameworks: Record<string, boolean>;
}
/**
 * Append ONE observation line to the durable framework-activation history
 * (Piece 3 — the standing-framework baseline). The externals-mandatory check
 * is judged against this recorded history over a lookback window, NOT a
 * just-in-time reading — so deactivating a framework right before converging
 * cannot present the agent as "genuinely single-framework."
 *
 * mkdir -p's the state dir; caps the file at the most recent
 * `ACTIVATION_HISTORY_MAX_LINES` lines on every write. Filesystem errors
 * propagate — a silently-unrecorded baseline would quietly weaken the
 * mandatory check (fail-loud).
 */
export declare function recordFrameworkActivationObservation(stateDir: string, observation: FrameworkActivationObservation): void;
/**
 * Was ANY non-Claude reviewer framework active at ANY point within the
 * lookback window, per the durable activation history? This is the
 * externals-mandatory check (Piece 3): `true` means the cross-model pass is
 * NON-SKIPPABLE for the spec — including when a framework was deactivated
 * inside the window (a just-before-converge deactivation does not exempt the
 * spec). The advisory "externals unavailable" floor is legitimate only when
 * this returns `false` across the whole lookback.
 *
 * NEVER throws: a missing file → false; corrupt lines are skipped.
 */
export declare function wasNonClaudeFrameworkActiveWithin(stateDir: string, lookbackDays: number, now?: Date): boolean;
export {};
//# sourceMappingURL=crossModelReviewer.d.ts.map