// tools/housekeeping-sweep.ts — SPEC-751 / SPEC-755
// MCP tool handler for housekeeping_sweep.
import { z } from 'zod';
import { runHousekeepingSweep } from '../engine/housekeeping/index.js';
import { cleanOrphanBrainstormingMarkdowns } from '../engine/housekeeping/orphan-brainstorming-cleaner.js';
import { cleanEphemeralArtifacts } from '../engine/housekeeping/ephemeral-artifacts-cleaner.js';
import { hashProjectPath } from '../storage/base-store.js';
import { specStore } from '../storage/index.js';
// ---------------------------------------------------------------------------
// Input schema
// ---------------------------------------------------------------------------
export const HousekeepingSweepInputSchema = z.object({
    projectPath: z
        .string()
        .optional()
        .describe('Absolute path to the project root (auto-detected if omitted)'),
    dryRun: z
        .boolean()
        .optional()
        .default(true)
        .describe('When true (default), only report — no deletions performed'),
    aggressive: z
        .boolean()
        .optional()
        .default(false)
        .describe('When true, skip the 1-hour recent-reflog safety guard'),
    includeStashes: z
        .boolean()
        .optional()
        .default(false)
        .describe('When true, also consider stashes older than 30 days for deletion'),
});
function formatOrphanSection(orphan) {
    return [
        ``,
        `ORPHAN BRAINSTORMING MARKDOWNS  ${String(orphan.scanned)} scanned`,
        `  matched+deleted: ${String(orphan.deletedMatched)}`,
        `  imported+deleted: ${String(orphan.importedThenDeleted)}`,
        `  skipped: ${String(orphan.skipped)}`,
    ].join('\n');
}
function formatEphemeralSection(ep) {
    const action = ep.deleted > 0 ? 'deleted' : ep.wouldDelete.length > 0 ? 'would delete' : 'none';
    const lines = [
        ``,
        `EPHEMERAL SPEC ARTIFACTS  ${String(ep.found)} found`,
        `  ${action}: ${String(ep.deleted > 0 ? ep.deleted : ep.wouldDelete.length)}`,
    ];
    for (const p of ep.wouldDelete) {
        lines.push(`    ${p.split('/planu/specs/')[1] ?? p}`);
    }
    if (ep.errors.length > 0) {
        lines.push(`  errors: ${ep.errors.join(', ')}`);
    }
    return lines.join('\n');
}
function formatReport(report, orphan) {
    const mode = report.executed ? 'EXECUTED' : 'DRY-RUN';
    const lines = [
        `Housekeeping Sweep — ${mode}`,
        `━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
        `Scanned: ${report.projectPath}`,
        ``,
        `STALE BRANCHES  ${String(report.wouldDelete.branches.length)} found`,
    ];
    for (const b of report.wouldDelete.branches) {
        const status = report.executed ? '✓ deleted' : 'would delete';
        lines.push(`  ${status}  ${b.name}  (${b.sha.slice(0, 8)})`);
    }
    lines.push(``, `STALE WORKTREES  ${String(report.wouldDelete.worktrees.length)} found`);
    for (const wt of report.wouldDelete.worktrees) {
        const status = report.executed ? '✓ removed' : 'would remove';
        lines.push(`  ${status}  ${wt.path}`);
    }
    lines.push(``, `STALE STASHES  ${String(report.wouldDelete.stashes.length)} found`);
    for (const st of report.wouldDelete.stashes) {
        const status = report.executed ? '✓ dropped' : 'would drop';
        lines.push(`  ${status}  ${st.ref}  [${st.reason}]  ${String(st.ageDays)}d  ${st.message}`);
    }
    if (report.kept.branches.length > 0) {
        lines.push(``, `KEPT BRANCHES  ${String(report.kept.branches.length)}`);
        for (const kb of report.kept.branches) {
            lines.push(`  kept  ${kb.name}  reason: ${kb.skipReason}`);
        }
    }
    if (report.executed) {
        lines.push(``, `DELETED  branches: ${String(report.deleted.branches)}  worktrees: ${String(report.deleted.worktrees)}  stashes: ${String(report.deleted.stashes)}`);
    }
    else {
        lines.push(``, `Run with dryRun: false to execute deletions.`, `Recovery: git update-ref refs/heads/<name> <sha>  (backed up to planu/.housekeeping-history.jsonl)`);
    }
    if (orphan && orphan.scanned > 0) {
        lines.push(formatOrphanSection(orphan));
    }
    return lines.join('\n');
}
function formatReportWithEphemeral(report, orphan, ephemeral) {
    const base = formatReport(report, orphan);
    if (ephemeral && ephemeral.found > 0) {
        return base + formatEphemeralSection(ephemeral);
    }
    return base;
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
export async function handleHousekeepingSweep(args, resolvedProjectPath) {
    const projectPath = args.projectPath ?? resolvedProjectPath;
    const dryRun = args.dryRun ?? true;
    const report = await runHousekeepingSweep({
        projectPath,
        dryRun,
        aggressive: args.aggressive ?? false,
        includeStashes: args.includeStashes ?? false,
    });
    // SPEC-755: Scan and clean orphan brainstorming markdowns from docs/superpowers/specs/
    let orphanSummary = null;
    try {
        const projectId = hashProjectPath(projectPath);
        const orphanResult = await cleanOrphanBrainstormingMarkdowns({
            projectPath,
            projectId,
            dryRun,
            listSpecs: (pid) => specStore.listSpecs(pid),
        });
        if (orphanResult.scanned > 0) {
            orphanSummary = {
                scanned: orphanResult.scanned,
                deletedMatched: orphanResult.entries.filter((e) => e.action === 'deleted_matched').length,
                importedThenDeleted: orphanResult.entries.filter((e) => e.action === 'imported_then_deleted').length,
                skipped: orphanResult.entries.filter((e) => e.action === 'skipped').length,
            };
        }
    }
    catch {
        /* best-effort — never block housekeeping_sweep */
    }
    // SPEC-762: Scan and clean ephemeral spec artifacts from done/discarded specs
    let ephemeralResult = null;
    try {
        ephemeralResult = await cleanEphemeralArtifacts({ projectPath, dryRun });
    }
    catch {
        /* best-effort — never block housekeeping_sweep */
    }
    let strictPlanuCleanup = null;
    try {
        const { runStrictPlanuCleanup, validateStrictPlanuLayout } = await import('../engine/spec-migrator/index.js');
        if (!dryRun) {
            const cleanup = await runStrictPlanuCleanup(projectPath);
            strictPlanuCleanup = { deleted: cleanup.deleted, merged: cleanup.merged, offenders: [] };
        }
        else {
            const validation = await validateStrictPlanuLayout(projectPath);
            strictPlanuCleanup = { deleted: [], merged: [], offenders: validation.offenders };
        }
    }
    catch {
        /* best-effort — never block housekeeping_sweep */
    }
    // SPEC-1012: Detect and auto-fix specs released on main but still in non-terminal status
    let releaseStatusDrift = null;
    try {
        const { detectReleaseStatusDrift, applyReleaseStatusDriftFix } = await import('../engine/drift-detection/release-status-drift.js');
        const driftReports = await detectReleaseStatusDrift(projectPath);
        let fixed = 0;
        if (!dryRun) {
            for (const driftItem of driftReports) {
                try {
                    await applyReleaseStatusDriftFix(driftItem, projectPath);
                    fixed++;
                }
                catch {
                    /* best-effort — continue to next */
                }
            }
        }
        if (driftReports.length > 0) {
            releaseStatusDrift = { detected: driftReports.length, fixed };
        }
    }
    catch {
        /* best-effort — never block housekeeping_sweep */
    }
    const text = formatReportWithEphemeral(report, orphanSummary, ephemeralResult);
    return {
        content: [{ type: 'text', text }],
        structuredContent: {
            sweepAt: report.sweepAt,
            projectPath: report.projectPath,
            executed: report.executed,
            wouldDelete: {
                branches: report.wouldDelete.branches.length,
                worktrees: report.wouldDelete.worktrees.length,
                stashes: report.wouldDelete.stashes.length,
            },
            deleted: report.deleted,
            kept: {
                branches: report.kept.branches.length,
            },
            details: report,
            ...(orphanSummary ? { orphanBrainstorming: orphanSummary } : {}),
            ...(ephemeralResult ? { ephemeralArtifacts: ephemeralResult } : {}),
            ...(strictPlanuCleanup ? { strictPlanuCleanup } : {}),
            ...(releaseStatusDrift ? { releaseStatusDrift } : {}),
        },
    };
}
//# sourceMappingURL=housekeeping-sweep.js.map