/** Suffix of the byte-exact pre-repair copy kept next to the config. */ export declare const CODEGRAPH_CONFIG_BACKUP_SUFFIX = ".bak"; export type CodegraphExcludeRepairPlan = { /** True when at least one rule would actually be dropped. */ readonly changed: boolean; /** The `exclude` array after the removal. */ readonly exclude: readonly string[]; /** Rules actually present in `exclude` and dropped, in config order. */ readonly removedRules: readonly string[]; }; /** * Pure: given the current `exclude` list and the rules to drop, return * the new list. No fs, no clock, no serialization. * * A rule named in `rulesToRemove` but absent from `exclude` is NOT * invented — the result is a subset of the input, so a caller can * never add a rule by accident. Removing an already-absent rule is a * no-op, which is what makes the whole repair idempotent: feeding the * repaired list back in yields `changed: false`. */ export declare function repairCodegraphExclude(input: { readonly exclude: readonly string[]; readonly rulesToRemove: readonly string[]; }): CodegraphExcludeRepairPlan; export type CodegraphConfigRepairPlan = { /** Rules from the exclude reconciler to drop. */ readonly rulesToRemove: readonly string[]; }; export type CodegraphConfigRepairOutcome = { readonly applied: false; readonly reason: 'nothing-to-repair'; readonly removedRules: readonly string[]; } | { readonly applied: true; readonly configPath: string; readonly backupPath: string; readonly removedRules: readonly string[]; readonly excludeCountBefore: number; readonly excludeCountAfter: number; }; /** * Apply BOTH config repairs to `/.codegraph/config.json` in one * atomic rewrite. * * No-op (and no write, no mtime change, no backup) when both lists are * empty. Otherwise: back up the original bytes to `config.json.bak`, then * rewrite the file with `exclude` reduced by exactly the rules that were * both requested and present, and `include` extended by exactly the * patterns that were both requested and absent. * * ONE rewrite, not two. A caller that widened `include` in one write and * dropped the newly-offending `exclude` rules in a second would leave a * window in which the config on disk is worse than it started (the widened * include admits a file that a surviving rule then hides from the index), * and would need two backups to stay rollback-exact. One rewrite through * the same-directory temp file has neither property. * * Throws only on real fs/parse failures and on the containment refusal * above — the caller decides whether that is fatal (`repair-exclude` / * `repair-index` → non-zero exit) or a surfaced warning (`init` → keep * going, the init itself already succeeded). Both are refusals BEFORE * anything is written, so a throw can never half-apply. */ export declare function applyCodegraphConfigRepair(projectRoot: string, repair: CodegraphConfigRepairPlan): CodegraphConfigRepairOutcome; export type CodegraphConfigRollbackResult = { readonly rolledBack: true; readonly configPath: string; readonly backupPath: string; } | { readonly rolledBack: false; readonly configPath: string; readonly backupPath: string; /** Why the rollback did not happen. Never `null` on this arm. */ readonly error: string; }; /** * Put `configPath` back to the bytes `writeConfigBackup` saved beside it, and * back to the mode those bytes were saved with. * * This is the reverse of `applyCodegraphConfigRepair`'s write half and the only * reason the `.bak` exists: until this function, the backup had no reader * anywhere in the repository, so "byte-exact rollback" described a file nobody * ever restored. * * The guard is `linkOrDirectoryAt` — the SAME predicate that refuses to WRITE * through a link at the backup path refuses to READ through one here. The * attack it closes is the mirror image of the writer's: `config.json.bak` is * fixed and committable, so a repository that ships it as a link would * otherwise have the link's target's bytes published into `.codegraph/config.json` * as if they were the pre-repair config — attacker-chosen content, written * under the operator's own config path, from a file the operator never * reviewed. A refusal is a refusal and not a repair, at both ends. * * The restore runs through `writeConfigAtomic` (same-directory CSPRNG temp + * `renameSync`) for the same reason the backup write does: a crash mid-write * must leave the repaired config intact rather than a prefix of the restored * one, and the mode is applied to the TEMP before the rename so the published * file never exists at the process umask. The mode comes from `statSync` of the * backup, symmetrical with the write end: the copy carries the original * config's mode, so handing that mode back to the config restores what the * project had granted rather than this process's umask. * * A refusal, or a backup that cannot be read, is RETURNED rather than thrown: * the caller reports it as a field and must keep going — the forced rebuild it * was asked for still has to run. `writeConfigAtomic`'s own fs throw is left to * propagate, and the caller catches that too. */ export declare function rollbackCodegraphConfig(configPath: string): Promise;