/**
 * `olam skills migrate-hooks` — forward strip of atlas-toolbox-shipped
 * olam-meta hook entries (Phase C C2).
 *
 *   olam skills migrate-hooks [--dry-run]
 *
 * Detects PreToolUse/PostToolUse matcher entries in ~/.claude/settings.json
 * whose `command` strings reference one of the agentmemory-classifier
 * scripts AND do NOT carry an olam-meta sentinel — i.e. atlas-toolbox-
 * shipped entries from the pre-Phase-B world. Strips them; writes a
 * pre-strip snapshot via the B5 primitive so the operator can undo via
 * `olam skills migrate-hooks-back`.
 *
 * Order of operations (snapshot first, mutation after, audit-log last):
 *   1. acquire settings-json lock (A4 — serializes against concurrent sync)
 *   2. read settings.json (return early if absent — nothing to strip)
 *   3. findStripCandidates(settings)
 *      - empty list → no-op + summary + exit 0
 *   4. --dry-run → print summary + exit 0 (no snapshot, no mutation)
 *   5. writeMetaHooksSnapshot(settings) — same prefix as B5, so
 *      `migrate-hooks-back` consumes it identically
 *   6. applyStrip(settings, candidates) → next settings.json
 *   7. atomic write via temp+rename
 *   8. appendTrustAudit({action: 'meta-hook-stripped', count, ...})
 *      — operator-visible append-only audit trail
 *   9. print summary
 *
 * Idempotent: re-running on already-stripped settings.json is a no-op
 * (findStripCandidates returns []).
 *
 * Reversibility: forward-fix-only. Audit-log entry is append-only by
 * contract; settings.json mutation is reversible via B5 snapshot.
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import pc from 'picocolors';
import { claudeSettingsPath } from '@olam/core/src/skill-sources/index.js';
import { withSettingsJsonLock } from '@olam/core/src/skill-sync/settings-json-lock.js';
import { writeMetaHooksSnapshot } from '@olam/core/src/skill-sources/meta-hooks-migration-snapshot.js';
import { appendTrustAudit } from '@olam/core/src/skill-sources/trust-audit-log.js';
import { AGENTMEMORY_SCRIPT_PATTERNS, OLAM_META_SENTINELS_GUARDED, findStripCandidates, applyStrip, } from '@olam/core/src/skill-sync/atlas-hook-strip.js';
import { printError, printInfo, printSuccess, printWarning } from '../output.js';
/** Stable command name — referenced by tests. */
export const MIGRATE_HOOKS_COMMAND = 'migrate-hooks';
// Re-export the pure strip helpers so the C2 test imports stay anchored
// on the CLI module surface (paths used by operators); the C3 engine
// imports the same names directly from `@olam/core/src/skill-sync/
// atlas-hook-strip.js` to avoid a CLI → core back-edge.
export { AGENTMEMORY_SCRIPT_PATTERNS, OLAM_META_SENTINELS_GUARDED, findStripCandidates, applyStrip, };
/**
 * True iff any hook command in settings.json contains an olam-meta
 * sentinel. Used by the migrate-hooks command to warn when stripping
 * atlas-shipped entries would leave NO olam-managed agentmemory hooks
 * behind (per Phase C CP3 follow-up MEDIUM #3).
 */
