import { buildIntentFrame } from "./intent-frame.js";
function generateGoalId() {
    return new Date().toISOString().replace(/[:.]/g, "-");
}
function slugifyTitle(title) {
    return title
        .toLowerCase()
        .replace(/[^a-z0-9\s-]/g, "")
        .trim()
        .replace(/\s+/g, "-")
        .slice(0, 40);
}
function deriveTitle(rawPrompt) {
    const firstSentence = rawPrompt.split(/[.!?]/, 1)[0]?.trim() ?? rawPrompt.trim();
    return firstSentence.slice(0, 120);
}
function deriveObjective(rawPrompt) {
    const firstPara = rawPrompt.split(/\n\n/)[0]?.trim() ?? rawPrompt.trim();
    return firstPara;
}
function inferSuccessCriteria(objective) {
    const criteria = [];
    const lowered = objective.toLowerCase();
    // Heuristic patterns for common engineering tasks
    const patterns = [
        {
            trigger: ["add", "implement", "introduce", "create", "build"],
            description: "The new functionality is implemented and integrated into the codebase",
        },
        {
            trigger: ["test", "verify", "validate", "check"],
            description: "Tests pass and the change is verified against acceptance criteria",
        },
        {
            trigger: ["fix", "bug", "issue", "error", "crash"],
            description: "The reported issue is reproduced, fixed, and verified with a regression test",
        },
        {
            trigger: ["refactor", "clean", "restructure"],
            description: "Code is restructured without changing external behavior and all tests pass",
        },
        {
            trigger: ["doc", "document", "readme", "guide"],
            description: "Documentation is updated and accurately reflects the current state",
        },
    ];
    for (const pattern of patterns) {
        if (pattern.trigger.some((t) => lowered.includes(t))) {
            criteria.push({
                id: `criterion-${criteria.length + 1}`,
                description: pattern.description,
                requirement: criteria.length === 0 ? "required" : "optional",
                weight: criteria.length === 0 ? 1.0 : 0.5,
                inferred: true,
            });
        }
    }
    if (criteria.length === 0) {
        criteria.push({
            id: "criterion-1",
            description: "The objective is completed and the result is demonstrable",
            requirement: "required",
            weight: 1.0,
            inferred: true,
        });
    }
    return criteria;
}
function deriveRiskLevel(objective) {
    const lowered = objective.toLowerCase();
    if (lowered.includes("production") ||
        lowered.includes("deploy") ||
        lowered.includes("migrate") ||
        lowered.includes("database") ||
        lowered.includes("security")) {
        return "high";
    }
    if (lowered.includes("refactor") ||
        lowered.includes("clean") ||
        lowered.includes("style") ||
        lowered.includes("format")) {
        return "low";
    }
    return "medium";
}
/**
 * Extract structured fields from a raw goal prompt using regex-based heuristics.
 * Supports Markdown-style headers and labelled sections.
 */
