// engine/autopilot/action-registry.ts — SPEC-454: Maps trigger actions to real handler functions
// Handlers are lazy-imported to avoid circular dependencies and keep startup fast.
// Only actions with exported engine functions are registered here.
// Actions without handlers (inline in registration files) are logged but not executed.
const ACTION_TIMEOUT_MS = 30_000;
async function withActionTimeout(fn, ms) {
    let timer;
    const timeout = new Promise((_resolve, reject) => {
        timer = setTimeout(() => {
            reject(new Error(`Action timed out after ${ms}ms`));
        }, ms);
    });
    try {
        return await Promise.race([fn(), timeout]);
    }
    finally {
        clearTimeout(timer);
    }
}
function wrapHandler(name, fn) {
    return async (ctx) => {
        const start = Date.now();
        try {
            const result = await withActionTimeout(() => fn(ctx), ACTION_TIMEOUT_MS);
            return { ...result, durationMs: Date.now() - start };
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            return {
                success: false,
                summary: `${name} failed: ${msg}`,
                durationMs: Date.now() - start,
            };
        }
    };
}
/** Safely access optional numeric/array properties from unknown results */
function safeCount(obj, key) {
    const val = obj[key];
    if (typeof val === 'number') {
        return val;
    }
    if (Array.isArray(val)) {
        return val.length;
    }
    return 0;
}
/** Extract a short summary from a ToolResult */
function extractToolSummary(result) {
    const first = result.content[0];
    if (first?.type !== 'text') {
        return result.isError === true ? 'Error' : 'Completed';
    }
    return first.text.length > 200 ? `${first.text.slice(0, 197)}...` : first.text;
}
/**
 * SPEC-456: Complete registry of action handlers.
 * Each key matches a trigger rule's `action` field.
 * Lazy-imports prevent circular deps and speed up startup.
 */
