import type { ClaudeSpec, SkillSpec, AgentSpec, Railway } from "./spec.js"; import type { LinterCheckResult } from "./linters.js"; import type { HarnessDialect } from "./dialect.js"; /** @internal Compute SHA-256 hash of content (excluding any existing hash line). */ export declare function computeHash(content: string): string; /** @internal Prepend a hash comment to compiled content. */ /** * A compiled body that carries a valid integrity stamp — mintable ONLY by * {@link addHash}, and by construction only handed out for a CLEAN compile. * * 🔴 WHY A BRAND AND NOT A CHECK AT THE WRITE SITE. #173 was a `CLAUDE.md` * written while its refs were known-dead: `compile` printed the errors, exited * 1, and wrote the file anyway, stamped. `lint` then verified the stamp and * exited 0 over an artifact that names files which do not exist. The fix that * shipped moved the write behind an error check in ONE compiler — and the same * three lines sat unchanged in four siblings (skill, subagent, railway, * generator), where the lint-side backstop does not even reach because * `spec-refs` only inspects `claude` specs. Reproduced end to end after that * fix: a skill spec with a stale ref still produced a stamped `SKILL.md` and a * green `lint`. * * Fixing five write sites leaves the class writable — the sixth compiler would * be written the same way. Branding the STAMP moves the guarantee to where it is * produced: `writeArtifact` accepts nothing else, and an erroring compile has no * stamp to give it. */ export type StampedMarkdown = string & { readonly __stamped: unique symbol; }; export declare function addHash(content: string, specFile: string): StampedMarkdown; /** * How every compiler finishes: the rendered body, plus a STAMPED artifact that * exists only when the compile is clean. * * `markdown` stays a plain string because callers legitimately want the draft * even when it is wrong — `adoptDiff` diffs "what the spec WOULD produce" * against the file on disk, and a stale ref must not stop that. `artifact` is * what a writer needs, and it is `null` the moment there is an error, so the * write cannot happen without narrowing. */ export declare function seal(body: string, errors: readonly CompileError[], specFile: string): { markdown: string; artifact: StampedMarkdown | null; }; /** @internal Check if a file's hash matches its content. Returns null if no hash found. */ export declare function verifyHash(content: string): { valid: boolean; specFile: string; } | null; /** * Estimate token count for a string. * * Uses the ~4 characters per token heuristic (accurate within ~10% for * English text and code). Swap in a real BPE tokenizer (tiktoken, gpt-tokenizer) * for exact counts if needed. */ /** @internal */ export declare function estimateTokens(text: string): number; export interface CompileError { type: "stale-file" | "stale-command" | "stale-ref" | "invalid-rule" | "budget-exceeded" | "section-too-long" | "section-has-header" | "reserved-section-key" | "spec-name-mismatch" | "unknown-tool" | "invalid-railway" | "purity-violation" | "output-without-fork" | "agent-without-fork" | "effect-in-skill" | "inline-code-too-long" | "entry-too-long" | "section-too-large" | "rules-too-large"; message: string; path?: string; } export declare function validateFileRef(filePath: string, basePath: string): CompileError | null; export declare function readPackageScripts(basePath: string): Record | null; export declare function validateCommandRef(command: string, basePath: string): CompileError | null; export declare function validateSymbolRef(file: string, name: string, basePath: string): Promise; export declare function validateDirRef(dirPath: string, basePath: string): CompileError | null; export declare function validateGlobRef(pattern: string, basePath: string): CompileError | null; /** * Why an `enforce()` reference was NOT verified. Separate members, not one * "unknown", because the compile output turns each into a different instruction * (make-the-distinction-a-type): a switch someone turned off is fixed by turning * it on, an unset rule by setting it, an unreadable config by fixing the probe. */ export type UnverifiedReason = /** `enforce(…, { verify: false })` on this rule. */ "verify-false" /** `verifyLinters: false` for the whole compile. */ | "verification-off" /** `linterModes[] === false`. */ | "linter-off" /** catalog-only: the rule exists, the config was not read. */ | "config-not-read" /** the whole config was read and no block sets the rule. */ | "not-configured" /** the rule exists; whether the config switches it on could not be read. */ | "enabled-unknown" /** the rule does not exist (a compile error as well). */ | "not-found" /** the config switches the rule off (a compile error as well). */ | "disabled"; /** What `compile` established about one `enforce()` reference. */ export type EnforceVerdict = { readonly kind: "verified"; } | { readonly kind: "unverified"; readonly reason: UnverifiedReason; }; /** One `enforce()` reference and its verdict, in spec order. */ export interface EnforceVerdictEntry { readonly ref: string; readonly verdict: EnforceVerdict; } /** * The sentence the compile prints for an unverified reference: what happened * and the one thing that turns it into "Enforced by". */ export declare function unverifiedAdvice(ref: string, reason: UnverifiedReason): string; export interface CompileClaudeResult { markdown: string; errors: CompileError[]; /** * Budget findings at the rule's DEFAULT severity (`compile-budget: "warn"`). * An over-long `keyFiles` description or a bloated section does not break * the harness, it makes every request more expensive, so by default it must * not stop a faithful adoption from compiling. Same channel discipline as * `checkInlineCode`. Empty when the rule is at `"error"` (the findings are in * `errors` then) or off. */ warnings: CompileError[]; /** * Every budget finding, whatever the severity — the SAME objects that were * placed in `warnings` or `errors`. `lint` re-derives a spec's references * through this compiler and must not report a size finding as a dead * reference, so it tells the two apart by identity here, never by type name. */ budgetFindings: CompileError[]; linterResults: LinterCheckResult[]; /** * One entry per `enforce()` rule, in spec order: rendered "**Enforced by:**" * exactly when `verdict.kind === "verified"`, "**Claimed (unverified):**" * otherwise. The CLI's "N linter-verified" count is the verified entries. */ enforceVerdicts: EnforceVerdictEntry[]; /** Estimated token count of compiled output (~4 chars/token). */ tokens: number; /** All targets from the spec (for multi-target compilation). */ targets: string[]; /** * Each target's file: its repo-relative path (a secondary target is named * from the primary's directory) and its markdown, rendered for THAT path so * every link resolves from where the file sits. The first is the primary, * whose markdown is `markdown`. */ outputs: CompiledOutput[]; } /** One file a multi-target compile writes. */ export interface CompiledOutput { readonly path: string; readonly markdown: string; } export interface CompileClaudeOptions { /** * The `compile-budget` rule's severity. `"warn"` (the default) reports the * per-entry and per-section budgets as warnings and evaluates the Rules total * only when a threshold for it is passed; `"error"` (what * `vigiles:recommended` sets) evaluates every budget, with defaults, as an * error; `false` reports none (an explicit `maxTokens` still fails, as it * always has — a separate check). */ budgetSeverity?: "warn" | "error" | false; /** Per-entry `commands` character budget (0 disables). */ maxEntryChars?: number; /** Per-section character budget, beside the line budget (0 disables). */ maxSectionChars?: number; /** Character budget for the whole rendered Rules section (0 disables). */ maxRulesChars?: number; /** The repo root every reference resolves against. Required: no cwd fallback. */ readonly basePath: string; specFile?: string; /** Injected harness dialect; its instructionTargets[0] is the default target. */ dialect?: HarnessDialect; /** Maximum number of rules allowed. Compilation fails if exceeded. */ maxRules?: number; /** Maximum estimated tokens for compiled output. */ maxTokens?: number; /** Maximum lines per prose section. Forces splitting into named sections. */ maxSectionLines?: number; /** Skip config-enabled checks, only verify rule exists in catalog. */ catalogOnly?: boolean; /** Custom linter configs (rulesDir). */ linters?: Record; /** Global kill switch: skip ALL linter verification. */ verifyLinters?: boolean; /** Per-linter verification mode: true (full), "catalog-only", or false (skip). */ linterModes?: Record; } /** * The generous per-section line guard. Exported because `adopt` raises it to * exactly the longest adopted section and must name the same number rather than * keep a second copy that drifts. */ export declare const DEFAULT_MAX_SECTION_LINES = 200; /** * Per-ENTRY budget for a `keyFiles` / `commands` description, in characters. * * WHY AN ENTRY AND NOT THE SECTION. The list is append-only in practice: every * session adds a row and none removes one, so the section total says "too big" * long after the point where a reader could act on it, and it names no * offender. A per-entry budget names the row. 200 is deliberately loose — an * entry is a POINTER ("what is this file for"), and anything that needs a * paragraph has a better home in that file's own header, where it is read when * someone opens the file rather than on every request. */ export declare const DEFAULT_MAX_ENTRY_CHARS = 200; /** * Per-section budget in CHARACTERS, beside the line budget. * * The line guard alone is measured to be useless on the shape that actually * bites: this repo's own `Positioning` section was 24 lines and 20 416 * characters, passing a 200-LINE gate with two orders of magnitude to spare. * Long lines are the normal shape of compiled prose, so lines do not measure * cost — characters do, because that is what the harness loads. */ export declare const DEFAULT_MAX_SECTION_CHARS = 15000; /** * The Rules section, as one budget. Under `compile-budget: "error"` only. * * WHY THE SECTION AND NOT EACH RULE. A per-rule length cap was built and * removed within an hour: it opened on 40 of 53 rules, because a rule's length * is not where the cost is — the SUM is, and it is loaded on every request. * 20,000 characters is half of Claude Code's 40,000-character instruction * budget, which leaves the other half for everything else the file carries. */ export declare const DEFAULT_MAX_RULES_CHARS = 20000; /** The compile options that decide how an `enforce()` reference is verified. */ type LinterVerifyOptions = Pick; /** * Compile a ClaudeSpec into markdown. * * Returns the compiled markdown, validation errors, and linter check results. * The markdown is generated even if there are errors (with warnings). */ export declare function compileClaude(spec: ClaudeSpec, options: Readonly): Promise; export interface CompileSkillResult { /** * The stamped artifact — present ONLY when `errors` is empty. `null` is what * makes a failed compile unwritable: `writeArtifact` accepts nothing else. */ artifact: StampedMarkdown | null; markdown: string; errors: CompileError[]; /** Non-blocking advisories (e.g. an over-long inline code block). */ warnings: CompileError[]; } /** * Compile a SkillSpec into SKILL.md markdown with YAML frontmatter. */ export declare function compileSkill(spec: SkillSpec, options: Readonly<{ /** The repo root every reference resolves against. Required: no cwd fallback. */ readonly basePath: string; specFile?: string; /** The harness dialect — supplies `skillFrontmatterKeys`. Omitting it emits * every key the compiler can render, so existing callers are unchanged. */ dialect?: HarnessDialect; }>): Promise; export interface CompileAgentResult { /** * The stamped artifact — present ONLY when `errors` is empty. `null` is what * makes a failed compile unwritable: `writeArtifact` accepts nothing else. */ artifact: StampedMarkdown | null; markdown: string; errors: CompileError[]; /** Non-blocking advisories (e.g. an over-long inline code block). */ warnings: CompileError[]; } /** * Compile an AgentSpec into a subagent markdown file with YAML frontmatter. * Verifies the tool contract and the body's references; the marks the body * carries (`vigiles:symbol`, file/cmd refs) are the same ones `lint` re-checks. */ export declare function compileAgent(spec: AgentSpec, options: Readonly<{ /** The repo root every reference resolves against. Required: no cwd fallback. */ basePath: string; specFile?: string; /** The harness dialect to verify the tool contract against (required — the * core defines no default dialect; the adapter/composition root injects it). */ dialect: HarnessDialect; } & LinterVerifyOptions>): Promise; export interface CompileRailwayOptions { /** Names of compiled agents, to resolve `delegate` targets. Skipped if omitted. */ knownAgents?: readonly string[]; specFile?: string; } export interface CompileRailwayResult { /** * The stamped artifact — present ONLY when `errors` is empty. `null` is what * makes a failed compile unwritable: `writeArtifact` accepts nothing else. */ artifact: StampedMarkdown | null; markdown: string; errors: CompileError[]; } /** Verify a railway: non-empty, bounded recovery, every delegate target real. */ export declare function validateRailway(rw: Railway, knownAgents?: readonly string[]): CompileError[]; /** * Compile a railway into an orchestrator command markdown (with integrity hash), * resolving every delegate target against `knownAgents` when provided. */ export declare function compileRailway(rw: Railway, options?: CompileRailwayOptions): CompileRailwayResult; export interface HashCheckResult { hasHash: boolean; valid: boolean; specFile: string | null; } /** Check if a generated file's hash is intact. */ export declare function checkFileHash(filePath: string): HashCheckResult; export interface AdoptResult { filePath: string; hasHash: boolean; valid: boolean; specFile: string | null; currentContent: string; compiledContent: string | null; addedLines: string[]; removedLines: string[]; changed: boolean; } /** * Compare a generated file against what the spec would produce. * Returns the diff so users can see what was manually changed. */ export declare function adoptDiff(filePath: string, spec: ClaudeSpec | SkillSpec | AgentSpec, basePath: string, dialect: HarnessDialect): Promise; export {}; //# sourceMappingURL=compile.d.ts.map