export function normalizeGoalPrompt(rawPrompt) {
    const objective = deriveObjective(rawPrompt);
    // Extract sections by common headers or labels
    const successCriteria = extractListSection(rawPrompt, [
        /(?:^|\n)(?:#{1,3}\s*)?(?:success\s*criteria|acceptance\s*criteria|criteria|성공\s*기준|완료\s*기준|수용\s*기준)(?:\s*[:-])?\s*\n?/i,
        /(?:^|\n)(?:success\s*criteria|acceptance\s*criteria|성공\s*기준|완료\s*기준|수용\s*기준):\s*/i,
    ]);
    const nonGoals = extractListSection(rawPrompt, [
        /(?:^|\n)(?:#{1,3}\s*)?(?:non[-\s]?goals|out of scope|exclusions|비목표|범위\s*제외|제외\s*사항)(?:\s*[:-])?\s*\n?/i,
    ]);
    const risks = extractListSection(rawPrompt, [
        /(?:^|\n)(?:#{1,3}\s*)?(?:risks|risk factors|concerns|위험|리스크|우려\s*사항)(?:\s*[:-])?\s*\n?/i,
    ]);
    const constraints = extractListSection(rawPrompt, [
        /(?:^|\n)(?:#{1,3}\s*)?(?:constraints|limitations|restrictions|제약|제한|필수\s*조건)(?:\s*[:-])?\s*\n?/i,
    ]);
    const expectedArtifacts = extractArtifactSection(rawPrompt);
    const intentFrame = buildIntentFrame(rawPrompt, {
        constraints,
        successCriteria,
        expectedArtifacts,
    });
    return {
        rawPrompt,
        objective,
        successCriteria,
        nonGoals,
        risks,
        expectedArtifacts,
        constraints,
        intentFrame,
    };
}
function extractListSection(text, headerPatterns) {
    for (const pattern of headerPatterns) {
        const match = pattern.exec(text);
        if (match) {
            const start = match.index + match[0].length;
            const remainder = text.slice(start);
            // Capture until next blank line followed by a non-list line, or next header, or end
            const lines = [];
            const lineRe = /.*(?:\n|$)/g;
            lineRe.lastIndex = 0;
            let m;
            while ((m = lineRe.exec(remainder)) !== null) {
                const line = m[0];
                if (/^#{1,3}\s/.test(line) || (lines.length > 0 && /^\s*$/.test(line) && !/^\s*(?:[-*]|\d+[.)])/.test(remainder.slice(lineRe.lastIndex)))) {
                    break;
                }
                const trimmed = line.trim();
                if (trimmed.length === 0)
                    continue;
                // Stop if we hit a line that looks like a new header or non-list after we've started
                if (lines.length > 0 && /^[A-Z][a-zA-Z\s]+[:-]\s*$/.test(trimmed)) {
                    break;
                }
                const listItem = trimmed.replace(/^(?:[-*]|\d+[.)])\s*/, "").trim();
                if (listItem.length > 0) {
                    lines.push(listItem);
                }
                if (lineRe.lastIndex >= remainder.length)
                    break;
            }
            if (lines.length > 0)
                return lines;
        }
    }
    return [];
}
function extractArtifactSection(text) {
    const artifacts = [];
    const headerPatterns = [
        /(?:^|\n)(?:#{1,3}\s*)?(?:artifacts|expected artifacts|deliverables|outputs|산출물|결과물|출력)(?:\s*[:-])?\s*\n?/i,
    ];
    for (const pattern of headerPatterns) {
        const match = pattern.exec(text);
        if (match) {
            const start = match.index + match[0].length;
            const remainder = text.slice(start);
            const lineRe = /.*(?:\n|$)/g;
            lineRe.lastIndex = 0;
            let m;
            while ((m = lineRe.exec(remainder)) !== null) {
                const line = m[0];
                if (/^#{1,3}\s/.test(line))
                    break;
                const trimmed = line.trim();
                if (trimmed.length === 0)
                    continue;
                if (/^[A-Z][a-zA-Z\s]+[:-]\s*$/.test(trimmed))
                    break;
                const cleaned = trimmed.replace(/^(?:[-*]|\d+[.)])\s*/, "").trim();
                // Try to extract a file path like `src/foo.ts` or `path/to/file.md`
                const pathMatch = cleaned.match(/`([^`]+\.[a-zA-Z0-9]+)`/);
                const name = cleaned.split(/[:-]/)[0].trim();
                if (name.length > 0) {
                    artifacts.push({
                        name,
                        path: pathMatch ? pathMatch[1] : undefined,
                    });
                }
                if (lineRe.lastIndex >= remainder.length)
                    break;
            }
            if (artifacts.length > 0)
                return artifacts;
        }
    }
    return artifacts;
}
export function normalizeGoal(input) {
    const now = new Date().toISOString();
    const title = input.title ?? deriveTitle(input.rawPrompt);
    const objective = input.objective ?? deriveObjective(input.rawPrompt);
    const criteria = input.successCriteria ?? inferSuccessCriteria(objective);
    const riskLevel = input.riskLevel ?? deriveRiskLevel(objective);
    const goalId = `${slugifyTitle(title)}-${generateGoalId()}`;
    const constraints = input.constraints ?? [];
    const expectedArtifacts = input.expectedArtifacts ?? [];
    const intentFrame = buildIntentFrame(input.rawPrompt, {
        constraints,
        successCriteria: criteria.map((criterion) => criterion.description),
        expectedArtifacts,
    });
    return {
        schemaVersion: 1,
        goalId,
        title,
        rawPrompt: input.rawPrompt,
        objective,
        successCriteria: criteria.map((c) => ({ ...c, inferred: c.inferred ?? false })),
        constraints: constraints.map((c, i) => ({ id: `constraint-${i + 1}`, description: c })),
        nonGoals: input.nonGoals ?? [],
        risks: input.risks ?? [],
        expectedArtifacts: expectedArtifacts.map((a) => ({ name: a.name, path: a.path })),
        status: "draft",
        riskLevel,
        planRevision: 0,
        createdAt: now,
        updatedAt: now,
        runIds: [],
        intentFrame,
        actionAtoms: intentFrame.actionAtoms,
    };
}
/**
 * Codex-style entrypoint: create a GoalSpec from a raw prompt with structured parsing.
 */
export function createGoalSpec(rawPrompt, overrides) {
    const parsed = normalizeGoalPrompt(rawPrompt);
    const parsedSuccessCriteria = parsed.successCriteria.length > 0
        ? parsed.successCriteria.map((desc, i) => ({
            id: `criterion-${i + 1}`,
            description: desc,
            requirement: i === 0 ? "required" : "optional",
            weight: i === 0 ? 1.0 : 0.5,
            inferred: false,
        }))
        : undefined;
    return normalizeGoal({
        rawPrompt,
        title: overrides?.title ?? deriveTitle(rawPrompt),
        objective: overrides?.objective ?? parsed.objective,
        successCriteria: overrides?.successCriteria ?? parsedSuccessCriteria,
        constraints: overrides?.constraints ?? parsed.constraints,
        nonGoals: overrides?.nonGoals ?? parsed.nonGoals,
        risks: overrides?.risks ?? parsed.risks.map((desc, i) => ({
            id: `risk-${i + 1}`,
            description: desc,
            level: deriveRiskLevel(desc),
        })),
        expectedArtifacts: overrides?.expectedArtifacts ?? parsed.expectedArtifacts,
        riskLevel: overrides?.riskLevel,
    });
}
export function updateGoalStatus(spec, status, options) {
    const updated = {
        ...spec,
        status,
        updatedAt: new Date().toISOString(),
    };
    if (options?.planRevision !== undefined) {
        updated.planRevision = options.planRevision;
    }
    if (options?.runId && !updated.runIds.includes(options.runId)) {
        updated.runIds = [...updated.runIds, options.runId];
    }
    return updated;
}
const TASK_TYPE_RULES = [
    { type: "explore", keywords: ["find", "search", "where", "how does", "what is", "explain", "understand", "map", "discover", "trace", "locate", "browser", "current page", "web page", "active tab", "dom", "chrome", "탐색", "찾기", "어디", "뭔지", "설명"], weight: 1 },
    { type: "implement", keywords: ["add", "implement", "create", "build", "introduce", "support", "feature", "develop", "write", "구현", "추가", "만들기", "작성", "개발"], weight: 1 },
    { type: "bugfix", keywords: ["fix", "bug", "error", "crash", "broken", "issue", "regression", "fails", "not working", "버그", "오류", "수정", "고장", "실패"], weight: 1 },
    { type: "refactor", keywords: ["refactor", "clean", "restructure", "rename", "extract", "simplify", "organize", "move", "리팩토링", "정리", "단순화"], weight: 1 },
    { type: "research", keywords: ["research", "investigate", "compare", "evaluate", "survey", "study", "analyze", "benchmark", "조사", "비교", "분석", "연구"], weight: 1 },
    { type: "review", keywords: ["review", "audit", "check", "look at", "inspect", "assess", "리뷰", "검토", "점검"], weight: 1 },
    { type: "plan", keywords: ["plan", "design", "architecture", "strategy", "roadmap", "structure", "blueprint", "설계", "계획", "구조"], weight: 1 },
    { type: "test", keywords: ["test", "verify", "validate", "coverage", "unit test", "e2e", "integration test", "regression", "테스트", "검증", "커버리지"], weight: 1 },
    { type: "document", keywords: ["doc", "readme", "guide", "document", "changelog", "comment", "wiki", "문서", "가이드", "주석"], weight: 1 },
    { type: "migrate", keywords: ["migrate", "upgrade", "update version", "migration", "bump", "deprecate", "switch to", "move to", "마이그레이션", "업그레이드", "버전업"], weight: 1 },
    { type: "security", keywords: ["security", "secret", "auth", "permission", "vulnerability", "credential", "token", "encrypt", "sanitize", "xss", "csrf", "injection", "보안", "인증", "권한", "취약점"], weight: 1 },
];
const ROLE_TEMPLATES = {
    explore: ["explorer", "researcher"],
    implement: ["planner", "coder", "reviewer", "qa"],
    bugfix: ["explorer", "debugger", "coder", "tester", "reviewer"],
    refactor: ["explorer", "planner", "coder", "qa", "reviewer"],
    research: ["researcher", "explorer", "architect"],
    review: ["reviewer", "architect"],
    plan: ["architect", "planner", "router"],
    test: ["tester", "debugger", "qa"],
    document: ["explorer", "researcher"],
    migrate: ["architect", "planner", "coder", "qa"],
    security: ["security", "reviewer", "architect"],
    general: ["planner", "coder", "reviewer"],
};
const WRITE_KEYWORDS = ["write", "edit", "implement", "fix", "create", "delete", "modify", "add", "build", "change", "update", "patch", "migrate", "refactor", "rename", "move", "extract", "코드작성", "수정", "구현", "추가", "삭제", "변경"];
const RESEARCH_KEYWORDS = ["docs", "official", "paper", "api", "reference", "current", "latest", "version", "release notes", "changelog", "specification", "rfc", "documentation", "browser", "current page", "web page", "active tab", "dom", "chrome", "문서", "공식", "최신", "버전", "스펙"];
const SECURITY_KEYWORDS = ["security", "secret", "auth", "permission", "vulnerability", "credential", "token", "encrypt", "sanitize", "xss", "csrf", "injection", "password", "api key", "보안", "인증", "권한", "취약점", "토큰"];
const RISK_REVIEW_KEYWORDS = ["critical issue", "critical issues", "risk", "risks", "danger", "dangerous", "risky", "위험", "리스크", "크리티컬 이슈", "중대 이슈"];
const READ_ONLY_DIRECTIVES = ["read-only", "read only", "readonly", "no edits", "do not edit", "do not modify", "don't edit", "don't modify", "without editing", "inspect only", "review only", "읽기 전용", "수정 금지", "변경 금지", "편집 금지"];
const TEST_KEYWORDS = ["test", "verify", "validate", "coverage", "unit test", "e2e", "integration", "regression", "jest", "mocha", "pytest", "테스트", "검증", "커버리지"];
const DESIGN_KEYWORDS = ["ui", "ux", "design", "component", "frontend", "react", "vue", "angular", "css", "tailwind", "styled", "screen", "page", "layout", "visual", "brand", "디자인", "컴포넌트", "화면", "프론트"];
const LANGUAGE_FRAMEWORK_KEYWORDS = [
    { id: "typescript", keywords: ["typescript", "ts", ".ts", "tsx", "type"], roles: ["coder"] },
    { id: "python", keywords: ["python", "py", ".py", "django", "flask", "fastapi"], roles: ["coder"] },
    { id: "react", keywords: ["react", "jsx", "tsx", "hook", "component"], roles: ["coder", "designer"] },
    { id: "vue", keywords: ["vue", "nuxt", ".vue"], roles: ["coder", "designer"] },
    { id: "angular", keywords: ["angular", ".component.ts", "ng-"], roles: ["coder", "designer"] },
    { id: "nextjs", keywords: ["next.js", "nextjs", "app router", "pages router"], roles: ["coder"] },
    { id: "nodejs", keywords: ["node.js", "nodejs", "express", "nest"], roles: ["coder"] },
    { id: "go", keywords: ["golang", "go.mod", ".go"], roles: ["coder"] },
    { id: "rust", keywords: ["rust", "cargo", ".rs"], roles: ["coder"] },
];
const TEST_FRAMEWORK_KEYWORDS = [
    { id: "jest", keywords: ["jest", "describe", "it(", "test("] },
    { id: "mocha", keywords: ["mocha", "chai"] },
    { id: "vitest", keywords: ["vitest"] },
    { id: "pytest", keywords: ["pytest", "def test_"] },
    { id: "playwright", keywords: ["playwright", "e2e"] },
    { id: "cypress", keywords: ["cypress"] },
];
function detectLanguageFrameworks(text) {
    const lowered = text.toLowerCase();
    const detected = [];
    for (const fw of LANGUAGE_FRAMEWORK_KEYWORDS) {
        if (fw.keywords.some((kw) => lowered.includes(kw.toLowerCase()))) {
            detected.push({ id: fw.id, roles: fw.roles });
        }
    }
    return detected;
}
function detectTestFrameworks(text) {
    const lowered = text.toLowerCase();
    const detected = [];
    for (const tf of TEST_FRAMEWORK_KEYWORDS) {
        if (tf.keywords.some((kw) => lowered.includes(kw.toLowerCase()))) {
            detected.push(tf.id);
        }
    }
    return detected;
}
function isQuestion(text) {
    const trimmed = text.trim();
    if (trimmed.endsWith("?"))
        return true;
    const questionStarters = [
        "how", "what", "why", "when", "where", "who", "which", "can you", "could you",
        "is there", "are there", "does", "do", "will", "would", "should",
        "어떻게", "뭐", "무엇", "왜", "언제", "어디", "누구", "which", "can you",
    ];
    const firstWords = trimmed.toLowerCase().split(/[\s,]+/).slice(0, 3).join(" ");
    return questionStarters.some((q) => firstWords.startsWith(q.toLowerCase()));
}
function detectDependencyChains(text) {
    const chains = [];
    const patterns = [
        /(\w+)\s+(?:depends? on|requires?|needs?)\s+(\w+)/gi,
        /(?:modify|change|update|fix)\s+(\w+).*?(?:and|as well as)\s+(\w+)/gi,
        /(\w+)\s+(?:and|&)\s+(\w+)\s+(?:should|need|must)/gi,
    ];
    for (const pattern of patterns) {
        let match;
        while ((match = pattern.exec(text)) !== null) {
            chains.push(`${match[1]} -> ${match[2]}`);
        }
    }
    return chains;
}
function scoreTaskTypes(text) {
    const lowered = text.toLowerCase();
    const scores = [];
    for (const rule of TASK_TYPE_RULES) {
        let score = 0;
        for (const kw of rule.keywords) {
            if (lowered.includes(kw.toLowerCase())) {
                score += rule.weight;
            }
        }
        if (score > 0)
            scores.push({ type: rule.type, score });
    }
    scores.sort((a, b) => b.score - a.score);
    return scores;
}
function countFileReferences(text) {
    const patterns = [
        // File paths: src/foo.ts, ./bar.js, path/to/file.md
        /(?:[\w-]+\/)+[\w-]+\.[a-zA-Z0-9]+/g,
        // Quoted file names: `src/foo.ts`, "config.json"
        /[`"']([\w./-]+\.[a-zA-Z0-9]+)[`"']/g,
        // Directory references: src/, lib/, components/
        /\b(?:src|lib|app|components|pages|tests?|specs?|docs?)\/[\w/]+/g,
    ];
    let count = 0;
    for (const pattern of patterns) {
        const matches = text.match(pattern);
        if (matches)
            count += matches.length;
    }
    return count;
}
function countConcepts(text) {
    const lowered = text.toLowerCase();
    const conceptMarkers = ["and", "also", "additionally", "furthermore", "plus", "besides", "moreover", "또한", "그리고", "추가로"];
    let count = 1;
    for (const marker of conceptMarkers) {
        const regex = new RegExp(`\\b${marker}\\b`, "gi");
        const matches = lowered.match(regex);
        if (matches)
            count += matches.length;
    }
    return count;
}
function hasAnyKeyword(text, keywords) {
    const lowered = text.toLowerCase();
    return keywords.some((kw) => lowered.includes(kw.toLowerCase()));
}
function inferComplexity(text) {
    const fileRefs = countFileReferences(text);
    const concepts = countConcepts(text);
    const length = text.length;
    if (text.toLowerCase().includes("architecture") ||
        text.toLowerCase().includes("migrate") ||
        text.toLowerCase().includes("security") ||
        text.toLowerCase().includes("refactor entire") ||
        text.toLowerCase().includes("redesign") ||
        fileRefs >= 6 ||
        (concepts >= 4 && length > 300)) {
        return "complex";
    }
    if (fileRefs >= 3 || concepts >= 3 || length > 150) {
        return "moderate";
    }
    return "simple";
}
function estimateWorkers(intent) {
    if (!intent.parallelizable)
        return 1;
    const base = intent.complexity === "simple" ? 1 :
        intent.complexity === "moderate" ? 2 :
            3;
    // Boost for implementation / refactor / migration with many file references
    if (["implement", "refactor", "migrate", "bugfix"].includes(intent.taskType)) {
        return Math.min(6, base + 1);
    }
    // Research and review tasks can fan out more
    if (["research", "review", "explore"].includes(intent.taskType)) {
        return Math.min(6, base + 2);
    }
    return Math.min(6, base);
}
function isParallelizable(taskType, complexity, text) {
    if (taskType === "explore" || taskType === "research" || taskType === "review")
        return true;
    if (taskType === "implement" && complexity !== "simple")
        return true;
    if (taskType === "refactor" && complexity !== "simple")
        return true;
    if (taskType === "bugfix" && text.toLowerCase().includes("multiple"))
        return true;
    if (taskType === "test" && text.toLowerCase().includes("coverage"))
        return true;
    return false;
}
/**
 * Analyze a raw user prompt to determine intent, complexity, required roles,
 * and optimal parallel distribution strategy.
 */
export function analyzeUserIntent(rawPrompt) {
    const scores = scoreTaskTypes(rawPrompt);
    let taskType = scores[0]?.type ?? "general";
    const complexity = inferComplexity(rawPrompt);
    const explicitReadOnly = hasAnyKeyword(rawPrompt, READ_ONLY_DIRECTIVES);
    const riskReview = hasAnyKeyword(rawPrompt, RISK_REVIEW_KEYWORDS);
    const readOnly = explicitReadOnly || !hasAnyKeyword(rawPrompt, WRITE_KEYWORDS);
    const needsResearch = hasAnyKeyword(rawPrompt, RESEARCH_KEYWORDS);
    const needsSecurityReview = riskReview || hasAnyKeyword(rawPrompt, SECURITY_KEYWORDS);
    const needsTesting = riskReview || hasAnyKeyword(rawPrompt, TEST_KEYWORDS);
    const needsDesignReview = hasAnyKeyword(rawPrompt, DESIGN_KEYWORDS);
    // Override to explore/research if the prompt is clearly a question
    if (isQuestion(rawPrompt) && (taskType === "general" || taskType === "implement")) {
        taskType = needsResearch ? "research" : "explore";
    }
    if (riskReview && (taskType === "general" || taskType === "explore" || taskType === "research")) {
        taskType = "review";
    }
    const parallelizable = isParallelizable(taskType, complexity, rawPrompt);
    const baseRoles = [...(ROLE_TEMPLATES[taskType] ?? ROLE_TEMPLATES.general)];
    if (needsResearch && !baseRoles.includes("researcher")) {
        baseRoles.push("researcher");
    }
    if (needsSecurityReview && !baseRoles.includes("security")) {
        baseRoles.unshift("security");
    }
    if (needsTesting && !baseRoles.includes("tester")) {
        baseRoles.push("tester");
    }
    if (needsDesignReview && !baseRoles.includes("designer")) {
        baseRoles.push("designer");
    }
    // Add language/framework specific roles
    const detectedFrameworks = detectLanguageFrameworks(rawPrompt);
    for (const fw of detectedFrameworks) {
        for (const role of fw.roles) {
            if (!baseRoles.includes(role))
                baseRoles.push(role);
        }
    }
    // Deduplicate while preserving order
    const requiredRoles = baseRoles.filter((r, i) => baseRoles.indexOf(r) === i);
    const detectedTests = detectTestFrameworks(rawPrompt);
    const depChains = detectDependencyChains(rawPrompt);
    const rationaleParts = [
        `taskType=${taskType} (score=${scores[0]?.score ?? 0})`,
        `complexity=${complexity}`,
        `readOnly=${readOnly}`,
        `parallelizable=${parallelizable}`,
        `roles=[${requiredRoles.join(", ")}]`,
    ];
    if (needsResearch)
        rationaleParts.push("needsResearch");
    if (needsSecurityReview)
        rationaleParts.push("needsSecurityReview");
    if (needsTesting)
        rationaleParts.push("needsTesting");
    if (needsDesignReview)
        rationaleParts.push("needsDesignReview");
    if (detectedFrameworks.length > 0)
        rationaleParts.push(`frameworks=[${detectedFrameworks.map((f) => f.id).join(", ")}]`);
    if (detectedTests.length > 0)
        rationaleParts.push(`testFrameworks=[${detectedTests.join(", ")}]`);
    if (depChains.length > 0)
        rationaleParts.push(`deps=${depChains.length}`);
    if (isQuestion(rawPrompt))
        rationaleParts.push("isQuestion");
    const intent = {
        taskType,
        complexity,
        estimatedWorkers: 0, // filled below
        requiredRoles,
        isReadOnly: readOnly,
        needsResearch,
        needsSecurityReview,
        needsTesting,
        needsDesignReview,
        parallelizable,
        rationale: rationaleParts.join("; "),
    };
    intent.estimatedWorkers = estimateWorkers(intent);
    return intent;
}
