import * as path from "path"; import { type SnapshotManifest } from "../backup-prune.js"; export interface RunRescueResult { status: number; } interface Config { ref: string; sourceRepo: string; dryRun: boolean; assumeYes: boolean; cloudUpdate: boolean; extraPreserve: string[]; preserveSubpaths: string[]; narrowPaths: string[]; hqRootOverride: string; historyCheck: boolean; floorShaOverride: string; doBackup: boolean; backupRoot: string; backupRetentionDays: string; backupRetentionCount: string; recoverSnapshots: boolean; verifyRestoredSnapshot: string; restoreSnapshot: string; } /** Read an environment value without relying on Windows' case-insensitive proxy. */ export declare function envValue(env: NodeJS.ProcessEnv, name: string): string | undefined; /** * Cygwin's rsync is a POSIX program even when Node gave us a native Win32 * path. Keep UNC paths in Cygwin's documented `//server/share` form and map * drive roots explicitly instead of relying on Cygwin's native-path fallback. */ export declare function toCygwinRsyncPath(localPath: string): string; interface RsyncPathCandidate { path: string; executable: boolean; } interface RsyncLauncher { command: string; leadingArgs: string[]; } /** * Scan PATH `entries` for an rsync binary. On win32, a real `rsync.exe` (or * any non-shim candidate with an extension) found ANYWHERE on PATH always * wins over a `.cmd`/`.bat` shim, even if the shim appears earlier in PATH * order — shims must be routed through cmd.exe (see `buildRsyncLauncher`) * and are strictly less reliable, so we only fall back to one when no real * executable exists anywhere on PATH. * * An extensionless `rsync` is not a win32 hit: Windows resolves the bare * name through PATHEXT and will never start that file. Keep scanning so a * later `.exe` or `.cmd` wins. If the extensionless file is the only hit, * report it as present but not executable instead of launching it. */ export declare function findRsyncOnPath(entries: string[], env: NodeJS.ProcessEnv): RsyncPathCandidate | undefined; /** The overlay requires rsync; do not allocate a recovery copy when it cannot run. */ export declare function requireWorkingRsync(env: NodeJS.ProcessEnv, err: (s: string) => void): RsyncLauncher; /** * Parse the rescue argv exactly as the bash `while case` loop did, including * validation and the same exit codes (1 unknown/usage, 2 bad option). Throws * ExitError on any validation failure. */ export declare function parseRescueArgs(argv: string[]): Config; /** * Execute a fully-parsed rescue. `argv` are the same flags the bash script * accepted; `env` defaults to process.env (GH_TOKEN is read from it). Returns * the process-equivalent exit status. Never throws for normal control-flow * exits — those are mapped to the returned status. */ export declare function runRescue(argv: string[], opts?: { env?: NodeJS.ProcessEnv; /** Test-only override for the ls-remote probe. Not read from the environment. */ gitReachabilityProbeTimeoutMs?: number; }): RunRescueResult; /** * Restore one captured path, then recreate only manifest-recorded symlinks * whose locations and targets are both confined to the HQ root. */ export declare function restoreSnapshotPath(snapshotDir: string, hqRoot: string, rel: string, manifest: SnapshotManifest): void; /** * True when `root` is usable as a rescue target: an absolute path that is not * a bare drive letter. Pure + exported for tests; `platformPath` defaults to * the host `path` module so Windows and POSIX runtimes each apply their own * absoluteness rules (tests pass `path.win32` / `path.posix` explicitly). * * Rejecting bare `C:` matters even though `path.win32.isAbsolute("C:")` is * already false — the explicit pattern documents the exact field failure and * guards against a future helper swap that treats drive-relative paths as * absolute. */ export declare function isUsableRescueRoot(root: string, platformPath?: path.PlatformPath): boolean; /** * Canonicalize the rescue root without treating a known-good directory as * unusable when Node's JavaScript realpath walker cannot inspect a Windows * drive prefix. Prefer the native syscall, retain the JS walker as a fallback, * and use an absolute path only after stat confirms it is a directory. */ export declare function realpathOrExit(p: string, err: (s: string) => void): string; export type RescueDecision = { kind: "skip"; rel: string; } | { kind: "drop-conflict-artifact"; rel: string; } | { kind: "drop-script-managed"; rel: string; } | { kind: "drop-reindex-symlink"; rel: string; target: string; } | { kind: "user-only"; rel: string; } | { kind: "cloud-symlink-reconciled"; rel: string; } | { kind: "drift-reconciled"; rel: string; } | { kind: "user-edit-diff-append"; rel: string; } | { kind: "user-edit-overwrite-safe"; rel: string; } | { kind: "user-edit-keep-local"; rel: string; } | { kind: "user-edit-conflict"; rel: string; } | { kind: "user-edit-rescue"; rel: string; target: string; } | { kind: "unchanged-preserve"; rel: string; } | { kind: "unchanged-delete"; rel: string; } | { kind: "wholesale-replace-template"; rel: "companies/_template"; }; type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue; }; /** Structural deep equality for JSON values (stable, order-sensitive for objects). */ export declare function jsonDeepEqual(a: JsonValue, b: JsonValue): boolean; /** * Compute the drift of `ours` relative to `base` (the three-way "ours − base"). * Returns a partial object holding only the values `ours` added or changed * versus `base`, suitable for merging into settings.local.json so Claude Code's * higher-precedence Local scope re-supplies them on top of the release baseline. * * - object: recurse per key; a key absent from `base` is drift wholesale. * - array: drift = the elements present in `ours` but not deep-contained in * `base`. This matches Claude Code's union merge for permissions/hooks, so a * relocated allow/deny entry unions back into the effective settings. * - scalar / type-mismatch: drift = ours's value when not deep-equal. * * `excludePath` names dotted key paths that are NEVER drift (env.PATH — a * machine-local value re-composed onto the release baseline, not relocated). * Returns `undefined` when there is no drift at this node. */ export declare function settingsDrift(ours: JsonValue, base: JsonValue | undefined, excludePath?: string[], pathPrefix?: string): JsonValue | undefined; /** * Deep-merge `patch` into `target` in place and return it. Objects merge * per-key; arrays union (append patch elements not already deep-present, so a * re-run is idempotent and no duplicate permission/hook entries accumulate); * scalars are added ONLY when the key is absent from `target`. A scalar the * user already set in Local is the higher-precedence value and MUST NOT be * clobbered by the lower-precedence Project drift (e.g. Project `model: opus` + * Local `model: sonnet` must stay `sonnet` after rescue). Mirrors how Claude * Code's Local scope overrides/unions over Project. */ export declare function deepMergeSettings(target: { [k: string]: JsonValue; }, patch: { [k: string]: JsonValue; }): { [k: string]: JsonValue; }; /** * Apply the user's array REMOVALS (elements present in `base` but absent from * `ours`) to the corresponding arrays in `theirs`, in place. A union-style Local * overlay cannot express a removal, so a permission/hook a user deliberately * deleted from the generated settings.json would otherwise be restored by the * release baseline and never removable. Filtering `theirs` — the Project file we * now own — is the only representation of that intent. Recurses through objects; * only acts when all three nodes are arrays (or objects to recurse). Requires a * REAL floor base: never call with base = theirs (HEAD), or release-only entries * would be mistaken for removals. */ export declare function applyArrayRemovals(theirs: { [k: string]: JsonValue; }, ours: { [k: string]: JsonValue; }, base: { [k: string]: JsonValue; }): void; interface ReconcileCtx { hqRoot: string; srcDir: string; env: NodeJS.ProcessEnv; historyFloor: string; baselineMode: "history_floor" | "head_compare"; /** cfg.preserveSubpaths — used to honor an explicit `--preserve-subpath` of * settings.json (or the whole `.claude` tree). */ preserveSubpaths: string[]; } /** * Post-overlay reconcile for the machine-local, generated `.claude/settings.json`. * * At entry the overlay has already re-laid the release template (theirs) over * settings.json, and the user's settings.local.json has been restored from the * preserve shuttle. This function: * 1. re-composes env.PATH onto theirs (machine-specific; deliverable 3), and * 2. extracts ours's drift versus the last-sync floor (base) — excluding * env.PATH — and merges it into settings.local.json (deliverable 2), * so the EFFECTIVE merged settings (Project ⊕ Local, per Claude Code's rules) * are preserved while the Project base returns to the shipped composition with * any newly-released hook registrations wired in (deliverable 1). * * Best-effort and non-fatal: unreadable/invalid JSON degrades to a * PATH-recompose-only pass (never worse than the old behavior), matching * rescue's posture elsewhere. */ export declare function reconcileClaudeSettings(ctx: ReconcileCtx, preRescueSettingsJson: string | null, out: (s: string) => void, err: (s: string) => void): void; /** Format the exact safe command presented in a breaker diagnostic. */ export declare function formatRecoveryCommand(hqRoot: string, backupRoot: string, verifyRestoredSnapshot?: string): string; /** Format the manifest-aware command that restores links as well as files. */ export declare function formatSnapshotRestoreCommand(hqRoot: string, backupRoot: string, snapshotName: string): string; /** * Paths the current process cannot write among the action plan's rename/delete * sources (and their parents) plus rename destination parents. Empty means the * plan is writable; a non-empty result must refuse before allocating a snapshot. */ export declare function collectUnwritableRescueMutationPaths(hqRoot: string, actions: ReadonlyArray<{ affectedRels: readonly string[]; destParentRels?: readonly string[]; }>): string[]; export {}; //# sourceMappingURL=rescue-core.d.ts.map