function settingsHasOlamMetaSentinel(settings) {
    for (const stage of ['PreToolUse', 'PostToolUse']) {
        const matchers = settings.hooks?.[stage];
        if (!Array.isArray(matchers))
            continue;
        for (const matcher of matchers) {
            const inner = Array.isArray(matcher.hooks) ? matcher.hooks : [];
            for (const h of inner) {
                const cmd = h?.command;
                if (typeof cmd !== 'string')
                    continue;
                for (const sentinel of OLAM_META_SENTINELS_GUARDED) {
                    if (cmd.includes(sentinel))
                        return true;
                }
            }
        }
    }
    return false;
}
function readSettings(filePath) {
    if (!fs.existsSync(filePath))
        return null;
    const raw = fs.readFileSync(filePath, 'utf-8');
    if (raw.trim().length === 0)
        return {};
    return JSON.parse(raw);
}
function writeSettings(filePath, settings) {
    fs.mkdirSync(path.dirname(filePath), { recursive: true });
    const tmp = `${filePath}.tmp-migrate-hooks-${process.pid}-${Date.now()}`;
    fs.writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n');
    fs.renameSync(tmp, filePath);
}
function printSummary(candidates, opts) {
    if (candidates.length === 0) {
        printInfo(MIGRATE_HOOKS_COMMAND, 'no atlas-toolbox-shipped agentmemory hooks found in settings.json — nothing to migrate');
        return;
    }
    const verb = opts.dryRun ? 'would strip' : 'stripped';
    console.log(pc.bold(`${verb} ${candidates.length} atlas-toolbox-shipped agentmemory hook entry(ies):`));
    for (const c of candidates) {
        const matcher = c.matcher ?? '*';
        const cmdPreview = c.command.length > 120 ? `${c.command.slice(0, 117)}...` : c.command;
        console.log(`  ${pc.dim(`[${c.stage}]`)} matcher=${pc.cyan(matcher)} pattern=${pc.yellow(c.matchedPattern)}`);
        console.log(`    ${pc.dim(cmdPreview)}`);
    }
    if (opts.snapshotPath) {
        console.log();
        printInfo('snapshot', opts.snapshotPath);
        printInfo('reverse', `run \`olam skills migrate-hooks-back\` (or \`migrate-hooks-back --snapshot <path>\`) to restore the pre-strip settings.json`);
    }
}
function asMessage(err) {
    return err instanceof Error ? err.message : String(err);
}
export function registerSkillsMigrateHooks(program) {
    const skills = program.commands.find((c) => c.name() === 'skills') ??
        program.command('skills').description('Manage skill sources and synchronization');
    skills
        .command(MIGRATE_HOOKS_COMMAND)
        .description('Strip atlas-toolbox-shipped agentmemory hook entries from ~/.claude/settings.json (forward migration; reverse via migrate-hooks-back)')
        .option('--dry-run', 'Show what would be stripped without mutating settings.json or writing a snapshot')
        .action(async (opts) => {
        try {
            await withSettingsJsonLock(async () => {
                const settingsFile = claudeSettingsPath();
                const settings = readSettings(settingsFile);
                if (settings === null) {
                    printInfo(MIGRATE_HOOKS_COMMAND, `${settingsFile} does not exist — nothing to migrate`);
                    return;
                }
                const candidates = findStripCandidates(settings);
                if (opts.dryRun) {
                    printSummary(candidates, { dryRun: true });
                    return;
                }
                if (candidates.length === 0) {
                    printSummary(candidates, { dryRun: false });
                    return;
                }
                // CP3 follow-up (Phase C adversarial review MEDIUM #3):
                // when stripping would leave NO olam-meta sentinel-bound
                // entries in settings.json, agentmemory recall/classify
                // dies entirely. The likely-but-not-guaranteed cause:
                // operator has been running `olam skills sync
                // --meta-hooks=never` (declined olam ownership) and now
                // ran migrate-hooks not realising it strips
                // unconditionally. Warn loudly — don't gate (the operator
                // who explicitly typed the command unambiguously asked
                // for the strip; --force ceremony would be hostile), but
                // the consequence MUST be visible.
                const hasOlamMetaSentinel = settingsHasOlamMetaSentinel(settings);
                if (!hasOlamMetaSentinel) {
                    printWarning(`stripping ${candidates.length} atlas-toolbox-shipped agentmemory entry(ies) WITHOUT any olam-meta sentinel-bound block present in settings.json. ` +
                        `After the strip, agentmemory recall/classify will NOT fire from this settings.json. ` +
                        `If unintended, reverse with \`olam skills migrate-hooks-back\` and re-sync with \`olam skills sync\` (default mode injects olam-meta blocks when memory-bridge is running).`);
                }
                // Snapshot BEFORE mutation. Reuses B5 prefix so
                // migrate-hooks-back picks it up identically.
                const snapshotPath = writeMetaHooksSnapshot(settings);
                const { nextSettings, removedCount } = applyStrip(settings, candidates);
                writeSettings(settingsFile, nextSettings);
                // Audit-log AFTER the write — entry records what landed,
                // not what was planned. T6 mitigation.
                appendTrustAudit({
                    gitUrl: 'internal:olam-meta-hooks',
                    action: 'meta-hook-stripped',
                    trustMethod: 'none',
                    sourceId: 'atlas-toolbox',
                    note: `removed ${removedCount} agentmemory hook entry(ies); snapshot at ${snapshotPath}`,
                });
                printSummary(candidates, { dryRun: false, snapshotPath });
                printSuccess(`stripped ${removedCount} atlas-toolbox-shipped agentmemory hook entry(ies) from ${settingsFile}`);
            }, { reason: `cli:${MIGRATE_HOOKS_COMMAND}` });
        }
        catch (err) {
            printError(asMessage(err));
            process.exitCode = 1;
        }
    });
}
//# sourceMappingURL=skills-migrate-hooks.js.map