/**
 * skillGraph check-up — build-time validation of a declared graph.
 *
 * Pure + side-effect-free. Catches wiring mistakes at authoring time instead of
 * mid-run: a skill nobody can reach, an edge to a skill that isn't in the graph,
 * two un-prioritized edges leaving one skill, a graph with no start, a self-loop,
 * an entry menu with no way to choose from it, a transition the cursor can never take.
 *
 * Surfaced two ways:
 *   • `graph.checkup()` → `{ ok, problems }` — always available, call it whenever.
 *   • `.build({ check: 'throw' | 'warn' | 'off' })` — run it at build time.
 *
 * **Only `unknown-skill` and `no-entry` are ERRORS.** Everything else is a WARNING,
 * and deliberately so: this file reports what it can PROVE from the declaration, and
 * a graph the model can still navigate is not a broken graph. `unreachable-skill`,
 * `model-edge-only` and `dead-entry-step` all describe skills or transitions that
 * deterministic routing cannot reach — but `read_skill` can, so calling them errors
 * would claim more than the declaration supports.
 *
 * ## What "reachable" means here (8.7.0)
 *
 * The BFS walks **deterministic** edges only — an edge with a `when` or an
 * `onToolReturn`. A bare `.route(a, b)` compiles to no trigger at all (`b` keeps its
 * `llm-activated` default), so counting it as reachability answered a question nobody
 * asked: the graph does not route there, the model does. Bare edges get their own
 * code, `model-edge-only`, which says exactly that and names the one cursor position
 * the gate will grant the jump from.
 */
import { type SkillMatchData } from './skillMatch.js';
import { type SkillGuardData } from './skillGuard.js';
/**
 * The compiled trigger kinds this file needs to tell apart. Mirrors
 * `InjectionTrigger['kind']` without importing it — the check-up is pure over
 * strings and must not depend on the engine's types (`skillMatch.ts` is itself
 * engine-type-free, which is what keeps that law intact here).
 */
