/** * Test-fixture sweep for the real skills root — finds and quarantines skill * directories that caamp unit tests wrote into `/skills` before the * test sandbox covered the skills root (T12645). * * Measured 2026-09-28 on macOS: 53 of the 70 entries were fixtures — * `-` dirs from `skills-installer.test.ts` / * `skills-installer-recordrow.test.ts`, plus `real-skill`, `skill-alpha` and * `skill-beta`. Every one surfaced in skill listings and harness installs. * * Matching is deliberately narrow. An entry is a fixture only when BOTH hold: * - its name is `-` or one of {@link SKILL_FIXTURE_EXACT_NAMES}; * - its content is exactly what those tests write: a `SKILL.md` whose * description is `Test skill ` or `Deep nested skill`, plus only * small regular files and directories (no links). * A name match whose content does not fit is reported `unclassified` and left * in place. Hidden/underscore entries (`.audit-log`, `_archive`, …), manifest * skills and bundled skill names are never candidates. * * Repair MOVES each fixture into * `/audit/skill-fixture-quarantine//` — nothing * is deleted — and appends intent/completed receipts to * `/audit/skill-fixtures.jsonl`, each naming both paths and the * sha256 of every file moved. Across filesystems (EXDEV) the move is a copy, * re-hashed against that inventory before the source is removed. * {@link restoreSkillFixtures} moves a run's entries back. * * @task T12645 */ /** Repair command printed in remedies. */ export declare const SKILL_FIXTURES_REPAIR_COMMAND = "cleo doctor skill-fixtures --repair"; /** `-` — every randomized caamp installer fixture name. */ export declare const SKILL_FIXTURE_UUID_NAME: RegExp; /** Fixed fixture names written by `skills-installer.test.ts`. */ export declare const SKILL_FIXTURE_EXACT_NAMES: readonly string[]; /** One file inside a fixture, recorded in the receipt. */ export interface SkillFixtureFile { /** Path relative to the fixture dir. */ path: string; /** Size in bytes. */ bytes: number; /** sha256 of the content. */ sha256: string; } /** One candidate entry in the skills root. */ export interface SkillFixtureEntry { /** Entry name. */ name: string; /** Absolute path. */ path: string; /** `fixture` is safe to quarantine; `unclassified` is reported only. */ state: 'fixture' | 'unclassified'; /** Why an entry is unclassified, else null. */ reason: string | null; /** Files found (empty when unclassified before the walk finished). */ files: SkillFixtureFile[]; } /** Audit of one skills root. */ export interface SkillFixtureAudit { /** Skills root inspected. */ skillsRoot: string; /** Every fixture-named entry, classified. */ entries: SkillFixtureEntry[]; /** Per-state counts. */ counts: { fixture: number; unclassified: number; }; /** True when no fixture remains. Unclassified entries need a human. */ healthy: boolean; /** Exact repair command, or null when healthy. */ remedy: string | null; } /** Receipt for one {@link repairSkillFixtures} run. */ export interface SkillFixtureReceipt { /** Unique receipt id; also the quarantine subdirectory name. */ receiptId: string; /** ISO timestamp. */ at: string; /** Dry run: nothing moved, nothing written. */ dryRun: boolean; /** `planned` (dry run, returned only) · `intent` · `completed` · `failed`. */ phase: 'planned' | 'intent' | 'completed' | 'failed'; /** Quarantine directory for this run. */ quarantineDir: string; /** Every fixture the run moves, with its destination and file hashes. */ planned: Array<{ from: string; to: string; files: SkillFixtureFile[]; }>; /** Entries actually moved: `rename`, or `copy` (verified, then source removed) across devices. */ moved: SkillFixtureMove[]; /** Entries left in place, with the reason. */ skipped: Array<{ path: string; reason: string; }>; /** Error that stopped the run, when `phase === 'failed'`. */ error: string | null; /** JSONL receipt log, or null on a dry run. */ receiptLog: string | null; } /** One entry moved by a repair or a restore. */ export interface SkillFixtureMove { /** Path before the move. */ from: string; /** Path after the move. */ to: string; /** `rename`, or `copy` when rename failed with EXDEV (hashes verified before the source is removed). */ method: 'rename' | 'copy'; /** * False when a verified copy landed but removing the source failed: both * paths then hold the entry, and `sourceError` says why. */ sourceRemoved: boolean; /** Why the source could not be removed, else null. */ sourceError: string | null; } /** Receipt for one {@link restoreSkillFixtures} run. */ export interface SkillFixtureRestoreReceipt { /** The repair receipt being undone. */ receiptId: string; /** ISO timestamp. */ at: string; /** Dry run: nothing moved, nothing written. */ dryRun: boolean; /** `restore-planned` (dry run, returned only) · `restored`. */ phase: 'restore-planned' | 'restored'; /** Entries moved back to their original path. */ restored: SkillFixtureMove[]; /** Entries left in quarantine, with the reason. */ skipped: Array<{ path: string; reason: string; }>; /** JSONL receipt log, or null on a dry run. */ receiptLog: string | null; } /** Move primitive; injectable so tests can force the cross-device path. */ export type RenameFn = (from: string, to: string) => void; /** Injection points (tests). */ export interface SkillFixtureOptions { /** Skills root. Defaults to {@link resolveSkillsRoot}. */ skillsRoot?: string; /** Receipt + quarantine dir. Defaults to `/audit`. */ auditDir?: string; /** Names that are never candidates. Defaults to manifest + bundled names. */ protectedNames?: readonly string[]; /** Rename primitive. Defaults to `renameSync`. */ rename?: RenameFn; } /** * Classify one fixture-named entry by content. * * @param name - Entry name. * @param entryPath - Absolute entry path. * @returns The classified entry. */ export declare function classifySkillFixture(name: string, entryPath: string): SkillFixtureEntry; /** * Audit a skills root for caamp test fixtures. * * @param opts - Injection points (tests). * @returns Every fixture-named entry, classified, with the remedy. * @task T12645 */ export declare function auditSkillFixtures(opts?: SkillFixtureOptions): SkillFixtureAudit; /** * Move every fixture found by {@link auditSkillFixtures} into a per-run * quarantine directory, with intent and outcome receipts. Each entry is * re-classified immediately before its move; one that changed is skipped. * * @param opts - `dryRun` plans without writing; plus injection points. * @returns The post-repair audit and the receipt. * @task T12645 */ export declare function repairSkillFixtures(opts?: SkillFixtureOptions & { dryRun?: boolean; }): { audit: SkillFixtureAudit; receipt: SkillFixtureReceipt; }; /** * Undo one {@link repairSkillFixtures} run: move every entry its receipt * records as moved back from quarantine to its original path. An entry whose * original path is occupied, whose quarantine copy is gone, or whose files no * longer match the recorded sha256 inventory is skipped and reported. Appends * a `restored` line to the same JSONL log. * * @param receiptId - The repair receipt id (also the quarantine dir name). * @param opts - `dryRun` plans without moving; `auditDir` / `rename` injection. * @returns The restore receipt. * @throws When the log has no finished run with that id, or it was already restored. * @task T12645 */ export declare function restoreSkillFixtures(receiptId: string, opts?: Pick & { dryRun?: boolean; }): SkillFixtureRestoreReceipt; //# sourceMappingURL=skill-fixtures.d.ts.map