/**
 * `olam flywheel migrate-overlays` — operator-local overlay-layer migration.
 *
 * Scans `~/.claude/{skills,agents}.overrides/` recursively for hardcoded
 * `~/.claude/scripts/<name>` references matching the SHIM_TARGETS registry;
 * sed-replaces each match with the corresponding `olam flywheel <subcmd>`
 * invocation. Each operator runs this once to migrate THEIR layer 3
 * (per-user overlay files) from the legacy script-path contract to the new
 * flywheel CLI surface.
 *
 * Closes K1 OQ23 (pass-3 resolution): the migration helper that scales
 * the namespace migration beyond just this operator's overlays. Each
 * operator with their own `.overrides/` tree runs this command once.
 *
 * Replaces:
 *   `bash ~/.claude/scripts/emit-breadcrumb.sh ...` → `olam flywheel emit-breadcrumb ...`
 *   `python3 ~/.claude/scripts/sanitize-persona-output.py ...` → `olam flywheel sanitize-persona-output ...`
 *   `~/.claude/scripts/k5-rubric-helper.py` → `olam flywheel k5-validate`
 *   etc. (full set per SHIM_TARGETS registry at @olam/core/lib/shim-targets)
 *
 * Flags:
 *   --dry-run             — preview changes without writing
 *   --target-dir <path>   — override the search root (default: ~/.claude/); for tests
 *   --json                — emit summary as JSON for tooling
 *
 *   --push                — PUSH mode: copy overlays into atlas-toolbox clone
 *   --target <name>       — required with --push; name of a registered skill source
 *
 * Idempotent: re-runs against already-migrated overlays produce no diff.
 *
 * Plan reference: docs/plans/olam-flywheel-cli-namespace/phase-c-tasks.md § C7
 * Phase B: docs/plans/member-overlays-sync/phase-b-tasks.md
 */
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { homedir } from 'node:os';
import { dirname, join, relative } from 'node:path';
import { SHIM_TARGETS } from '@olam/core/src/lib/shim-targets.js';
import { listSkillSources, skillSourceClonePath, } from '@olam/core/src/skill-sources/index.js';
import { validateCanonical, formatCanonicalViolation, } from '@olam/core/src/skill-sync/prefix-rules.js';
// ---------------------------------------------------------------------------
// Rewrite-mode helpers (Phase C7 — unchanged)
// ---------------------------------------------------------------------------
function escapeRegex(s) {
    return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function applyReplacements(content) {
    let working = content;
    const replacements = [];
    for (const target of SHIM_TARGETS) {
        const escaped = escapeRegex(target.basename);
        const patterns = [
            { re: new RegExp(`(?:bash |python3 )?~/\\.claude/scripts/${escaped}`, 'g'), label: 'tilde' },
            { re: new RegExp(`(?:bash |python3 )?\\$HOME/\\.claude/scripts/${escaped}`, 'g'), label: 'home' },
            { re: new RegExp(`(?:bash |python3 )?/Users/[\\w.-]+/\\.claude/scripts/${escaped}`, 'g'), label: 'absolute' },
        ];
        let count = 0;
        for (const { re } of patterns) {
            const matches = working.match(re);
            if (matches !== null) {
                count += matches.length;
                working = working.replace(re, target.replacedBy);
            }
        }
        if (count > 0) {
            replacements.push({ basename: target.basename, count, replacedBy: target.replacedBy });
        }
    }
    return { newContent: working, replacements };
}
function walkOverlayFiles(dir) {
    const found = [];
    let entries;
    try {
        entries = readdirSync(dir);
    }
    catch {
        return [];
    }
    for (const entry of entries) {
        const fullPath = join(dir, entry);
        let stat;
        try {
            stat = statSync(fullPath);
        }
        catch {
            continue;
        }
        if (stat.isDirectory()) {
            found.push(...walkOverlayFiles(fullPath));
        }
        else if (stat.isFile() && /\.(md|json|sh)$/.test(entry)) {
            found.push(fullPath);
        }
    }
    return found;
}
// ---------------------------------------------------------------------------
// Push-mode helpers (Phase B)
// ---------------------------------------------------------------------------
/**
 * Error thrown by push-mode operations. Carries an exit code so callers
 * can distinguish between different failure scenarios.
 */
export class PushError extends Error {
    exitCode;
    constructor(exitCode, message) {
        super(message);
        this.exitCode = exitCode;
        this.name = 'PushError';
    }
}
const ATLAS_USER_RE = /^[a-z0-9][a-z0-9_-]{0,38}$/;
/**
 * Validate that `value` matches the GitHub login shape used as atlas-user.
 * Throws PushError on mismatch so callers handle it via the standard error path.
 */
function assertValidAtlasUser(value) {
    if (!ATLAS_USER_RE.test(value)) {
        throw new PushError(1, `invalid atlasUser "${value}" in ~/.claude/.atlas-user — must match /^[a-z0-9][a-z0-9_-]{0,38}$/`);
    }
}
/**
 * Read atlas-user from ~/.claude/.atlas-user (or OLAM_CLAUDE_DIR override).
 */
function resolveAtlasUser(opts) {
    if (opts._testAtlasUser !== undefined) {
        const v = opts._testAtlasUser;
        if (!v)
            return null;
        assertValidAtlasUser(v);
        return v;
    }
    const claudeDir = opts._testClaudeDir ?? process.env['OLAM_CLAUDE_DIR'] ?? join(homedir(), '.claude');
    const f = join(claudeDir, '.atlas-user');
    if (existsSync(f)) {
        const v = readFileSync(f, 'utf-8').trim();
        if (v.length === 0)
            return null;
        assertValidAtlasUser(v);
        return v;
    }
    return null;
}
/**
 * Run a git command, returning stdout. Throws on non-zero exit.
 */
function runGit(args, cwd) {
    const result = spawnSync('git', args, {
        cwd,
        encoding: 'utf-8',
        stdio: ['ignore', 'pipe', 'pipe'],
    });
    if (result.status !== 0) {
        const errMsg = (result.stderr ?? '').trim() || `git ${args[0]} failed`;
        throw new Error(errMsg);
    }
    return (result.stdout ?? '').trim();
}
/**
 * Walk overlay source directories for push-mode.
 * Returns array of { srcFile, overlayKind: 'skills' | 'agents', relPath }.
 * Skips paths matching *.overrides.local/ (Decision #4).
 */
function walkPushSourceFiles(claudeDir) {
    const result = [];
    const roots = [
        { dir: join(claudeDir, 'skills.overrides'), overlayKind: 'skills' },
        { dir: join(claudeDir, 'agents.overrides'), overlayKind: 'agents' },
    ];
    for (const { dir, overlayKind } of roots) {
        const files = walkOverlayFiles(dir);
        for (const f of files) {
            // Defensive skip for .overrides.local/ paths (Decision #4)
            if (f.includes('.overrides.local'))
                continue;
            const relPath = relative(dir, f);
            result.push({ srcFile: f, overlayKind, relPath });
        }
    }
    return result;
}
/**
 * Generate branch name. If exists on origin, auto-suffix -2, -3, etc.
 */
function resolveBranchName(clonePath, atlasUser) {
    const now = new Date();
    const pad = (n) => String(n).padStart(2, '0');
    const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
    const base = `feat/${atlasUser}/overlay-update-${ts}`;
    // Check if candidate exists on origin OR locally; auto-suffix if so.
    let candidate = base;
    let suffix = 2;
    while (true) {
        let existsOnOrigin = false;
        let existsLocally = false;
        try {
            const out = runGit(['ls-remote', 'origin', candidate], clonePath);
            existsOnOrigin = out.length > 0;
        }
        catch {
            // ls-remote failure (offline, etc.) — assume name is free on origin
        }
        if (!existsOnOrigin) {
            try {
                runGit(['rev-parse', '--verify', `refs/heads/${candidate}`], clonePath);
                existsLocally = true;
            }
            catch {
                // non-zero exit means branch does not exist locally
            }
        }
        if (!existsOnOrigin && !existsLocally)
            break;
        candidate = `${base}-${suffix}`;
        suffix += 1;
        if (suffix > 100)
            break; // safety ceiling
    }
    return candidate;
}
/**
 * Run the pre-flight gate against the clone.
 *
 * When `dryRunWarnOnly` is true, blocking conditions emit stderr WARNINGs
 * instead of returning error strings — the dry-run path collects warnings
 * so the operator can see what would block a real run. The returned
 * `preflightWarnings` array contains any warnings emitted.
 *
 * When `dryRunWarnOnly` is false (default / real-run), the first blocking
 * condition returns immediately with `error` set.
 */
function runPreFlight(clonePath, forceCurrentBranch, dryRunWarnOnly = false) {
    const warnings = [];
    const warn = (msg) => {
        process.stderr.write(`[dry-run] pre-flight WARNING: ${msg}\n`);
        warnings.push(msg);
    };
    // 1. Dirty working tree
    let statusOut;
    try {
        statusOut = runGit(['status', '--porcelain'], clonePath);
    }
    catch (err) {
        const msg = `pre-flight: failed to check clone status: ${err instanceof Error ? err.message : String(err)}`;
        return { error: msg, preflightWarnings: warnings };
    }
    if (statusOut.length > 0) {
        const msg = [
            `pre-flight: clone has uncommitted changes at "${clonePath}".`,
            `  Resolve before pushing overlays.`,
            `  Actions: [Stash and continue] / [Abort]`,
        ].join('\n');
        if (dryRunWarnOnly) {
            warn(msg);
        }
        else {
            return { error: msg, preflightWarnings: warnings };
        }
    }
    // 2. Not on main branch
    let currentBranch;
    try {
        currentBranch = runGit(['rev-parse', '--abbrev-ref', 'HEAD'], clonePath);
    }
    catch (err) {
        const msg = `pre-flight: failed to determine current branch: ${err instanceof Error ? err.message : String(err)}`;
        return { error: msg, preflightWarnings: warnings };
    }
    if (currentBranch !== 'main' && !forceCurrentBranch) {
        const msg = [
            `pre-flight: clone is on branch "${currentBranch}" (expected "main").`,
            `  Switch to main first, or use --force-current-branch to continue on "${currentBranch}".`,
            `  Actions: [Switch to main first] / [Continue on current branch]`,
        ].join('\n');
        if (dryRunWarnOnly) {
            warn(msg);
        }
        else {
            return { error: msg, preflightWarnings: warnings };
        }
    }
    // 3. Clone behind origin/main — warn but allow continue (both modes)
    try {
        runGit(['fetch', 'origin'], clonePath);
        const behindCount = runGit(['rev-list', '--count', 'HEAD..origin/main'], clonePath);
        const n = parseInt(behindCount, 10);
        if (!isNaN(n) && n > 0) {
            process.stderr.write(`warning: clone is ${n} commit(s) behind origin/main.\n` +
                `  Run \`git -C "${clonePath}" pull\` first, or continue with stale base.\n` +
                `  Actions: [Pull first] / [Continue]\n`);
        }
    }
    catch {
        // fetch failure is non-fatal (offline); proceed
    }
    return { error: null, preflightWarnings: warnings };
}
/**
 * Core push-overlays implementation (Phase B).
 * Returns PushSummary. Throws PushError on validation/pre-flight failures
 * (lets callers handle process.exit vs test assertions cleanly).
 */
export function pushOverlays(opts) {
    // B2 — Resolve atlas-user
    const atlasUser = resolveAtlasUser(opts);
    if (atlasUser === null) {
        throw new PushError(1, `~/.claude/.atlas-user not set.\n` +
            `  Run \`olam skills atlas-user set <name>\` to configure your atlas user.`);
    }
    // Determine source overlay directory and walk overlay files.
    const claudeDir = opts._testClaudeDir ?? opts.targetDir ?? process.env['OLAM_CLAUDE_DIR'] ?? join(homedir(), '.claude');
    const sourceFiles = walkPushSourceFiles(claudeDir);
    // Phase A skill-prefix-rules reverse validator. Refuses push of overlays
    // whose filename basename OR frontmatter `name:` carries any registered
    // prefix. Runs BEFORE clone-path resolution and pre-flight so the
    // operator gets an actionable error without needing a valid clone target.
    // Locks the canonical-only invariant at push time (forward-rewrite on sync
    // forms a defense-in-depth pair with this reverse validator).
    const registeredPrefixes = (opts._testSkillSources ?? listSkillSources())
        .map((s) => s.prefix)
        .filter((p) => typeof p === 'string' && p.length > 0);
    if (registeredPrefixes.length > 0) {
        for (const { srcFile, relPath } of sourceFiles) {
            const basename = relPath.split('/').pop() ?? relPath;
            let content;
            try {
                content = readFileSync(srcFile);
            }
            catch {
                continue;
            }
            const validation = validateCanonical(basename, content, registeredPrefixes);
            if (!validation.ok && validation.violation !== undefined) {
                throw new PushError(1, formatCanonicalViolation(relPath, validation.violation));
            }
        }
    }
    // B2 — Resolve skill source (test injection or registry lookup)
    let clonePath;
    if (opts._testClonePath !== undefined) {
        clonePath = opts._testClonePath;
    }
    else {
        // Resolve from registry (or test-injected sources)
        const sources = opts._testSkillSources ?? listSkillSources();
        const source = sources.find((s) => s.name === opts.target) ?? null;
        if (source === null) {
            const available = sources.map((s) => s.name).join(', ') || '(none)';
            throw new PushError(1, `target source "${opts.target}" not registered.\n` +
                `  Available sources: ${available}\n` +
                `  Run \`olam skills source add\` to register one.\n` +
                `  Actions: [List sources] / [Cancel]`);
        }
        clonePath = skillSourceClonePath(source.id);
    }
    // Dry-run: surface pre-flight warnings, then preview copy actions (no clone mutation)
    if (opts.dryRun === true) {
        const { preflightWarnings } = runPreFlight(clonePath, opts.forceCurrentBranch === true, true);
        if (preflightWarnings.length > 0) {
            process.stderr.write(`[dry-run] pre-flight warnings (would BLOCK in real run):\n` +
                preflightWarnings.map((w) => `  - ${w.split('\n')[0]}`).join('\n') +
                '\n');
        }
        const membersBase = join(clonePath, 'members', atlasUser);
        let wouldCopy = 0;
        let wouldUnchange = 0;
        let wouldCreate = 0;
        for (const { srcFile, overlayKind, relPath } of sourceFiles) {
            const targetDir = join(membersBase, `${overlayKind}.overrides`);
            const targetFile = join(targetDir, relPath);
            let srcBuf;
            try {
                srcBuf = readFileSync(srcFile);
            }
            catch {
                continue;
            }
            if (existsSync(targetFile)) {
                const dstBuf = readFileSync(targetFile);
                if (srcBuf.equals(dstBuf)) {
                    wouldUnchange += 1;
                    process.stdout.write(`  [skip] ${overlayKind}.overrides/${relPath} (unchanged)\n`);
                    continue;
                }
                wouldCopy += 1;
                process.stdout.write(`  [copy] ${overlayKind}.overrides/${relPath} (changed)\n`);
            }
            else {
                wouldCreate += 1;
                process.stdout.write(`  [new]  ${overlayKind}.overrides/${relPath}\n`);
            }
        }
        process.stdout.write(`[dry-run] would copy ${wouldCopy + wouldCreate} file(s), ${wouldUnchange} unchanged.\n`);
        return {
            filesCopied: wouldCopy,
            filesUnchanged: wouldUnchange,
            filesCreated: wouldCreate,
            branchName: '',
            nextStepGhCommand: '',
        };
    }
    // B4 — Pre-flight gate (real run — fail fast)
    const { error: preFlightError } = runPreFlight(clonePath, opts.forceCurrentBranch === true, false);
    if (preFlightError !== null) {
        throw new PushError(1, preFlightError);
    }
    // B4 — Generate and create branch
    const branchName = resolveBranchName(clonePath, atlasUser);
    try {
        runGit(['checkout', '-b', branchName], clonePath);
    }
    catch (err) {
        throw new PushError(1, `failed to create branch "${branchName}": ${err instanceof Error ? err.message : String(err)}`);
    }
    // B3 — Copy files (atomic: rollback branch on any copy failure)
    const membersBase = join(clonePath, 'members', atlasUser);
    let filesCopied = 0;
    let filesUnchanged = 0;
    let filesCreated = 0;
    try {
        for (const { srcFile, overlayKind, relPath } of sourceFiles) {
            const targetDir = join(membersBase, `${overlayKind}.overrides`, dirname(relPath));
            const targetFile = join(membersBase, `${overlayKind}.overrides`, relPath);
            let srcBuf;
            try {
                srcBuf = readFileSync(srcFile);
            }
            catch {
                continue;
            }
            const targetExists = existsSync(targetFile);
            if (targetExists) {
                const dstBuf = readFileSync(targetFile);
                if (srcBuf.equals(dstBuf)) {
                    filesUnchanged += 1;
                    continue;
                }
                // Content differs — overwrite
                mkdirSync(targetDir, { recursive: true });
                copyFileSync(srcFile, targetFile);
                filesCopied += 1;
            }
            else {
                // New file
                mkdirSync(targetDir, { recursive: true });
                copyFileSync(srcFile, targetFile);
                filesCreated += 1;
            }
        }
    }
    catch (copyErr) {
        // Rollback: return clone to main and delete the new branch
        try {
            runGit(['checkout', 'main'], clonePath);
        }
        catch {
            // best-effort
        }
        try {
            runGit(['branch', '-D', branchName], clonePath);
        }
        catch {
            // best-effort
        }
        const origMsg = copyErr instanceof Error ? copyErr.message : String(copyErr);
        throw new PushError(1, `Copy failed partway through — clone reverted to main. Original: ${origMsg}`);
    }
    // B4 — Compose next-steps copy-paste block
    const memberSubdir = `members/${atlasUser}/`;
    const autoSubject = `feat: push operator overlays for ${atlasUser}`;
    const nextStepGhCommand = `gh pr create --base main --head ${branchName} --title "${autoSubject}" --body "Operator overlay sync for ${atlasUser}."`;
    const copyPasteBlock = [
        `\nNext steps (run in ${clonePath}):`,
        `  git -C "${clonePath}" add ${memberSubdir}`,
        `  git -C "${clonePath}" commit -m '${autoSubject}'`,
        `  git -C "${clonePath}" push -u origin ${branchName}`,
        `  ${nextStepGhCommand}`,
        '',
    ].join('\n');
    return {
        filesCopied,
        filesUnchanged,
        filesCreated,
        branchName,
        nextStepGhCommand,
        _copyPasteBlock: copyPasteBlock,
    };
}
// ---------------------------------------------------------------------------
// Init-member mode (Phase C C3)
// ---------------------------------------------------------------------------
/**
 * Scaffold members/<name>/{skills,agents}.overrides/.gitkeep + README.md
 * in the atlas-toolbox clone. Leaves the working tree dirty.
 * Throws PushError on validation / pre-flight / idempotency failures.
 */
export function initMember(opts) {
    const memberName = opts.initMember;
    // Validate member name shape (same regex as ATLAS_USER_RE).
    if (!ATLAS_USER_RE.test(memberName)) {
        throw new PushError(1, `invalid member name "${memberName}" — must match /^[a-z0-9][a-z0-9_-]{0,38}$/`);
    }
    // Resolve target source (reuse same path as pushOverlays).
    let clonePath;
    if (opts._testClonePath !== undefined) {
        clonePath = opts._testClonePath;
    }
    else {
        const sources = opts._testSkillSources ?? listSkillSources();
        const source = sources.find((s) => s.name === opts.target) ?? null;
        if (source === null) {
            const available = sources.map((s) => s.name).join(', ') || '(none)';
            throw new PushError(1, `target source "${opts.target}" not registered.\n` +
                `  Available sources: ${available}\n` +
                `  Run \`olam skills source add\` to register one.\n` +
                `  Actions: [List sources] / [Cancel]`);
        }
        clonePath = skillSourceClonePath(source.id);
    }
    // Resolve atlas-user for branch name.
    const atlasUser = resolveAtlasUser(opts) ?? memberName;
    const memberDir = join(clonePath, 'members', memberName);
    // Idempotency: if members/<name>/ already exists, refuse.
    if (existsSync(memberDir)) {
        throw new PushError(1, `members/${memberName}/ already exists; use --push to update; or remove manually first\n` +
            `  Actions: [Show existing] / [Cancel]`);
    }
    const wouldCreate = [
        join(memberDir, 'skills.overrides', '.gitkeep'),
        join(memberDir, 'agents.overrides', '.gitkeep'),
        join(memberDir, 'README.md'),
    ];
    // Dry-run: preview only.
    if (opts.dryRun === true) {
        const { preflightWarnings } = runPreFlight(clonePath, opts.forceCurrentBranch === true, true);
        if (preflightWarnings.length > 0) {
            process.stderr.write(`[dry-run] pre-flight warnings (would BLOCK in real run):\n` +
                preflightWarnings.map((w) => `  - ${w.split('\n')[0]}`).join('\n') +
                '\n');
        }
        for (const p of wouldCreate) {
            process.stdout.write(`  [new]  ${p.replace(clonePath + '/', '')}\n`);
        }
        process.stdout.write(`[dry-run] would create ${wouldCreate.length} path(s) in members/${memberName}/\n`);
        return {
            memberName,
            branchName: '',
            nextStepGhCommand: '',
            createdPaths: wouldCreate,
        };
    }
    // Real run: pre-flight gate.
    const { error: preFlightError } = runPreFlight(clonePath, opts.forceCurrentBranch === true, false);
    if (preFlightError !== null) {
        throw new PushError(1, preFlightError);
    }
    // Generate and create branch.
    const now = new Date();
    const pad = (n) => String(n).padStart(2, '0');
    const ts = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
    const branchName = `feat/${atlasUser}/init-member-${memberName}-${ts}`;
    let resolvedBranch = branchName;
    {
        let suffix = 2;
        while (true) {
            let existsLocally = false;
            try {
                runGit(['rev-parse', '--verify', `refs/heads/${resolvedBranch}`], clonePath);
                existsLocally = true;
            }
            catch {
                // doesn't exist
            }
            if (!existsLocally)
                break;
            resolvedBranch = `${branchName}-${suffix}`;
            suffix += 1;
            if (suffix > 100)
                break;
        }
    }
    try {
        runGit(['checkout', '-b', resolvedBranch], clonePath);
    }
    catch (err) {
        throw new PushError(1, `failed to create branch "${resolvedBranch}": ${err instanceof Error ? err.message : String(err)}`);
    }
    // CP3 ADV-C-2 fix: atomic copy rollback. If mkdirSync/writeFileSync
    // fails partway through, leave the clone on the new branch with partial
    // files — next invocation refuses the idempotency check + accumulates
    // orphan branches. Mirror the pushOverlays pattern: try/catch wrapped
    // around all create ops + best-effort cleanup (checkout main + branch -D)
    // before re-throwing as PushError with the "reverted to main" prefix.
    try {
        // Create directory tree.
        mkdirSync(join(memberDir, 'skills.overrides'), { recursive: true });
        writeFileSync(join(memberDir, 'skills.overrides', '.gitkeep'), '');
        mkdirSync(join(memberDir, 'agents.overrides'), { recursive: true });
        writeFileSync(join(memberDir, 'agents.overrides', '.gitkeep'), '');
        writeFileSync(join(memberDir, 'README.md'), `# Member overlays for \`${memberName}\`\n\nSee docs/plans/member-overlays-sync/ for layer architecture.\n`);
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        // Best-effort cleanup: switch back to main + delete the orphan branch.
        try {
            runGit(['checkout', 'main'], clonePath);
        }
        catch {
            // ignore; operator can recover manually
        }
        try {
            runGit(['branch', '-D', resolvedBranch], clonePath);
        }
        catch {
            // ignore
        }
        throw new PushError(1, `Scaffold failed partway through — clone reverted to main. Original: ${msg}`);
    }
    // Compose copy-paste block.
    const autoSubject = `feat: scaffold members/${memberName}/ for atlas-toolbox-tracked overlays`;
    const nextStepGhCommand = `gh pr create --base main --head ${resolvedBranch} --title "${autoSubject}" --body "Scaffold member overlay directory for ${memberName}."`;
    const copyPasteBlock = [
        `\nNext steps (run in ${clonePath}):`,
        `  git -C "${clonePath}" add members/${memberName}/`,
        `  git -C "${clonePath}" commit -m '${autoSubject}'`,
        `  git -C "${clonePath}" push -u origin ${resolvedBranch}`,
        `  ${nextStepGhCommand}`,
        '',
    ].join('\n');
    return {
        memberName,
        branchName: resolvedBranch,
        nextStepGhCommand,
        createdPaths: wouldCreate,
        _copyPasteBlock: copyPasteBlock,
    };
}
// ---------------------------------------------------------------------------
// Rewrite-mode entry (Phase C7 — unchanged)
// ---------------------------------------------------------------------------
export function migrateOverlays(opts = {}) {
    const root = opts.targetDir ?? join(homedir(), '.claude');
    const overrideRoots = [
        join(root, 'skills.overrides'),
        join(root, 'agents.overrides'),
    ];
    const allFiles = [];
    for (const overrideRoot of overrideRoots) {
        allFiles.push(...walkOverlayFiles(overrideRoot));
    }
    const summary = {
        scanned: allFiles.length,
        modified: 0,
        unchanged: 0,
        totalReplacements: 0,
        perFile: [],
    };
    for (const filePath of allFiles) {
        let original;
        try {
            original = readFileSync(filePath, 'utf8');
        }
        catch {
            continue;
        }
        const { newContent, replacements } = applyReplacements(original);
        const totalReplacements = replacements.reduce((s, r) => s + r.count, 0);
        if (totalReplacements === 0) {
            summary.unchanged += 1;
            continue;
        }
        if (opts.dryRun !== true) {
            writeFileSync(filePath, newContent, 'utf8');
        }
        summary.modified += 1;
        summary.totalReplacements += totalReplacements;
        summary.perFile.push({ path: filePath, replacements, totalReplacements });
    }
    return summary;
}
// ---------------------------------------------------------------------------
// Commander registration
// ---------------------------------------------------------------------------
export function registerFlywheelMigrateOverlays(parent) {
    parent
        .command('migrate-overlays')
        .description('Migrate operator-local overlay files (~/.claude/{skills,agents}.overrides/) from hardcoded ~/.claude/scripts/<name> references to olam flywheel <subcmd> invocations. With --push, copy overlays into atlas-toolbox clone.')
        .option('--dry-run', 'preview changes without modifying files')
        .option('--target-dir <path>', 'override ~/.claude/ root (default: $HOME/.claude); for tests')
        .option('--json', 'emit summary as JSON instead of human-readable')
        .option('--push', 'PUSH mode: copy overlays into atlas-toolbox clone')
        .option('--target <name>', 'skill-source name to push into (required with --push or --init-member)')
        .option('--force-current-branch', 'Allow --push from non-main branches without pre-flight refusal')
        .option('--init-member <name>', 'Scaffold members/<name>/{skills,agents}.overrides/ on the atlas-toolbox clone')
        .action((opts) => {
        // T6 mode-overloading guard: --push requires --target
        if (opts.push === true && !opts.target) {
            process.stderr.write(`Error: --push requires --target=<skill-source-name>.\n` +
                `  Actions: [Add --target=atlas-toolbox] / [Cancel]\n`);
            process.exit(1);
        }
        // C3 mode-overloading guard: --init-member requires --target
        if (opts.initMember !== undefined && !opts.target) {
            process.stderr.write(`Error: --init-member requires --target=<skill-source-name>.\n` +
                `  Actions: [Add --target=atlas-toolbox] / [Cancel]\n`);
            process.exit(1);
        }
        if (opts.initMember !== undefined) {
            let summary;
            try {
                summary = initMember(opts);
            }
            catch (err) {
                if (err instanceof PushError) {
                    process.stderr.write(`Error: ${err.message}\n`);
                    process.exit(err.exitCode);
                }
                throw err;
            }
            if (opts.json === true) {
                process.stdout.write(JSON.stringify(summary) + '\n');
                return;
            }
            const dryRunLabel = opts.dryRun === true ? ' (dry-run)' : '';
            process.stdout.write(`olam flywheel migrate-overlays --init-member ${opts.initMember} → ${opts.target}${dryRunLabel}\n`);
            if (opts.dryRun !== true) {
                process.stdout.write(`  branch:  ${summary.branchName}\n`);
                process.stdout.write(`  created: ${summary.createdPaths.length} path(s)\n`);
                if (summary._copyPasteBlock !== undefined) {
                    process.stdout.write(summary._copyPasteBlock);
                }
            }
            return;
        }
        if (opts.push === true) {
            let summary;
            try {
                summary = pushOverlays(opts);
            }
            catch (err) {
                if (err instanceof PushError) {
                    process.stderr.write(`Error: ${err.message}\n`);
                    process.exit(err.exitCode);
                }
                throw err;
            }
            if (opts.json === true) {
                process.stdout.write(JSON.stringify(summary) + '\n');
                return;
            }
            const dryRunLabel = opts.dryRun === true ? ' (dry-run)' : '';
            process.stdout.write(`olam flywheel migrate-overlays --push → ${opts.target}${dryRunLabel}\n`);
            if (opts.dryRun !== true) {
                process.stdout.write(`  branch:  ${summary.branchName}\n`);
                process.stdout.write(`  copied:  ${summary.filesCopied}\n`);
                process.stdout.write(`  created: ${summary.filesCreated}\n`);
                process.stdout.write(`  unchanged: ${summary.filesUnchanged}\n`);
                if (summary._copyPasteBlock !== undefined) {
                    process.stdout.write(summary._copyPasteBlock);
                }
            }
            return;
        }
        // Rewrite mode
        const root = opts.targetDir ?? join(homedir(), '.claude');
        const summary = migrateOverlays(opts);
        if (opts.json === true) {
            process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
            return;
        }
        const dryRunSuffix = opts.dryRun === true ? ' (dry-run)' : '';
        process.stdout.write(`olam flywheel migrate-overlays → ${root}${dryRunSuffix}\n`);
        process.stdout.write(`  scanned: ${summary.scanned} file(s)\n`);
        process.stdout.write(`  modified: ${summary.modified}\n`);
        process.stdout.write(`  unchanged: ${summary.unchanged}\n`);
        process.stdout.write(`  total replacements: ${summary.totalReplacements}\n`);
        if (summary.perFile.length > 0) {
            process.stdout.write(`\nPer-file changes:\n`);
            for (const file of summary.perFile) {
                process.stdout.write(`  ${relative(root, file.path)}${dryRunSuffix}\n`);
                for (const r of file.replacements) {
                    process.stdout.write(`    ${r.count}× ${r.basename} → ${r.replacedBy}\n`);
                }
            }
        }
        if (summary.modified === 0) {
            process.stdout.write(`\n[migrate-overlays] no changes needed — all overlays already migrated or no shim-target references found.\n`);
        }
    });
}
//# sourceMappingURL=migrate-overlays.js.map