export type CheckupTriggerKind = 'always' | 'rule' | 'on-tool-return' | 'llm-activated';
export type GraphProblemCode = 'unusable-tool-name' | 'unknown-skill' | 'no-entry' | 'unreachable-skill' | 'model-edge-only' | 'multi-entry-fanout' | 'dead-entry-step' | 'ambiguous-routes' | 'self-loop' | 'guard-unsatisfiable' | 'rule-id-exists' | 'overlapping-rules' | 'rules-shadowed-by-order' | 'intent-without-classify' | 'duplicate-intent-example' | 'overlapping-intents' | 'body-foreign-tool' | 'body-unknown-tool' | 'example-misses-own-rule' | 'example-shadowed-by-earlier' | 'example-shadowed-by-default' | 'example-unclaimed' | 'never-routes-claimed' | 'never-routes-by-default' | 'never-routes-contradicts-example' | 'tools-share-prefix' | 'few-declared-edges' | 'one-way-entries' | 'no-negative-evidence' | 'skill-wraps-one-tool' | 'artifact-kind-unsatisfied';
/** One issue found by the check-up. `kind: 'error'` fails `ok` (and `'throw'`). */
export interface GraphProblem {
    readonly kind: 'error' | 'warning';
    readonly code: GraphProblemCode;
    readonly message: string;
    /** The skill the problem is about (unreachable/ambiguous source). */
    readonly skill?: string;
    readonly from?: string;
    readonly to?: string;
    /**
     * The declared phrase this problem is about — present on the `example-*`
     * codes and on the `never-routes-*` ones, the checks that reason about a
     * concrete phrase (in the positive and the negative direction respectively).
     * The message always quotes it too; this is the same string as data, so a
     * tool can group by phrase without parsing prose.
     */
    readonly example?: string;
}
/** Result of `graph.checkup()`. `ok` is false iff there is ≥1 `error`. */
export interface GraphCheckup {
    readonly ok: boolean;
    readonly problems: readonly GraphProblem[];
    /**
     * What this report does NOT cover — present only when a check ran whose
     * SILENCE could be misread as proof. Today that is the start-rule examples
     * (skillExamples.ts): they prove things about the phrases the author
     * declared and nothing about phrases nobody wrote, so a clean report is not
     * proof of coverage — and the report says so itself rather than leaving it
     * to prose docs. Absent (not empty) when nothing needed saying, so a graph
     * that never heard of examples reports the exact same object as always.
     */
    readonly notes?: readonly string[];
}
/** One declared entry, in declaration order. */
export interface CheckupEntry {
    readonly id: string;
    /** Has a `when` predicate or a `match` data matcher — i.e. it does NOT
     *  unconditionally win the cold-start cursor. */
    readonly conditional: boolean;
    /** The DATA matcher behind the condition, when the rule was declared as data
     *  (`match:`) rather than code (`when:`). Only rules that carry it can be
     *  compared (`overlapping-rules` / `rules-shadowed-by-order`) — a `when`
     *  predicate is opaque, and this file never claims to have checked one. */
    readonly match?: SkillMatchData;
}
export interface CheckupInput {
    /** Every skill id IN the graph. */
    readonly skillIds: ReadonlySet<string>;
    /** Declared entries, in declaration order (the order the cursor resolver reads). */
    readonly entries: readonly CheckupEntry[];
    /** Declared edges; `deterministic` = has a `when`/`onToolReturn`/
     *  `onToolStatus`/`guard` condition. A `guard` rides as DATA so this file
     *  can prove contradictions; `onToolReturnExact`/`onToolStatuses` are the
     *  only preconditions that are provably comparable with it (a RegExp
     *  `onToolReturn` is not decided, so it never arrives here). */
    readonly routes: ReadonlyArray<{
        fromId: string;
        toId: string;
        deterministic: boolean;
        guard?: SkillGuardData;
        onToolReturnExact?: string;
        onToolStatuses?: readonly string[];
    }>;
    /** Decision-`tree()` graphs are exhaustive by construction — only id checks apply. */
    readonly isTree: boolean;
    /**
     * The entries are EXCLUSIVE — a scorer (`.entryBy()`/`.entryByRelevance()`) or
     * `.entryByRead()` picks exactly one. When true, the fan-out checks do not apply:
     * choosing among the entries is precisely what those strategies do.
     */
    readonly exclusiveEntries: boolean;
    /**
     * The COMPILED trigger kind per skill id. Read only to keep `unreachable-skill`'s
     * sentence true: "the model can still open it with read_skill" holds for an
     * `llm-activated` trigger and for no other kind — the agent's gate admits an open
     * pick only for that one (`Agent.openSkillIds`). A skill that arrived carrying a
     * hand-authored `rule` trigger keeps it (`deriveTrigger` returns null for an
     * unwired skill), and for that skill the old sentence was false.
     */
    readonly triggerKinds: ReadonlyMap<string, CheckupTriggerKind>;
    /**
     * A classifier is configured (`.classify()` / `start.classify`, SG-C). Two
     * consequences here: an intent entry is judgeable (no
     * `intent-without-classify`), and the tier-1 pairwise rule checks RUN even
     * though the entries are exclusive — the cold cascade reads rule entries in
     * declaration order again (first match wins), so the checks' premise holds,
     * unlike under a pure scorer (which ranks ALL matching candidates) or
     * `.entryByRead()` (the model picks).
     */
    readonly hasClassifier?: boolean;
}
/** Run the check-up. Pure. */
export declare function checkupGraph(input: CheckupInput): GraphCheckup;
/**
 * Format a check-up for a thrown error / console warning.
 *
 * Notes render AFTER the problems, tagged `[note]`: they are statements about
 * the report's own reach, not findings. A check-up with no notes formats
 * byte-identically to every version before them.
 */
export declare function formatCheckup(checkup: GraphCheckup): string;
//# sourceMappingURL=skillGraphCheckup.d.ts.map