/** * vigiles generate harness — emit ONE typed registry over the whole harness. * * The third generated artifact beside `generate-types` (`.d.ts`) and * `generate-schema` (JSON Schema): a `harness.gen.ts` that imports every * `*.spec.ts` in a directory, folds the agents into a `registry`, and asserts * the cross-spec invariants at the TYPE level — so a single `tsc --noEmit` * checks the WHOLE harness as one program (think TanStack's `routeTree.gen.ts`). * See research/whole-harness-codegen.md for the design + the measured perf. * * The shipped scope (the first increment): * 1. DANGLING `delegate` → a `tsc` error. Each `railway()` delegate target is a * name the generator reads at codegen time; the gen file emits one shallow * per-edge assertion (`KnownAgentName<"target", AgentName>` — O(N), no * recursion) that the target resolves to a real agent, else a `tsc` error * naming the dangling target + its railway. * 2. DUPLICATE agent/skill NAMES → a generator error (this module returns a * `duplicate` diagnostic; the CLI exits non-zero). This is the O(N) JS check * the encoding rule mandates — a set-uniqueness MAPPED TYPE is the TS2589 * wall (measured ≈ N=1000), so duplicates are NEVER a type. * 3. The whole-harness CAPABILITY LATTICE: the UNION of every agent's * `effectSurface(tools, dialect)` — a generator-computed value + type, the * substrate the future repo-scale capability-diff reads. * 4. CROSS-FILE TYPED COMPOSITION: when a `railway()` success-track step declares * what it `needs()`, the gen file emits one shallow per-pair assertion * (`Handoff, needs>` — O(N), no recursion) * that the PRIOR step's `result().ok` SUPPLIES it, so a cross-file handoff * mismatch (a missing field / wrong type) is a `tsc` error naming the field. * The repo-scale generalization of the per-file `pipe`/`Supplies` composition. * Scoped to the linear success track; recover/onError (which consume an `err`, * not the prior `ok`) are a noted follow-up. * * Harness-agnostic: the `dialect` (for the capability lattice) is INJECTED by * the composition root (the CLI), never hard-coded — mirroring `compileAgent` / * `scanPlugin`. The core stays free of any Claude-Code literal. */ import type { HarnessDialect } from "./dialect.js"; import { type PurityLevel } from "./effects.js"; /** One agent the harness defines (its registry-relevant facts). */ export interface HarnessAgentEntry { /** The agent's dispatch name — also a registry key + the dangling-check union. */ readonly name: string; /** The agent's declared tool contract (used to compute its effect surface). */ readonly tools?: readonly string[]; /** The spec file this agent came from (for import + duplicate diagnostics). */ readonly file: string; } /** One delegate edge: a railway dispatches `target` (resolved against agents). */ export interface HarnessDelegateEdge { /** The railway / orchestrator the edge originates from (for the diagnostic). */ readonly from: string; /** The delegate target name — must resolve to a known agent, else dangling. */ readonly target: string; } /** * One CROSS-FILE handoff edge: a consecutive success-track pair where the * CONSUMER (`to`) declares the input it `needs`, asserted against the PRODUCER * (`from`, the prior step's agent) `result().ok` at the type level. The * registry already imports each agent's `TypedAgentSpec`, so the generator reads * the producer's `ok` shape off the registry (`OkOf`) and * emits one shallow `Handoff<>` assertion per such pair (O(N), no recursion). */ export interface HarnessHandoffEdge { /** The railway the edge originates from (for the diagnostic). */ readonly railway: string; /** The PRODUCER agent name (the prior success-track step) — registry key. */ readonly from: string; /** The CONSUMER agent name (this step) — names the failing edge. */ readonly to: string; /** The consumer's declared input shape (`needs(...)`) — the literal emitted. */ readonly needs: Readonly>; } /** Everything the generator needs, already loaded (the pure-core input). */ export interface HarnessModel { readonly agents: readonly HarnessAgentEntry[]; readonly edges: readonly HarnessDelegateEdge[]; /** * Cross-file handoff edges (consecutive success-track step pairs whose * consumer declares `needs`). Optional + defaults to none, so an existing * model with no handoffs generates exactly as before — backwards-compatible. */ readonly handoffs?: readonly HarnessHandoffEdge[]; } export interface GenerateHarnessOptions { /** The dialect the capability lattice is computed against (injected). */ readonly dialect: HarnessDialect; /** * The module specifier the generated file imports `KnownAgentName` from. * Defaults to the public package (`"vigiles/spec"`); the in-repo dogfood * passes a relative path so the generated file resolves without the package. */ readonly specImport?: string; /** The directory the gen file will be written to (to relativize spec imports). */ readonly outDir: string; } /** A duplicate-name collision found at codegen time (the O(N) JS check). */ export interface DuplicateNameDiagnostic { readonly name: string; readonly first: string; readonly second: string; /** A ready-to-print message. */ readonly message: string; } /** The whole-harness capability lattice — the union of every agent's surface. */ export interface HarnessCapabilities { /** Every read-only tool reachable anywhere in the harness (de-duped, sorted). */ readonly readOnly: readonly string[]; /** Every side-effecting tool reachable anywhere (de-duped, sorted). */ readonly sideEffecting: readonly string[]; /** Every unknown-effect tool (MCP / unrecognized) reachable anywhere. */ readonly unknown: readonly string[]; /** The harness-wide purity: the LOOSEST purity of any single agent. */ readonly purity: PurityLevel; } export interface GenerateHarnessResult { /** The generated `harness.gen.ts` source (always produced — even on a dup, so * the caller can decide; the CLI gates the WRITE on `duplicate` being absent). */ readonly gen: string; /** The computed capability lattice (also embedded in `gen`). */ readonly capabilities: HarnessCapabilities; /** Set iff two agents declare the same name — the caller exits non-zero. */ readonly duplicate?: DuplicateNameDiagnostic; /** The number of agents + edges folded in (for the CLI summary). */ readonly agentCount: number; readonly edgeCount: number; /** The number of cross-file handoff assertions emitted (for the CLI summary). */ readonly handoffCount: number; } /** * Fold every agent's `effectSurface` into one harness-wide lattice: the union of * each bucket and the loosest purity. An agent with no `tools` inherits all (a * wildcard), so its surface is `unrestricted` — handled by `effectSurface` when * we pass `["*"]`. O(N) over the agents; the per-agent legs are fixed-arity. */ export declare function computeHarnessCapabilities(agents: readonly HarnessAgentEntry[], dialect: HarnessDialect): HarnessCapabilities; /** * Find the FIRST pair of agents that declare the same `name`. O(N) over the * agents — the set-cardinality check the encoding rule says must live in the JS * generator, never as an N×N mapped type (the measured TS2589 wall). Returns * `undefined` when names are unique. */ export declare function findDuplicateName(agents: readonly HarnessAgentEntry[]): DuplicateNameDiagnostic | undefined; /** * Generate the `harness.gen.ts` source over an already-loaded `HarnessModel`. * * Pure: no filesystem read, no spec loading — just string emission + the two * O(N) computations (capability lattice + duplicate check). The fs/scan wrapper * (`loadHarnessModel`) feeds this. */ export declare function generateHarness(model: HarnessModel, options: GenerateHarnessOptions): GenerateHarnessResult; /** A minimal shape of a loaded spec value (the fields the model reads). */ interface LoadedSpecLike { readonly _specType?: string; readonly name?: string; readonly tools?: readonly string[]; readonly steps?: readonly { readonly agent?: string; readonly needs?: Readonly>; }[]; readonly onError?: { readonly agent?: string; }; readonly recover?: { readonly step?: { readonly agent?: string; }; }; } /** Discover every `*.spec.ts` directly under `dir` (non-recursive, sorted). */ export declare function findHarnessSpecFiles(dir: string): string[]; /** * Build a `HarnessModel` from `dir`'s spec files using a caller-supplied * `load(file) → value` (the CLI injects its `loadSpec`, so this stays * fs/runtime-agnostic and unit-testable with fakes). Agents become registry * entries; railways contribute delegate edges (steps + recover + onError). */ export declare function loadHarnessModel(dir: string, load: (absFile: string) => Promise): Promise; /** Convenience: the gen file's basename, used by the CLI default out path. */ export declare const HARNESS_GEN_FILENAME = "harness.gen.ts"; /** Relative label for a path under cwd (CLI-only nicety; pure). */ export declare function labelFor(cwd: string, abs: string): string; /** Read a spec file's raw text (helper exposed for callers that need the source). */ export declare function readSpecSource(absFile: string): string; /** The directory a gen file at `outFile` lives in (helper for the CLI). */ export declare function genOutDir(outFile: string): string; export {}; //# sourceMappingURL=generate-harness.d.ts.map