/** * Read-only classification of a repo's existing AI canon — the heart of * `aih adopt`. It answers ONE question without writing anything: is this repo * greenfield (use `init`), already on aih's managed model (no-op), or a * BROWNFIELD canon that predates aih and must be *adopted* rather than * overwritten? The two brownfield shapes were found in real reference repos: * * - `marker-divergent` (eicp): the bootloader already carries an * `ai-canonical:shared` block (eicp is where the marker convention comes from) * but the body differs — typically a folded-in project extension a blind * `bootstrap-ai --apply` would destroy. * - `foreign-scheme` (syntegris): equivalent canon (a RULE_ROUTER, a hand-rolled * regenerate script, a migration doc) under a DIFFERENT shape, with no aih * marker in the bootloader at all. * * Everything here uses {@link readIfExists}/{@link existsSync} — no mutation, so * it is safe to run on any path and from `doctor` as an advisory. */ /** * Root bootloaders that can carry the `ai-canonical:shared` managed block. Cursor * (`.cursor/rules/*.mdc`), Windsurf (`.windsurfrules`), and Copilot * (`.github/copilot-instructions.md`) use other formats, so the managed-block — * and therefore adopt's reconcile path — only applies to these markdown files. */ export declare const MARKER_BOOTLOADERS: readonly ["CLAUDE.md", "AGENTS.md", "GEMINI.md"]; export type CanonClass = /** No canon worth adopting — `aih init` / `bootstrap-ai` create path applies. */ "greenfield" /** aih marker present and body matches the current canonical block — re-run is a no-op. */ | "already-adopted" /** Shares `ai-canonical:shared` but the body diverges (eicp) — reconcile, don't overwrite. */ | "marker-divergent" /** Equivalent canon under a foreign shape, no aih marker (syntegris) — import + insert. */ | "foreign-scheme"; /** Per-bootloader state used to decide the class and, later, the reconcile plan. */ export interface BootloaderState { /** Repo-relative path, e.g. `CLAUDE.md`. */ path: string; /** Carries an `ai-canonical:shared` managed block. */ hasMarker: boolean; /** The managed block body equals the current canonical body (no drift). */ bodyMatches: boolean; /** * Diff-inferred count of on-disk block lines absent from the canonical body — * an estimate of the human "project extension" a reconcile must preserve. * Zero unless `hasMarker && !bodyMatches`. */ preservedLines: number; } export interface CanonClassification { kind: CanonClass; /** `/RULE_ROUTER.md` exists. */ routerPresent: boolean; /** `/adapters/_shared-canonical-block.md` exists. */ sharedBlockSourcePresent: boolean; /** A committed `.aih-config.json` marker exists (the repo was bootstrapped by aih). */ configPresent: boolean; /** State for each marker-capable bootloader present on disk. */ bootloaders: BootloaderState[]; /** Repo-relative paths of detected prior-art artifacts (under the context dir). */ legacyArtifacts: string[]; } /** * Classify the canon under `root`/`contextDir`. Pure and read-only. The context * dir should be the committed one when present (callers pass * `readAihConfig(root)?.contextDir ?? ctx.contextDir`, mirroring `doctor`). */ export declare function classifyCanon(root: string, contextDir: string): CanonClassification; /** True for the two brownfield shapes that warrant `aih adopt` (not init, not no-op). */ export declare function isAdoptable(kind: CanonClass): boolean;