// engine/dep-guard/cycle-detector.ts — SPEC-728: DepGuard pre-approve cycle blocker.
// Wraps spec-dependency-graph.ts to provide a typed gate result.
import { buildSpecDependencyGraph, detectCycles } from '../spec-dependency-graph.js';
function mapToGraphSpec(s) {
    return {
        id: s.id,
        title: s.id,
        status: 'draft',
        dependencies: s.dependencies,
        estimation: { devHours: 0, reviewHours: 0, totalCostUsd: 0 },
        type: 'feature',
        target: 'backend',
        scope: 'feature',
        difficulty: 1,
        risk: 'low',
        tags: [],
        createdAt: '',
        updatedAt: '',
        spec_format_version: '1.0',
        spec_version: '1.0.0',
        blockedBy: [],
        history: [],
    };
}
/**
 * SPEC-728: Pre-approve gate that blocks if the spec participates in a dependency cycle.
 *
 * Checks in order:
 *  1. Self-dependency (spec.dependencies.includes(spec.id))
 *  2. Cycles detected by detectCycles() that include spec.id
 *  3. Orphan dependencies (deps pointing to non-existing spec ids) — warn only, never block
 *
 * @param spec      The spec being transitioned to 'approved'.
 * @param allSpecs  All specs in the project (used to build the full graph).
 * @returns DepGuardResult — blocked: true means the transition is rejected.
 */
export function checkApprovedDepGate(spec, allSpecs) {
    const warnings = [];
    // 1. Self-dependency check
    if (spec.dependencies.includes(spec.id)) {
        return {
            blocked: true,
            code: 'SELF_DEPENDENCY',
            cyclePath: [spec.id, spec.id],
            warnings,
            narrative: `Spec ${spec.id} cannot depend on itself. Remove the self-reference from the dependencies field.`,
        };
    }
    // 2. Orphan dependency detection — warn, do not block
    const allSpecIds = new Set(allSpecs.map((s) => s.id));
    for (const depId of spec.dependencies) {
        if (!allSpecIds.has(depId)) {
            warnings.push({ code: 'ORPHAN_DEPENDENCY', missingId: depId });
        }
    }
    // 3. Cycle detection using existing graph engine
    // Build graph from allSpecs (which already includes spec if it's in the list)
    const specsForGraph = allSpecs.map((s) => mapToGraphSpec(s));
    // If spec is not already in allSpecs, add it for cycle detection
    if (!allSpecIds.has(spec.id)) {
        specsForGraph.push(mapToGraphSpec(spec));
    }
    const graph = buildSpecDependencyGraph(specsForGraph);
    const cycles = detectCycles(graph);
    // Filter cycles that contain the spec being approved
    const relevantCycles = cycles.filter((cycle) => cycle.includes(spec.id));
    if (relevantCycles.length > 0) {
        const cycle = relevantCycles[0] ?? [];
        const cycleArrow = cycle.join(' -> ');
        return {
            blocked: true,
            code: 'DEPENDENCY_CYCLE',
            cyclePath: cycle,
            warnings,
            narrative: `Cycle detected: ${cycleArrow}. Break the cycle by removing one edge from the dependencies field.`,
        };
    }
    return {
        blocked: false,
        warnings,
        narrative: 'No dependency cycles detected.',
    };
}
//# sourceMappingURL=cycle-detector.js.map