const ACTION_HANDLERS = {
    // --- spec:created triggers ---
    validate_criteria_quality: wrapHandler('validate_criteria_quality', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { lintSpec } = await import('../ears/spec-linter.js');
        const report = await lintSpec(ctx.specId, ctx.projectPath);
        const r = report;
        const grade = typeof r.grade === 'string' ? r.grade : 'N/A';
        const issueCount = typeof r.issueCount === 'number' ? r.issueCount : 0;
        return {
            success: true,
            summary: `Criteria quality: grade ${grade}, ${String(issueCount)} issues`,
            durationMs: 0,
        };
    }),
    suggest_criteria: wrapHandler('suggest_criteria', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleSuggestCriteria } = await import('../../tools/domain-criteria-handler.js');
        const result = await handleSuggestCriteria({
            projectId: ctx.projectId,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    detect_contradictions: wrapHandler('detect_contradictions', async (ctx) => {
        const { specStore } = await import('../../storage/index.js');
        const { detectContradictions } = await import('../contradiction-detector.js');
        const specs = await specStore.listSpecs(ctx.projectId);
        const inputs = specs.map((s) => ({
            id: s.id,
            title: s.title,
            content: s.title,
        }));
        const contradictions = detectContradictions(inputs);
        const filtered = ctx.specId
            ? contradictions.filter((c) => c.specA === ctx.specId || c.specB === ctx.specId)
            : contradictions;
        const blockers = filtered.filter((c) => c.severity === 'blocker').length;
        const warnings = filtered.filter((c) => c.severity === 'warning').length;
        return {
            success: true,
            summary: `Contradictions: ${String(blockers)} blockers, ${String(warnings)} warnings`,
            durationMs: 0,
        };
    }),
    // --- spec:status:approved triggers ---
    challenge_spec: wrapHandler('challenge_spec', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleChallengeSpec } = await import('../../tools/challenge-spec.js');
        const result = await handleChallengeSpec({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    spec_quality_score: wrapHandler('spec_quality_score', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleSpecQualityScore } = await import('../../tools/spec-quality-score-handler.js');
        const result = await handleSpecQualityScore({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    generate_orchestration_plan: wrapHandler('generate_orchestration_plan', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleOrchestrateAgents } = await import('../../tools/orchestrate-agents-handler.js');
        const result = await handleOrchestrateAgents({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    // --- spec:status:implementing triggers ---
    tdd_scaffold: wrapHandler('tdd_scaffold', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        // TDD scaffold needs specPath — resolve from specId
        const { specStore } = await import('../../storage/index.js');
        const spec = await specStore.getSpec(ctx.projectId, ctx.specId);
        if (!spec?.specPath) {
            return { success: false, summary: `Spec ${ctx.specId} has no specPath`, durationMs: 0 };
        }
        const { handleTddScaffold } = await import('../../tools/tdd-scaffold-handler.js');
        const result = await handleTddScaffold({
            projectPath: ctx.projectPath,
            specPath: spec.specPath,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    // --- spec:status:done triggers ---
    auto_fix_health: wrapHandler('auto_fix_health', async (ctx) => {
        const { autoFixHealth } = await import('../health/auto-fixer.js');
        const result = (await autoFixHealth(ctx.projectPath, ctx.projectId));
        const fixed = safeCount(result, 'fixedCount');
        return { success: true, summary: `Auto-fixed ${fixed} health issues`, durationMs: 0 };
    }),
    scan_crash_risks: wrapHandler('scan_crash_risks', async (ctx) => {
        const { scanCrashRisks } = await import('../crash-shield/index.js');
        const result = (await scanCrashRisks(ctx.projectPath));
        const score = typeof result.score === 'number' ? result.score : 0;
        const risks = safeCount(result, 'risks');
        return {
            success: true,
            summary: `Crash scan: score ${score}/100, ${risks} risks`,
            durationMs: 0,
        };
    }),
    generate_changelog: wrapHandler('generate_changelog', async (ctx) => {
        const { specStore } = await import('../../storage/index.js');
        const specs = await specStore.listSpecs(ctx.projectId);
        const { generateChangelog } = await import('../doc-generator/support-generators.js');
        const doc = generateChangelog(specs, 'en');
        // Generated changelog is returned to callers; strict Planu mode forbids planu/CHANGELOG.md.
        const { writeFile, mkdir } = await import('node:fs/promises');
        const { join } = await import('node:path');
        const { projectDataDir } = await import('../../storage/base-store.js');
        const changelogPath = join(projectDataDir(ctx.projectId), 'generated', 'CHANGELOG.md');
        await mkdir(join(projectDataDir(ctx.projectId), 'generated'), { recursive: true });
        await writeFile(changelogPath, doc.content, 'utf-8');
        return {
            success: true,
            summary: `Changelog written to external Planu project data (${doc.content.length} chars)`,
            durationMs: 0,
        };
    }),
    // --- drift:detected triggers ---
    resolve_drift_violations: wrapHandler('resolve_drift_violations', async (ctx) => {
        const { resolveDriftViolations } = await import('../drift/violation-resolver.js');
        const result = (await resolveDriftViolations(ctx.projectPath, ctx.projectId));
        const resolved = safeCount(result, 'resolvedCount');
        return {
            success: true,
            summary: `Resolved ${resolved} drift violations`,
            durationMs: 0,
        };
    }),
    log_lesson: wrapHandler('log_lesson', async (ctx) => {
        const { addLesson } = await import('../../storage/lessons-store.js');
        const lesson = await addLesson(ctx.projectPath, {
            projectId: ctx.projectId,
            specId: ctx.specId,
            category: 'process',
            severity: 'low',
            title: 'Drift detected',
            description: `Spec drift was detected${ctx.specId !== undefined ? ` for ${ctx.specId}` : ''}.`,
            prevention: 'Review spec file alignment with implementation after each commit.',
            tags: ['drift', 'autopilot'],
        });
        return {
            success: true,
            summary: `Lesson persisted: ${lesson.id}`,
            durationMs: 0,
        };
    }),
    // ── B.2: spec:created triggers ──────────────────────────────────────────────
    heal_spec_docs: wrapHandler('heal_spec_docs', async (ctx) => {
        const { handleHealSpecDocs } = await import('../../tools/heal-spec-docs.js');
        // SPEC-579: pass specId so cascade-triggered healing scopes to the changing
        // spec only, instead of mass-rewriting all technical.md files in the project.
        const result = await handleHealSpecDocs({
            projectPath: ctx.projectPath,
            ...(ctx.specId !== undefined && ctx.specId !== '' ? { specId: ctx.specId } : {}),
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    similar_problems_finder: wrapHandler('similar_problems_finder', async (ctx) => {
        const { handleSimilarProblemsFinder } = await import('../../tools/knowledge-base-handler.js');
        const result = await handleSimilarProblemsFinder({
            projectPath: ctx.projectPath,
            query: ctx.specId ?? '',
            limit: 5,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    analyze_spec_size: wrapHandler('analyze_spec_size', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { specStore } = await import('../../storage/index.js');
        const spec = await specStore.getSpec(ctx.projectId, ctx.specId);
        if (!spec) {
            return { success: false, summary: `Spec ${ctx.specId} not found`, durationMs: 0 };
        }
        const { analyzeSpecSize } = await import('../spec-splitter.js');
        const analysis = analyzeSpecSize(spec);
        const suggestion = analysis.shouldSplit ? ' — consider splitting' : '';
        return {
            success: true,
            summary: `Spec size: ${analysis.estimatedHours}h, ${analysis.criteriaCount} criteria${suggestion}`,
            durationMs: 0,
        };
    }),
    run_spec_lint: wrapHandler('run_spec_lint', async (ctx) => {
        const { handleRunSpecLint } = await import('../../tools/spec-lint-handler.js');
        const result = await handleRunSpecLint({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    ears_lint: wrapHandler('ears_lint', async (ctx) => {
        const { lintAllSpecs } = await import('../ears/spec-linter.js');
        const report = await lintAllSpecs(ctx.projectPath);
        const grade = report.averageScore >= 9
            ? 'A'
            : report.averageScore >= 7
                ? 'B'
                : report.averageScore >= 5
                    ? 'C'
                    : 'D';
        return {
            success: true,
            summary: `EARS lint: grade ${grade}, avg ${report.averageScore.toFixed(1)}/10 (${report.totalSpecs} specs)`,
            durationMs: 0,
        };
    }),
    // ── B.2: spec:status:approved triggers ───────────────────────────────────────
    spec_health_check: wrapHandler('spec_health_check', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleSpecHealthCheck } = await import('../../tools/validation-loop-handler.js');
        const result = await handleSpecHealthCheck({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    check_parallel_safety: wrapHandler('check_parallel_safety', async (ctx) => {
        const { handleCheckParallelSafety } = await import('../../tools/parallel-safety-handler.js');
        const result = handleCheckParallelSafety({ projectPath: ctx.projectPath });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    // ── B.2: spec:status:done triggers ───────────────────────────────────────────
    auto_fix_validation: wrapHandler('auto_fix_validation', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleAutoFixValidation } = await import('../../tools/auto-fix-validation.js');
        const result = await handleAutoFixValidation({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
            failureType: 'test',
            failureDetails: 'Post-done autopilot validation check',
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    calibrate_estimates: wrapHandler('calibrate_estimates', async (ctx) => {
        const { handleCalibrateEstimates } = await import('../../tools/estimation-accuracy-handler.js');
        const result = await handleCalibrateEstimates({
            projectPath: ctx.projectPath,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    coverage_gap_analyzer: wrapHandler('coverage_gap_analyzer', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { specStore } = await import('../../storage/index.js');
        const spec = await specStore.getSpec(ctx.projectId, ctx.specId);
        const specPath = spec?.specPath ?? ctx.specId;
        const { analyzeCoverageGaps } = await import('../coverage-gap-analyzer.js');
        const report = await analyzeCoverageGaps(specPath, ctx.projectPath);
        return {
            success: true,
            summary: `Coverage gaps: ${report.gaps.length} uncovered out of ${report.totalCriteria} criteria (score: ${report.coverageScore}/100)`,
            durationMs: 0,
        };
    }),
    // ── B.2: spec:status:implementing triggers ───────────────────────────────────
    generate_edge_tests: wrapHandler('generate_edge_tests', async (ctx) => {
        if (!ctx.specId) {
            return { success: false, summary: 'No specId', durationMs: 0 };
        }
        const { handleGenerateEdgeTests } = await import('../../tools/tdd-handler.js');
        const result = await handleGenerateEdgeTests({
            projectPath: ctx.projectPath,
            specId: ctx.specId,
        });
        return {
            success: result.isError !== true,
            summary: extractToolSummary(result),
            durationMs: 0,
        };
    }),
    // ── B.2: spec:status:implementing triggers ───────────────────────────────────
    // ── B.2: remaining handlers (delegated to handlers-b2.ts) ────────────────────
    /**
     * inject_criteria: Appends GIVEN/WHEN/THEN stubs when spec has fewer than 3
     * acceptance criteria. Fire-and-forget — writes directly to spec.md.
     */
    inject_criteria: wrapHandler('inject_criteria', async (ctx) => {
        const { injectCriteria } = await import('./handlers-b2.js');
        return injectCriteria(ctx);
    }),
    /**
     * rewrite_criteria_ears: Rewrites non-EARS criteria to EARS format in-place.
     * Already-compliant lines are preserved unchanged.
     */
    rewrite_criteria_ears: wrapHandler('rewrite_criteria_ears', async (ctx) => {
        const { rewriteCriteriaEars } = await import('./handlers-b2.js');
        return rewriteCriteriaEars(ctx);
    }),
    /**
     * verify_spec_compliance: Checks spec.md criteria, technical.md file refs,
     * and that referenced files exist on disk. Read-only — returns findings.
     */
    verify_spec_compliance: wrapHandler('verify_spec_compliance', async (ctx) => {
        const { verifySpecCompliance } = await import('./handlers-b2.js');
        return verifySpecCompliance(ctx);
    }),
    /**
     * analyze_code_impact: Counts and categorizes files from technical.md.
     * Flags specs with >10 affected files as HIGH IMPACT. Read-only.
     */
    analyze_code_impact: wrapHandler('analyze_code_impact', async (ctx) => {
        const { analyzeCodeImpact } = await import('./handlers-b2.js');
        return analyzeCodeImpact(ctx);
    }),
};
/**
 * Execute an action by name. Returns null if no handler is registered.
 */
export async function executeAction(actionName, event) {
    const handler = ACTION_HANDLERS[actionName];
    if (!handler) {
        return null;
    }
    return handler({
        projectPath: event.projectPath,
        projectId: event.projectId,
        specId: event.specId,
        payload: event.payload,
    });
}
/** Check if an action has a registered handler */
export function hasActionHandler(actionName) {
    return actionName in ACTION_HANDLERS;
}
//# sourceMappingURL=action-registry.js.map