import { type DuplicatesGroup } from "../audit/duplicates.js"; import { type ReferenceReport } from "../audit/references.js"; import { type HygieneCommonOptions } from "../shared.js"; export type DuplicatesKeepRule = "oldest" | "newest" | "shortest-path" | "interactive"; export interface CleanupDuplicatesOptions extends HygieneCommonOptions { root?: string; index?: string; limit?: number; includeSystem?: boolean; includeSystemFields?: boolean; language?: string; batchSize?: number; minGroupSize?: number; /** * Which member of each duplicate group survives. Default `oldest` * (created-date) — the original wins, most likely the one with * inbound refs. */ keepRule?: DuplicatesKeepRule; /** Concurrency for delete calls. Default 4. */ concurrency?: number; whatIf?: boolean; allowWrite?: boolean; /** Required when `keepRule === "interactive"` and stdin isn't a TTY. */ nonInteractive?: boolean; /** * Skip the inbound-reference pre-flight. Use when the operator * knows refs to dupes are acceptable (e.g. a content migration that * will rebuild refs separately). The pre-flight is enabled by * default so a dupe delete doesn't silently break content links; * `audit broken-links list` was the previous post-cleanup mitigation * but the agent never knew to run it. Inverse: `--force` also * bypasses (existing escape hatch). */ skipRefCheck?: boolean; /** Force-delete dupes even when blockers are found. */ force?: boolean; /** * Skip the internal `runAuditDuplicates` call and use this pre- * computed group list as the cleanup input. Set by the CLI's * `--from-stdin` flag when the operator pipes `scai hygiene audit * duplicates list --json` directly into the cleanup. Eliminates the * dual-discovery drift where audit and cleanup run the same hash * pass twice (and could disagree on the group set when the tenant * changes between calls). */ preComputedGroups?: DuplicatesGroup[]; } export interface DuplicatePurgeAction { contentHash: string; kept: { itemId: string; path: string; }; deleted: Array<{ itemId: string; path: string; status: "deleted" | "what-if" | "failed" | "blocked"; error?: string; /** * Inbound-reference reports that blocked the delete. Populated * when status is "blocked". The cleanup ran `audit references` * for the dupe's itemId; the operator can resolve each pointer * before re-running, or pass `--skip-ref-check` / `--force` to * delete anyway and clean up dangling refs after. */ blockers?: ReferenceReport[]; }>; } /** * Purge duplicate items, keeping one per group according to `--keep-rule`. * * Strategy: * 1. Run `audit duplicates` to find groups. * 2. For each group, apply the keep-rule to pick the survivor. * 3. Delete the rest via `deleteItem(permanently: true)`. * * Notes: * - **Inbound refs to deleted dupes become broken.** Run * `audit broken-links list` after a purge — the duplicates that * other content pointed to leave dangling references behind. * - With `--keep-rule interactive`, the command prompts per group. * Honors `s`/`skip` per-group to leave a group untouched. * Requires a TTY; pass any other keep-rule when scripting. */ export declare const runCleanupDuplicates: (options: CleanupDuplicatesOptions) => Promise;