/** * Reconcile scaffold-over-target collisions before the scaffold is copied into * an existing target. The scaffold IS the hq-core tree, and its shape can clash * with an existing HQ in two mirror-image ways that fs-extra's * `copy(..., { overwrite: true })` refuses to resolve on its own: * * 1. The scaffold ships a SYMLINK where the target holds a non-symlink (e.g. * hq-core's `AGENTS.md -> .claude/CLAUDE.md` over a hand-written AGENTS.md). * fs-extra's `onLink` reads the destination, and if `readlink(dest)` throws * `EINVAL` (dest is a regular file) it re-links WITHOUT unlinking first, so * `fs.symlink` fails with `EEXIST` and the whole install aborts (CREATE-HQ-5). * * 2. The scaffold ships a real DIRECTORY where the target holds a non-directory * — most often a whole-directory symlink from HQ's generated `.claude/skills` * link farm standing where hq-core now ships a real `.claude/skills/` * directory. fs-extra refuses to overwrite a non-directory with a directory * ("Cannot overwrite non-directory ... with directory ..."), so the whole * install aborts (CREATE-HQ-6). * * These reconcilers walk the SOURCE tree and, at every path where the scaffold * shape cannot land as-is, clear the blocking target ENTRY — never its referent * — so the subsequent `copy({ overwrite: true })` can create it. A real target * directory standing where the scaffold ships a symlink is never deleted: that * raises ScaffoldSymlinkCollisionError instead. Everything else is left for * `copy({ overwrite: true })` to handle exactly as before, so the fix stays * version-independent: if a future fs-extra honours both overwrite directions, * the reconciler simply becomes a no-op. */ /** * Thrown when the scaffold ships a symlink at a path where the target already * holds a real directory. fs-extra itself refuses directory-over-file/link * overwrites, and recursively deleting a directory the user never agreed to * lose would be worse than failing — so we stop with a precise, actionable * message that names the exact path instead of fs-extra's opaque error. */ export declare class ScaffoldSymlinkCollisionError extends Error { /** Path relative to the target root, e.g. `AGENTS.md` or `.agents/skills`. */ readonly relativePath: string; /** Absolute path of the colliding directory in the target. */ readonly targetPath: string; constructor(relativePath: string, targetPath: string); } /** The kind of scaffold entry a blocking target was cleared to make room for. */ export type ScaffoldReplaceKind = "symlink" | "directory"; /** Reports a target entry that was replaced to make room for a scaffold entry. */ export type ScaffoldReplaceReporter = (relativePath: string, kind: ScaffoldReplaceKind) => void; /** * Async: clear every target path the scaffold shape cannot land over so the * subsequent `copy({ overwrite: true })` can create it — a non-symlink standing * where the scaffold ships a symlink, or a non-directory standing where the * scaffold ships a directory. Reports each replaced entry through `onReplace` * with the kind installed in its place. Throws `ScaffoldSymlinkCollisionError` * if the scaffold ships a symlink where the target holds a real directory. * * Two phases: a read-only pass collects every replacement and throws on the * FIRST directory collision BEFORE anything is unlinked, so a collision found * late in the walk can never leave an already-deleted user file behind for an * install that then aborts. */ export declare function reconcileScaffoldSymlinks(sourceRoot: string, targetDir: string, onReplace?: ScaffoldReplaceReporter): Promise; /** Sync twin of {@link reconcileScaffoldSymlinks}. */ export declare function reconcileScaffoldSymlinksSync(sourceRoot: string, targetDir: string, onReplace?: ScaffoldReplaceReporter): void; /** * Sync helper for the `--local-template` install site: ensure the target * exists, reconcile scaffold symlink/directory collisions, then copy the tree * with fs-extra's own `overwrite` semantics. Kept as one unit so the reconcile * step can never be accidentally skipped ahead of the copy. */ export declare function installScaffoldTreeSync(sourceRoot: string, targetDir: string, onReplace?: ScaffoldReplaceReporter): void; //# sourceMappingURL=scaffold-copy.d.ts.map