/** * `cleo docs doctor` — store health diagnostics + safe repairs — T13447. * * Audits the CleoDocs store (`cleo.db` `attachments`/`docs_wikilinks` tables * plus the on-disk legacy surfaces) for eight known drift classes and, when * invoked with `apply: true`, performs the repairs that are safe to run * in-place: * * - `dangling-pointers` — rows whose recorded local `path` is gone from * disk or lives inside a worktree/tmp directory. * Rows already `lifecycle_status = 'archived'` * are excluded (T13562). Repair for a missing * `local-file` target archives the row; a * `blob`-kind payload carrying a stale legacy * `path` whose content still resolves in the * content-addressed blob store is re-pointed * instead — the stale `path` field is cleared * (`clear-stale-path`, T13562) since the bytes * are alive. Existing files are left alone * (content capture is T13360's scope). * - `doc-version-skew` — slugged rows whose `docVersion` trails the * revision count in `.cleo/audit/docs-versioning.jsonl` * (the T13351 bug left historical rows at 1). Repair * sets `docVersion = revisions + 1`. * - `empty-provenance` — slugged text docs with NULL/empty `topics` AND * `related_tasks` whose content is retrievable. * Repair re-derives both columns via * {@link deriveDocLinks} (T13357 semantics). * - `wikilinks-drift` — expected edges (derived from the current * provenance columns) missing from `docs_wikilinks`. * Repair is a full {@link rebuildDocsWikilinks}. * - `legacy-store-present`— `.cleo/attachments/index.db`, * `.cleo/docs-publications.json` and/or * `.cleo/attachments/sha256/` still on disk. * - `legacy-blob-coverage`— content-bearing rows whose sha256 has NO * counterpart in the blob store * (`.cleo/blobs/blobs/`). This is the T13362 AC2 * deletion pre-check: the legacy byte tree may * only be deleted once this diagnostic is clean * (severity `error` while any are missing). * Repair (`backfill-blob-bytes`, T13653): locate * the bytes in the legacy `.cleo/attachments/` * tree or a live keep-link, SHA-256-verify them * against the row's recorded hash, and mirror * them into the blob store. Hash-verified and * idempotent — never writes unverified bytes, * skips shas already in the store. Runs under * the normal `--apply`, or focused via * `--repair` (backfill only). * - `stale-drafts` — slugged docs still `draft` after * `--older-than-days` (default 30). * - `conduit-history-gaps`— adopted docs whose `conduit_attachment_versions` * row set is incomplete: updates written before * the conduit adoption recorded no conduit rows, * so a doc at docVersion N can hold fewer rows. * Repair (`backfill-conduit-versions`, T13727) * reconstructs the missing rows from the honest * pre-adoption sources (the docs-versioning * audit log + the surviving registry/blob bytes) * and inserts them through the canonical * accessor. Runs under `--apply`; `--repair` * stays the T13653 blob-backfill-only mode. A * doc whose remaining positions are * unrecoverable stays listed at `info` level — * visibility only, no invented repair (T13843). * * SAFETY: `apply: true` (or `repair: true`) REFUSES to run without a backup receipt * ({@link DocsDoctorOptions.backupReceiptPath}) whose mtime is at most * {@link DOCS_DOCTOR_BACKUP_MAX_AGE_MS} old. The dispatch layer creates the * backup itself (same `createBackup` as `cleo backup add`) and passes the * receipt path automatically; a dry run needs no receipt. * * The module never logs to console — every outcome flows through the * returned report. * * @task T13447 (Epic T13340 / Saga T13339) * @see docs-update.ts — audit-log version semantics (version = revisions + 1) * @see derive-links.ts — topics/related_tasks derivation (T13357) * @see wikilinks.ts — derived edge table + rebuild (T11826) */ /** * Maximum age of the backup receipt accepted before an `apply` run. * * A receipt older than this is treated as stale: the operator (or the * dispatch layer) must create a fresh backup before repairs proceed. * * @task T13447 */ export declare const DOCS_DOCTOR_BACKUP_MAX_AGE_MS: number; /** * Default age threshold for the `stale-drafts` diagnostic (days). * * @task T13447 */ export declare const DOCS_DOCTOR_DEFAULT_OLDER_THAN_DAYS = 30; /** * Per-diagnostic detail cap — the report stays readable on stores with * hundreds of offenders; the full count is always reported. * * @task T13447 */ export declare const DOCS_DOCTOR_MAX_DETAIL_ITEMS = 50; /** * The eight diagnostic classes {@link runDocsDoctor} checks for. * * @task T13447 */ export type DocsDoctorDiagnosticKind = 'dangling-pointers' | 'doc-version-skew' | 'empty-provenance' | 'wikilinks-drift' | 'legacy-store-present' | 'legacy-blob-coverage' | 'stale-drafts' | 'conduit-history-gaps'; /** * One diagnostic class in the report: severity, total offender count, and a * capped detail list. * * @task T13447 */ export interface DocsDoctorDiagnostic { /** Which check produced this entry. */ readonly kind: DocsDoctorDiagnosticKind; /** `error` blocks nothing but should be fixed; `info` is observational. */ readonly severity: 'info' | 'warn' | 'error'; /** Total offenders found (may exceed `items.length`). */ readonly count: number; /** Capped per-item detail (see {@link DOCS_DOCTOR_MAX_DETAIL_ITEMS}). */ readonly items: ReadonlyArray>; /** True when `count` exceeded the detail cap. */ readonly truncated: boolean; } /** * One planned or executed repair step. * * @task T13447 */ export interface DocsDoctorRepairAction { /** Diagnostic class this repair addresses. */ readonly kind: DocsDoctorDiagnosticKind; /** Machine-readable action verb (e.g. `set-doc-version`). */ readonly action: string; /** False on a dry run (the action is a plan, not a write). */ readonly applied: boolean; /** Doc slug the action touched, when row-scoped. */ readonly slug?: string; /** Attachment id the action touched, when row-scoped. */ readonly attachmentId?: string; /** Human-readable detail (old→new values, derived counts, …). */ readonly detail: string; } /** * Structured outcome of a {@link runDocsDoctor} run. * * @task T13447 */ export interface DocsDoctorReport { /** True when repairs were written (`opts.apply === true`). */ readonly applied: boolean; /** Age threshold used for the `stale-drafts` check. */ readonly olderThanDays: number; /** Backup receipt that authorised this run (null on dry runs). */ readonly backupReceiptPath: string | null; /** All eight diagnostics, in declaration order. */ readonly diagnostics: readonly DocsDoctorDiagnostic[]; /** Repair plan (dry run) or executed repairs (apply). */ readonly repairs: readonly DocsDoctorRepairAction[]; /** One-line human summary. */ readonly summary: string; } /** * Options accepted by {@link runDocsDoctor}. * * @task T13447 */ export interface DocsDoctorOptions { /** * Execute repairs instead of producing a dry-run plan. Requires * {@link backupReceiptPath} — see the module docblock for the safety rule. */ readonly apply?: boolean; /** * Execute ONLY the legacy-blob-coverage backfill (T13653): mirror * hash-verified legacy bytes into the blob store so unmigrated docs become * readable again. Subject to the same backup-receipt gate as * {@link apply}; ignored on dry runs. */ readonly repair?: boolean; /** Age threshold (days) for the `stale-drafts` diagnostic. Default 30. */ readonly olderThanDays?: number; /** * Path to a backup artifact created within the last * {@link DOCS_DOCTOR_BACKUP_MAX_AGE_MS} milliseconds (checked via mtime). * Required when {@link apply} is true. */ readonly backupReceiptPath?: string; } /** * Discriminated outcome of {@link runDocsDoctor}. * * The failure arm covers the backup-gate refusals * (`E_DOCS_DOCTOR_BACKUP_REQUIRED` / `E_DOCS_DOCTOR_BACKUP_STALE`); the * dispatch layer maps it onto a LAFS error envelope. * * @task T13447 */ export type RunDocsDoctorResult = { readonly ok: true; readonly report: DocsDoctorReport; } | { readonly ok: false; readonly error: { readonly code: string; readonly message: string; }; }; /** * Run the docs store health check. * * Dry run (default): every diagnostic is reported and every repair is * listed as a plan item with `applied: false` — nothing is written. * Apply: the backup gate runs first, then repairs execute in dependency * order (doc-version-skew, empty-provenance, dangling-pointers, and finally * the wikilinks rebuild, which re-derives from the freshly repaired columns). * On apply the report's diagnostics are re-scanned AFTER the repairs so the * envelope reflects POST-repair state and the summary counts match it * (T13560); a dry run reports the pre-repair state unchanged. * * @param projectRoot - Absolute path to the CLEO project root. * @param opts - Apply flag, stale-draft threshold, backup receipt. * @returns The structured report, or a backup-gate refusal. * @task T13447 */ export declare function runDocsDoctor(projectRoot: string, opts?: DocsDoctorOptions): Promise; //# sourceMappingURL=doctor.d.ts.map