"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.graphHealthRules = void 0;
function diag(rule, filePath, message, opts = {}) {
    return {
        ruleId: rule.id,
        filePath,
        severity: opts.severity || rule.severity,
        message,
        help: opts.help || rule.help,
        autoFixable: opts.autoFixable ?? false,
    };
}
const contextExists = {
    id: "graph/context-exists",
    name: "Living Context file exists",
    category: "node",
    severity: "error",
    description: "Checks that .mycontext/context.json exists and is valid JSON",
    help: "Run `mycontext init` to create the Living Context file",
    appliesTo: ["node", "nextjs", "turbo"],
    async check(ctx) {
        const results = [];
        const contextPath = ".mycontext/context.json";
        const exists = await ctx.fileExists(contextPath);
        if (!exists) {
            results.push(diag(this, contextPath, "Missing .mycontext/context.json", { severity: "error" }));
            return results;
        }
        const data = await ctx.readJson(contextPath);
        if (!data) {
            results.push(diag(this, contextPath, "context.json is not valid JSON", { severity: "error" }));
            return results;
        }
        return results;
    },
};
const featureNodesPresent = {
    id: "graph/feature-nodes",
    name: "Feature Graph nodes defined",
    category: "node",
    severity: "warning",
    description: "Checks that at least one feature is defined in the context",
    help: "Define features in context.json under the features[] array. Run `mycontext feature:plan` to get started.",
    appliesTo: ["node", "nextjs", "turbo"],
    async check(ctx) {
        const results = [];
        const contextPath = ".mycontext/context.json";
        const data = await ctx.readJson(contextPath);
        if (!data)
            return results;
        const features = data.features;
        if (!features || !Array.isArray(features) || features.length === 0) {
            results.push(diag(this, contextPath, "No features defined in context.json", { severity: "warning" }));
            return results;
        }
        for (const feature of features) {
            if (!feature.id) {
                results.push(diag(this, contextPath, `Feature missing 'id' field: ${JSON.stringify(feature)}`));
            }
            if (!feature.name) {
                results.push(diag(this, contextPath, `Feature ${feature.id || "(no id)"} missing 'name' field`));
            }
            if (!feature.description) {
                results.push(diag(this, contextPath, `Feature ${feature.id} missing 'description' field`));
            }
        }
        return results;
    },
};
const dependencyDAGValid = {
    id: "graph/dependency-dag",
    name: "Feature dependency DAG is valid",
    category: "node",
    severity: "error",
    description: "Checks for circular dependencies between features",
    help: "Ensure no feature depends on another that directly or indirectly depends on it. Use `mycontext feature:graph` to visualize.",
    appliesTo: ["node", "nextjs", "turbo"],
    async check(ctx) {
        const results = [];
        const data = await ctx.readJson(".mycontext/context.json");
        if (!data?.features)
            return results;
        const features = data.features;
        const depMap = new Map();
        for (const f of features) {
            depMap.set(f.id, f.dependencies || []);
        }
        const visited = new Set();
        const inStack = new Set();
        function detectCycle(node, path) {
            if (inStack.has(node)) {
                const cycleStart = path.indexOf(node);
                return path.slice(cycleStart).concat(node);
            }
            if (visited.has(node))
                return null;
            visited.add(node);
            inStack.add(node);
            path.push(node);
            const deps = depMap.get(node) || [];
            for (const dep of deps) {
                if (depMap.has(dep)) {
                    const cycle = detectCycle(dep, path);
                    if (cycle)
                        return cycle;
                }
            }
            path.pop();
            inStack.delete(node);
            return null;
        }
        for (const f of features) {
            const cycle = detectCycle(f.id, []);
            if (cycle) {
                results.push(diag(this, ".mycontext/context.json", `Circular dependency detected: ${cycle.join(" → ")}`, {
                    severity: "error",
                }));
            }
        }
        if (results.length === 0 && features.length > 0) {
            const orphaned = features.filter((f) => f.dependencies &&
                f.dependencies.length > 0 &&
                f.dependencies.some((d) => !depMap.has(d)));
            for (const f of orphaned) {
                const missing = f.dependencies.filter((d) => !depMap.has(d));
                results.push(diag(this, ".mycontext/context.json", `Feature "${f.id}" depends on non-existent feature(s): ${missing.join(", ")}`, {
                    severity: "warning",
                    autoFixable: true,
                    help: `Remove ${missing.join(", ")} from ${f.id}'s dependencies or define the missing features`,
                }));
            }
        }
        return results;
    },
};
const questionHealth = {
    id: "graph/question-blockers",
    name: "Question Graph health",
    category: "node",
    severity: "warning",
    description: "Checks for unanswered blocking questions in context.json",
    help: "Answer blocking questions with `mycontext questions:answer <id> <value>` before proceeding",
    appliesTo: ["node", "nextjs", "turbo"],
    async check(ctx) {
        const results = [];
        const data = await ctx.readJson(".mycontext/context.json");
        if (!data?.brain?.context?.questions)
            return results;
        const questions = data.brain.context.questions;
        if (!Array.isArray(questions))
            return results;
        const unansweredBlockers = questions.filter((q) => q.isBlocking && (!q.answer || q.answer.trim() === ""));
        for (const q of unansweredBlockers) {
            results.push(diag(this, ".mycontext/context.json", `Blocking question unanswered: "${q.question || q.text || q.id}"`, {
                severity: "warning",
                autoFixable: false,
                help: `Run \`mycontext questions:answer ${q.id} "<your answer>"\``,
            }));
        }
        return results;
    },
};
const implGraphHealth = {
    id: "graph/impl-completeness",
    name: "Implementation Graph completeness",
    category: "node",
    severity: "warning",
    description: "Checks that completed features have corresponding implementation plans",
    help: "Run `mycontext implementation:plan <feature-id>` for completed features without plans",
    appliesTo: ["node", "nextjs", "turbo"],
    async check(ctx) {
        const results = [];
        const data = await ctx.readJson(".mycontext/context.json");
        if (!data?.features)
            return results;
        const completedFeatures = data.features.filter((f) => f.status === "completed");
        const implData = data.brain?.context?.implGraph;
        for (const f of completedFeatures) {
            const hasImpl = implData?.nodes?.some((n) => n.featureId === f.id);
            if (!hasImpl) {
                results.push(diag(this, ".mycontext/context.json", `Completed feature "${f.id}" has no implementation plan`, {
                    severity: "warning",
                    autoFixable: false,
                    help: `Run \`mycontext implementation:plan ${f.id}\``,
                }));
            }
        }
        return results;
    },
};
exports.graphHealthRules = [
    contextExists,
    featureNodesPresent,
    dependencyDAGValid,
    questionHealth,
    implGraphHealth,
];
//# sourceMappingURL=graph-health-rules.js.map