import { checkGate } from '../engine/clarification-gate/gate.js';
import { upsertToken, hashQuestions } from '../engine/clarification-gate/token-store.js';
import { ti } from '../i18n/index.js';
import { knowledgeStore, specStore } from '../storage/index.js';
import { readTechnologySelectionContract } from '../storage/technology-selection-store.js';
import { formatSuccess, addNextSteps, toolResult, interactiveResult } from './response-helpers.js';
import { writeFile, mkdir, rm, readFile, stat as fsStat } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import { join as pathJoin } from 'node:path';
import { estimateSpec } from '../engine/estimator.js';
import { checkSpecReadiness } from '../engine/readiness-checker.js';
import { buildSpecContext, buildSplitResult } from './create-spec/spec-builder.js';
import { validateConstitution } from './create-spec/constitution-validator.js';
import { setupGitBranch, checkContradictions, fireSpecCreatedHook, generatePostCreationSuggestions, formatPostCreationSuggestion, runAutopilotAsync, getAsyncAnalysisPath, } from './create-spec/post-creation.js';
import { notifyStoreChange } from '../engine/doc-generator/portal/regen-hook.js';
import { compactObj } from '../engine/compact-obj.js';
import { buildCreateSpecSummary } from '../engine/human-summary.js';
import { runAutoPostCreatePipeline } from './create-spec/auto-pipeline.js';
import { extractCriteria, generateLeanSpecContent, } from '../engine/spec-format/lean-spec-generator.js';
import { generateLeanTechnicalContent, } from '../engine/spec-format/lean-technical-generator.js';
import { extractFilesFromSpecBody } from '../engine/spec-format/technical-md-populator.js';
import { buildUnifiedSpecContent } from '../engine/spec-format/unified-spec-builder.js';
import { appendImplementationContractIfMissing } from '../engine/implementation-contract/index.js';
import { validateEnglishOnlySpecText } from '../engine/spec-language/english-only.js';
import { ApiKeyResolver, FallbackGenerator, OpusGenerator, } from '../engine/spec-generator/index.js';
import { analyzeProjectForSpec, getEmptyAutopilotResult, } from './create-spec/autopilot-analyzer.js';
import { AutopilotSummaryCollector } from '../engine/autopilot/summary-collector.js';
import { trackCost } from '../engine/cost-tracking/operation-tracker.js';
import { analyzeSimplicity } from '../engine/simplicity-detector.js';
import { withBudget, unwrapBudget, withTotalBudget } from '../engine/timing/budget.js';
import { measureStep } from '../engine/timing/structured-log.js';
import { resolveProjectIdOrAutoDetect } from './resolve-project-id.js';
import { hashProjectPath } from '../storage/base-store.js';
import { findSimilarSpecs } from '../engine/spec-searcher.js';
import { scoreSpecQuality } from '../engine/spec-quality-scorer.js';
import { enrichEstimationWithVelocity } from './create-spec/adapters/velocity-estimation.js';
import { enrichEstimationWithCalibration } from './create-spec/adapters/calibration-estimation.js';
import { runPriorDecisionsHint } from './create-spec/adapters/prior-decisions-hint.js';
import { suggestOutOfScope } from '../engine/scope-boundaries/index.js';
import { adviseSimilarSpecs } from '../engine/complexity-budget/index.js';
import { issuePlannerToken } from '../engine/reviewer-tokens/issuer.js';
import { generateInteractiveQuestions } from './create-spec/question-generator.js';
import { buildCriterionGroundingRecords, filterGroundedCriteria, getAdvisoryCriteria, getContractCriteria, } from '../engine/spec-grounding/contract.js';
import { checkGenericSpecOutput } from '../engine/spec-quality/generic-output-gate.js';
import { calculateActionableSpecMetrics, shouldExposeMetric, } from '../engine/spec-metrics/actionable-metrics.js';
/** SPEC-584: Persist a clarification token when interactive questions are emitted. Best-effort. */
async function persistClarificationToken(earlyReturn, projectId, toolName) {
    try {
        const rawQuestions = earlyReturn.structuredContent?.interactiveQuestions;
        if (!Array.isArray(rawQuestions) || rawQuestions.length === 0) {
            return;
        }
        const emittedQuestions = rawQuestions;
        await upsertToken({
            toolName,
            projectId,
            questionsHash: hashQuestions(emittedQuestions),
            emittedAt: new Date().toISOString(),
            questions: emittedQuestions,
        });
    }
    catch {
        /* best-effort — token persistence should not block the response */
    }
}
/** SPEC-584: Run the clarification gate check for a tool call. Returns error ToolResult or null. */
async function runClarificationGate(projectPath, toolName, answers) {
    const gateResult = await checkGate({
        projectId: hashProjectPath(projectPath),
        toolName,
        clarificationAnswers: answers,
    });
    return gateResult.allow ? null : gateResult.error;
}
function handleClarification(_server, description, knowledge, autopilot, params) {
    if (!autopilot.needsClarification) {
        return null;
    }
    // If the LLM already gathered answers from the user, skip re-asking and proceed.
    const hasAnswers = params.clarificationAnswers !== undefined &&
        Object.keys(params.clarificationAnswers).length > 0;
    if (hasAnswers) {
        return null;
    }
    const generatedQuestions = generateInteractiveQuestions(description, knowledge);
    const questions = generatedQuestions.length > 0
        ? generatedQuestions
        : [
            {
                question: 'What specific behavior should this spec deliver?',
                header: 'Scope',
                options: [
                    {
                        label: 'User workflow (Recommended)',
                        description: 'Focus the spec on the end-to-end user behavior and completion state.',
                    },
                    {
                        label: 'Backend behavior',
                        description: 'Focus the spec on API, persistence, jobs, or integration behavior.',
                    },
                    {
                        label: 'Cleanup/fix',
                        description: 'Focus the spec on removing stale behavior or fixing a known bug.',
                    },
                ],
                multiSelect: false,
            },
        ];
    return {
        earlyReturn: interactiveResult(questions, 'The description needs more detail before a spec can be created. Answer the questions, then retry create_spec with clarificationAnswers or an enriched description.', 'universal'),
    };
}
function hasFileEntries(files) {
    return files.create.length + files.modify.length + files.test.length > 0;
}
async function resolveTechnicalFiles(input) {
    const generatedSource = [input.generatedTechnicalSection, input.generatedSpecBody]
        .filter((part) => part.trim().length > 0)
        .join('\n\n');
    const extracted = await extractFilesFromSpecBody(generatedSource, input.projectPath);
    if (extracted !== null && hasFileEntries(extracted)) {
        return extracted;
    }
    if (input.fallbackReason !== undefined && hasFileEntries(input.autopilot.suggestedFiles)) {
        return input.autopilot.suggestedFiles;
    }
    return { create: [], modify: [], test: [] };
}
async function groundTechnicalFiles(input) {
    const userInput = input.userInput.toLowerCase();
    const autopilotPaths = new Set([
        ...input.autopilot.suggestedFiles.modify.map((file) => file.path),
        ...input.autopilot.suggestedFiles.create.map((file) => file.path),
        ...input.autopilot.suggestedFiles.test.map((file) => file.path),
    ]);
    const grounded = {
        create: [],
        modify: [],
        test: [],
    };
    const records = [];
    const advisoryFiles = [];
    for (const section of ['create', 'modify', 'test']) {
        for (const file of input.files[section]) {
            const pathInUserInput = userInput.includes(file.path.toLowerCase());
            const exists = await pathExists(pathJoin(input.projectPath, file.path));
            const fromAutopilot = autopilotPaths.has(file.path);
            const derivedTest = section === 'test' && isDerivedFromGroundedSource(file.path, input.files.modify);
            if (pathInUserInput || exists || fromAutopilot || derivedTest) {
                grounded[section].push(file);
                records.push({
                    path: file.path,
                    section,
                    source: pathInUserInput ? 'user_input' : 'project_evidence',
                    evidence: [
                        ...(pathInUserInput ? ['create_spec.description'] : []),
                        ...(exists ? [`file:${file.path}`] : []),
                        ...(fromAutopilot ? ['autopilot.suggestedFiles'] : []),
                        ...(derivedTest ? ['derived-test-path'] : []),
                    ],
                    confidence: pathInUserInput || exists ? 'high' : 'medium',
                });
            }
            else {
                advisoryFiles.push(file);
            }
        }
    }
    return { files: grounded, records, advisoryFiles };
}
function isDerivedFromGroundedSource(testPath, modifyFiles) {
    return modifyFiles.some((file) => {
        const withoutSrc = file.path.startsWith('src/') ? file.path.slice(4) : file.path;
        const ext = withoutSrc.replace(/^.*(\.[^.]+)$/, '$1');
        const base = withoutSrc.slice(0, -ext.length);
        return testPath === `tests/${base}.test${ext}` || testPath === `tests/${withoutSrc}`;
    });
}
async function pathExists(path) {
    try {
        const fs = await import('node:fs/promises');
        if (typeof fs.stat !== 'function') {
            return false;
        }
        await fs.stat(path);
        return true;
    }
    catch {
        return false;
    }
}
const HIGH_RISK_WARNING = 'High-risk spec — consider: ' +
    '1) Is the approach technically feasible within the project constraints? ' +
    '2) What edge cases (network failure, concurrent writes, empty state) could break this? ' +
    '3) Which existing specs or external services does this depend on?';
async function runQualityScore(spec) {
    try {
        const report = await scoreSpecQuality(spec);
        return {
            total: report.score.total,
            grade: report.score.grade,
            completeness: report.score.completeness,
            testability: report.score.testability,
            ambiguity: report.score.ambiguity,
            risk: report.score.risk,
        };
    }
    catch {
        /* best-effort — never block spec creation */
        return null;
    }
}
function runSimplicityCheck(text, hours) {
    try {
        return analyzeSimplicity(text, hours);
    }
    catch {
        return null;
    }
}
function makeAdvisorySignal(args) {
    return { ...args, persistable: false };
}
function buildAdvisoryCompat() {
    return {
        legacyTopLevelAdvisoryFields: true,
        deprecatedAfter: 'vNext+2',
        canonicalField: 'advisorySignals',
    };
}
/** SPEC-612: resolve outOfScope items (provided by user, or auto-suggested from description). */
function resolveOutOfScope(description, provided) {
    if (provided !== undefined && provided.length > 0) {
        return { items: provided, message: null };
    }
    try {
        const auto = suggestOutOfScope(description).allSuggestions.slice(0, 3);
        if (auto.length > 0) {
            return {
                items: auto,
                message: `Auto-suggested outOfScope items based on description keywords: ${auto.join(', ')}`,
            };
        }
    }
    catch {
        /* best-effort */
    }
    return { items: [], message: null };
}
/** SPEC-614 AC4: suggest complexity category based on similar historical specs (best-effort). */
async function runComplexityAdvice(projectId, currentSpecId, tags, targetCriteriaCount) {
    try {
        const allSpecs = await specStore.listSpecs(projectId);
        const historicals = allSpecs
            .filter((s) => s.id !== currentSpecId)
            .slice(-200)
            .map((s) => ({
            specId: s.id,
            criteriaCount: s.reviewNotes?.length ?? 0,
            filesCount: s.impactAnalysis?.affectedFiles.length ?? 0,
            estimatedHours: s.estimation.devHours,
            actualHours: s.actuals?.devHours,
            tags: s.tags,
        }));
        const advice = adviseSimilarSpecs(targetCriteriaCount, tags, historicals);
        if (advice.similarSpecsCount === 0) {
            return null;
        }
        return {
            suggestedCategory: advice.suggestedCategory,
            reasoning: advice.reasoning,
            similarSpecsCount: advice.similarSpecsCount,
        };
    }
    catch {
        return null;
    }
}
/** SPEC-783: Synthesize agent team findings into unified spec.md ## Technical section. */
async function handleAgentTeamSynthesis(specId, projectPath, findings) {
    try {
        const { synthesizeFindings } = await import('../engine/agent-team-planner/findings-synthesizer.js');
        const { glob } = await import('glob');
        const { join } = await import('node:path');
        // SPEC-783: Detect legacy technical.md and fold it into spec.md first (migration)
        const legacyPattern = join(projectPath, 'planu/specs', `${specId}-*`, 'technical.md');
        const [legacyTechnicalPath] = await glob(legacyPattern);
        if (legacyTechnicalPath) {
            const { foldTechnicalIntoSpec } = await import('../engine/spec-migrator/fold-technical.js');
            const specDir = join(legacyTechnicalPath, '..');
            await foldTechnicalIntoSpec(specDir, projectPath);
        }
        // SPEC-783: Operate exclusively on unified spec.md
        const globPattern = join(projectPath, 'planu/specs', `${specId}-*`, 'spec.md');
        const [specPath] = await glob(globPattern);
        if (!specPath) {
            return {
                content: [
                    {
                        type: 'text',
                        text: `Spec ${specId} not found. Tried: ${globPattern}`,
                    },
                ],
                isError: true,
            };
        }
        const specContent = await readFile(specPath, 'utf8');
        // SPEC-783 AC-2/AC-3: Extract ## Technical body or append fresh section
        const technicalMatch = /\n## Technical\n([\s\S]*)$/.exec(specContent);
        let updatedContent;
        if (technicalMatch?.[1] !== undefined) {
            // Has ## Technical section: synthesize into existing body
            const originalSection = technicalMatch[1];
            const synthesized = synthesizeFindings(originalSection, findings);
            updatedContent = specContent.replace(/\n## Technical\n[\s\S]*$/, `\n## Technical\n${synthesized}`);
        }
        else {
            // No ## Technical section: append fresh one
            const synthesized = synthesizeFindings('', findings);
            updatedContent = `${specContent.trimEnd()}\n\n## Technical\n\n${synthesized}\n`;
        }
        await writeFile(specPath, updatedContent, 'utf8');
        return {
            content: [
                {
                    type: 'text',
                    text: `Agent team findings synthesized into ${specId} spec.md ## Technical section (${String(findings.length)} experts contributed).`,
                },
            ],
        };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        return { content: [{ type: 'text', text: `Synthesis failed: ${msg}` }], isError: true };
    }
}
/** SPEC-770: Derive idempotency key from title and projectPath. */
function computeIdempotencyKey(title, projectPath) {
    return createHash('sha256').update(`${title}::${projectPath}`).digest('hex');
}
/**
 * SPEC-781: Check if async analysis has completed for a given spec.
 * Returns { pending: true } when external analysis state is absent.
 */
async function checkAnalysisStatus(projectPath, specId) {
    try {
        await readFile(getAsyncAnalysisPath(projectPath, specId), 'utf-8');
        return { pending: false };
    }
    catch {
        return { pending: true };
    }
}
const FRONTMATTER_KEY_RE = /^idempotencyKey:\s*(\S+)/m;
const FRONTMATTER_ID_RE = /^id:\s*(SPEC-\d+)/m;
const FRONTMATTER_TITLE_RE = /^title:\s*"?([^"\n]+?)"?\s*$/m;
const FRONTMATTER_STATUS_RE = /^status:\s*(\S+)/m;
/** SPEC-770: Scan store and filesystem for a spec with matching idempotency key within windowMs. */
async function findByIdempotencyKey(projectPath, key, projectId, windowMs = 10 * 60 * 1000) {
    const cutoff = Date.now() - windowMs;
    // Fast path: check store (always authoritative for successfully created specs)
    try {
        const existing = await specStore.listSpecs(projectId);
        const match = existing.find((s) => s.idempotencyKey === key && new Date(s.createdAt).getTime() > cutoff);
        if (match !== undefined) {
            return match;
        }
    }
    catch {
        /* best-effort */
    }
    // Fallback: scan frontmatter in spec.md files (catches specs written before store update)
    try {
        const { glob } = await import('glob');
        const { join: joinPath } = await import('node:path');
        const specFiles = await glob(joinPath(projectPath, 'planu/specs/*/spec.md'));
        for (const specFile of specFiles) {
            const fileStat = await fsStat(specFile).catch(() => null);
            if (fileStat === null || fileStat.mtimeMs < cutoff) {
                continue;
            }
            const content = await readFile(specFile, 'utf-8').catch(() => '');
            const keyMatch = FRONTMATTER_KEY_RE.exec(content);
            if (keyMatch?.[1] !== key) {
                continue;
            }
            const idMatch = FRONTMATTER_ID_RE.exec(content);
            const titleMatch = FRONTMATTER_TITLE_RE.exec(content);
            const statusMatch = FRONTMATTER_STATUS_RE.exec(content);
            if (idMatch?.[1] !== undefined) {
                const specStatus = statusMatch?.[1] ?? 'draft';
                return {
                    id: idMatch[1],
                    title: titleMatch?.[1] ?? '',
                    status: specStatus,
                };
            }
        }
    }
    catch {
        /* best-effort */
    }
    return undefined;
}
/** SPEC-770: Scan for recently written spec.md files (for possibleDuplicates in timeout response). */
async function findRecentlyWrittenSpecs(projectPath, windowMs) {
    try {
        const { glob } = await import('glob');
        const { join: joinPath } = await import('node:path');
        const cutoff = Date.now() - windowMs;
        const specFiles = await glob(joinPath(projectPath, 'planu/specs/*/spec.md'));
        const results = [];
        for (const specFile of specFiles) {
            const fileStat = await fsStat(specFile).catch(() => null);
            if (fileStat === null || fileStat.mtimeMs < cutoff) {
                continue;
            }
            const content = await readFile(specFile, 'utf-8').catch(() => '');
            const idMatch = FRONTMATTER_ID_RE.exec(content);
            const titleMatch = FRONTMATTER_TITLE_RE.exec(content);
            if (idMatch?.[1] !== undefined) {
                results.push({ id: idMatch[1], title: titleMatch?.[1] ?? '' });
            }
        }
        return results;
    }
    catch {
        return [];
    }
}
const DESCRIPTION_MAX_CHARS = 10_000;
const DESCRIPTION_EXCEED_MSG = 'Description exceeds 10000 chars. Create with a summary (<3k) and use reconcile_spec to add the rest.';
function checkEnglishOnlyInput(params) {
    const validation = validateEnglishOnlySpecText(`${params.title}\n\n${params.description}`);
    if (validation.ok) {
        return null;
    }
    return {
        content: [
            {
                type: 'text',
                text: `English-only spec gate blocked create_spec.\n\n${validation.reason ?? 'Non-English prose detected.'}\n\n` +
                    'Rewrite the title and description in English, then call create_spec again. User-facing responses may be localized, but spec.md must be English.',
            },
        ],
        isError: true,
        structuredContent: {
            error: 'SPEC_LANGUAGE_GATE_BLOCKED',
            detectedLanguage: validation.detectedLanguage,
            signals: validation.signals,
            fixHint: 'Rewrite title and description in English before creating the spec.',
        },
    };
}
// eslint-disable-next-line max-lines-per-function
export async function handleCreateSpec(inputParams, server) {
    // SPEC-781: Friendly error for description exceeding 10000 chars (replaces cryptic Zod error)
    if (inputParams.description && inputParams.description.length > DESCRIPTION_MAX_CHARS) {
        return {
            content: [{ type: 'text', text: DESCRIPTION_EXCEED_MSG }],
            isError: true,
            structuredContent: {
                error: 'DESCRIPTION_TOO_LONG',
                maxChars: DESCRIPTION_MAX_CHARS,
                actualChars: inputParams.description.length,
                fixHint: 'Create with a summary description under 3000 chars, then append detailed context through reconcile_spec.',
            },
        };
    }
    // SPEC-509: resolve projectPath from git root when omitted
    const resolved = await resolveProjectIdOrAutoDetect({ projectPath: inputParams.projectPath });
    if (!resolved.ok) {
        return resolved.errorResult;
    }
    const resolvedPath = resolved.projectPath || inputParams.projectPath;
    if (!resolvedPath) {
        return {
            content: [
                {
                    type: 'text',
                    text: 'Could not determine projectPath. Run init_project first or provide projectPath explicitly.',
                },
            ],
            isError: true,
        };
    }
    const resolvedInputParams = {
        ...inputParams,
        projectPath: resolvedPath,
    };
    // SPEC-697: Agent team findings synthesis path — early return before full spec creation
    if (inputParams.agentTeamFindings &&
        inputParams.agentTeamFindings.length > 0 &&
        inputParams.specId) {
        return handleAgentTeamSynthesis(inputParams.specId, resolvedPath, inputParams.agentTeamFindings);
    }
    const languageGate = checkEnglishOnlyInput(resolvedInputParams);
    if (languageGate) {
        return languageGate;
    }
    // SPEC-665: Fire-and-forget TTL refresh for conventions.json (never blocks create_spec)
    void import('../engine/conventions-ttl.js').then(({ triggerConventionsTtlRefresh }) => {
        void triggerConventionsTtlRefresh(resolvedPath).catch(() => {
            /* best-effort */
        });
    });
    // SPEC-584: Gate check — block if pending clarification token exists and no answers provided
    const gateError = await runClarificationGate(resolvedPath, 'create_spec', inputParams.clarificationAnswers);
    if (gateError) {
        return gateError;
    }
    // SPEC-770: Idempotency check — return existing spec if same title+projectPath within 10 minutes
    const idempotencyKey = computeIdempotencyKey(inputParams.title, resolvedPath);
    const existingByKey = await findByIdempotencyKey(resolvedPath, idempotencyKey, hashProjectPath(resolvedPath));
    if (existingByKey) {
        // SPEC-781: Check if async analysis has completed in external project data.
        const analysisStatus = await checkAnalysisStatus(resolvedPath, existingByKey.id);
        return {
            content: [
                {
                    type: 'text',
                    text: `spec already exists: ${existingByKey.id} — returning existing spec to avoid duplicate.\n\nTitle: ${existingByKey.title}\nStatus: ${existingByKey.status}\n\nNo new spec was created.`,
                },
            ],
            structuredContent: {
                specId: existingByKey.id,
                title: existingByKey.title,
                status: existingByKey.status,
                message: `spec already exists: ${existingByKey.id}`,
                idempotencyMatch: true,
                pendingAnalysis: analysisStatus.pending,
                analysisComplete: !analysisStatus.pending,
            },
        };
    }
    // eslint-disable-next-line max-lines-per-function, complexity -- orchestrator: one sequential gate + response-build block per lifecycle step; splitting requires threading 15+ shared variables
    return trackCost(resolvedInputParams.projectPath ?? '', 'create_spec', async () => {
        // Allow internal re-enrichment without mutating function parameter
        let params = resolvedInputParams;
        const { description, clarificationSessionId } = params;
        try {
            // SPEC-713: Phase 1 — Critical path: build + persist spec within 25s hard ceiling.
            // Only the steps required to produce spec.md on disk are inside this budget.
            // Post-creation enrichment (git, contradictions, quality scores, etc.) runs outside.
            const criticalResult = await withTotalBudget('create_spec-critical', 25_000, async () => {
                // Build spec context (ID, estimation, diagrams, scope filters)
                const buildResult = await measureStep('buildSpecContext', () => buildSpecContext(params));
                if (!buildResult.ok) {
                    return {
                        ok: false,
                        earlyReturn: {
                            content: [{ type: 'text', text: buildResult.errorMessage }],
                            isError: true,
                        },
                    };
                }
                const { spec, specDir, specPath, technicalPath, estimation, splitSuggestion, duplicate, projectId, agentTeamPlan, } = buildResult.context;
                // Load project knowledge and clarification session
                const knowledge = await measureStep('loadKnowledge', () => knowledgeStore.getKnowledge(projectId));
                const clarificationSession = clarificationSessionId
                    ? await knowledgeStore.getClarification(projectId, clarificationSessionId)
                    : null;
                // Validate against Constitution
                const constitutionCheck = await measureStep('validateConstitution', () => validateConstitution(projectId, spec.title, spec.tags));
                if (constitutionCheck.blocked && constitutionCheck.errorResult) {
                    return { ok: false, earlyReturn: constitutionCheck.errorResult };
                }
                // SPEC-461 Phase 3: Autopilot — analyze project to enrich spec (best-effort)
                // SPEC-560: Wrapped with 5s timeout to prevent hangs on large projects (2000+ files)
                // SPEC-713: measureStep for Phase 0 diagnostic; withBudget enforces 5s ceiling
                const autopilotResult = await withBudget('autopilot-analyzer', 5_000, () => measureStep('autopilot-analyzer', () => analyzeProjectForSpec(params.projectPath ?? '', description, spec.title, knowledge)));
                const autopilot = unwrapBudget(autopilotResult, getEmptyAutopilotResult());
                // If very vague (<10 words), capture as idea instead of spec
                if (autopilot.isIdea) {
                    return {
                        ok: false,
                        earlyReturn: {
                            content: [
                                {
                                    type: 'text',
                                    text: `💡 **Idea captured**: "${description}"\n\nThe description is too brief for a full spec. Use \`capture_idea\` to save it to the backlog, or provide more detail (>10 words) to create a spec.`,
                                },
                            ],
                        },
                    };
                }
                // SPEC-463 / SPEC-471: Interactive clarification
                const clarificationResult = handleClarification(server, description, knowledge, autopilot, params);
                if (clarificationResult !== null) {
                    if ('earlyReturn' in clarificationResult) {
                        // SPEC-584: Persist token so the gate blocks retries without answers
                        await persistClarificationToken(clarificationResult.earlyReturn, projectId, 'create_spec');
                        return { ok: false, earlyReturn: clarificationResult.earlyReturn };
                    }
                    params = clarificationResult.params;
                }
                // Create spec directory and write lean files (SPEC-461)
                await measureStep('mkdir-specDir', () => mkdir(specDir, { recursive: true }));
                const filteredCriteria = filterGroundedCriteria(autopilot.suggestedCriteria);
                const technologyContract = await readTechnologySelectionContract(params.projectPath ?? '');
                const contractNote = technologyContract
                    ? [
                        '',
                        'Technology Contract:',
                        `- Mode: ${technologyContract.mode}`,
                        technologyContract.language ? `- Language: ${technologyContract.language}` : '',
                        technologyContract.framework
                            ? `- Framework: ${technologyContract.framework}`
                            : '- Framework: none or not selected',
                        technologyContract.platform ? `- Platform: ${technologyContract.platform}` : '',
                        technologyContract.projectType
                            ? `- Project type: ${technologyContract.projectType}`
                            : '',
                        '- Agents must not choose a different stack unless the user approves a new contract.',
                    ]
                        .filter(Boolean)
                        .join('\n')
                    : '';
                const anthropicKey = await new ApiKeyResolver().resolveAnthropicKey(params.projectPath ?? '');
                const specGenerator = anthropicKey !== undefined
                    ? new OpusGenerator({ apiKey: anthropicKey })
                    : new FallbackGenerator();
                const generatedSpec = await measureStep('generateSpecBody', () => specGenerator.generate({
                    title: spec.title,
                    description: `${description}${contractNote}`,
                    type: spec.type,
                    scope: spec.scope,
                    target: spec.target,
                    acFormat: params.acFormat,
                    projectContext: {
                        language: technologyContract?.language ?? knowledge?.language ?? undefined,
                        framework: technologyContract?.framework ?? knowledge?.framework ?? undefined,
                        architecture: knowledge?.architecture.primary ?? undefined,
                    },
                }));
                spec.generatedWithModel = generatedSpec.generatedWithModel;
                spec.generatedAt = generatedSpec.generatedAt;
                spec.qualityWarnings = generatedSpec.qualityWarnings;
                const baseCriteria = extractCriteria(generatedSpec.specBody).map((criterion) => criterion.text);
                const groundingCriteria = buildCriterionGroundingRecords({
                    criteria: [...baseCriteria, ...filteredCriteria],
                    userInput: description,
                    projectEvidence: [
                        ...autopilot.detectedPatterns.map((pattern) => `detected-pattern:${pattern}`),
                        ...autopilot.suggestedFiles.modify.map((file) => `file:${file.path}`),
                        ...autopilot.suggestedFiles.create.map((file) => `file:${file.path}`),
                        ...autopilot.suggestedFiles.test.map((file) => `file:${file.path}`),
                    ],
                    generatedEvidence: [generatedSpec.generatedWithModel],
                });
                const contractCriteria = getContractCriteria(groundingCriteria);
                const advisoryCriteria = getAdvisoryCriteria(groundingCriteria).map((record) => record.text);
                const actionableMetrics = calculateActionableSpecMetrics({
                    criteria: [...baseCriteria, ...filteredCriteria],
                    groundingRecords: groundingCriteria,
                }).filter(shouldExposeMetric);
                const technicalFiles = await measureStep('resolveTechnicalFiles', () => resolveTechnicalFiles({
                    generatedSpecBody: generatedSpec.specBody,
                    generatedTechnicalSection: generatedSpec.technicalSection,
                    projectPath: params.projectPath ?? '',
                    autopilot,
                    fallbackReason: generatedSpec.fallbackReason,
                }));
                const groundedTechnical = await measureStep('groundTechnicalFiles', () => groundTechnicalFiles({
                    files: technicalFiles,
                    projectPath: params.projectPath ?? '',
                    userInput: description,
                    autopilot,
                }));
                const outOfScopeResolved = resolveOutOfScope(description, params.outOfScope);
                const scenarioTestPaths = groundedTechnical.files.test.map((file) => file.path);
                const leanSpec = generateLeanSpecContent({
                    spec,
                    description: generatedSpec.specBody,
                    estimation,
                    criteriaOverride: contractCriteria.map((record) => ({
                        text: record.text,
                        done: false,
                    })),
                    groundingCriteria,
                    groundingTechnicalReferences: groundedTechnical.records,
                    acFormat: params.acFormat,
                    scenarioTestPaths,
                });
                const leanTechnical = generateLeanTechnicalContent({
                    specId: spec.id,
                    filesToCreate: groundedTechnical.files.create,
                    filesToModify: groundedTechnical.files.modify,
                    filesToTest: groundedTechnical.files.test,
                });
                // SPEC-709: write unified spec.md from origin — no separate technical.md.
                // The legacy two-file output is preserved by appending the technical body
                // as a `## Technical` section inside spec.md.
                const unifiedWithoutContract = buildUnifiedSpecContent(leanSpec, leanTechnical);
                const unifiedSpec = appendImplementationContractIfMissing(unifiedWithoutContract, {
                    description: generatedSpec.specBody,
                    criteria: contractCriteria.map((record) => ({ text: record.text, done: false })),
                    files: groundedTechnical.files,
                    outOfScope: outOfScopeResolved.items,
                    verificationCommands: [
                        ...(scenarioTestPaths.length > 0
                            ? [`pnpm vitest run ${scenarioTestPaths.join(' ')}`]
                            : []),
                        'pnpm typecheck',
                        'pnpm lint',
                        'pnpm test',
                    ],
                });
                const genericOutputGate = checkGenericSpecOutput(unifiedSpec);
                if (!genericOutputGate.passed) {
                    return {
                        ok: false,
                        earlyReturn: {
                            content: [
                                {
                                    type: 'text',
                                    text: 'Spec quality gate blocked generic output before persistence. ' +
                                        genericOutputGate.issues
                                            .slice(0, 3)
                                            .map((issue) => `${issue.phrase}: ${issue.reason}`)
                                            .join('; '),
                                },
                            ],
                            isError: true,
                            structuredContent: {
                                error: 'GENERIC_SPEC_OUTPUT_BLOCKED',
                                sddConstitutionRuleId: 'sdd.no-generic-output',
                                issues: genericOutputGate.issues,
                                fixHint: 'Replace generic criteria or placeholder references with grounded, testable behavior.',
                            },
                        },
                    };
                }
                try {
                    // SPEC-713: measure file write — this is the critical persistence step
                    await measureStep('writeFile-specPath', () => writeFile(specPath, unifiedSpec, 'utf-8'));
                    // SPEC-709: technical.md no longer written — content lives inside spec.md.
                    // SPEC-461: No progress.md, no HTML reports.
                }
                catch (writeErr) {
                    // Clean up partial files to avoid orphaned spec directories
                    await rm(specDir, { recursive: true, force: true });
                    throw writeErr;
                }
                // SPEC-612: Auto-suggest outOfScope when user did not provide any
                if (outOfScopeResolved.items.length > 0) {
                    spec.outOfScope = outOfScopeResolved.items;
                }
                const outOfScopeSuggestionMsg = outOfScopeResolved.message;
                // Persist spec in storage
                // SPEC-713: measure specStore.createSpec — file lock + JSON rewrite
                await measureStep('specStore-createSpec', () => specStore.createSpec(projectId, spec));
                return {
                    ok: true,
                    data: {
                        spec,
                        specDir,
                        specPath,
                        technicalPath,
                        estimation,
                        splitSuggestion,
                        duplicate,
                        projectId,
                        agentTeamPlan,
                        knowledge,
                        clarificationSession,
                        constitutionCheck,
                        autopilot,
                        filteredCriteria,
                        advisoryCriteria: [
                            ...advisoryCriteria,
                            ...groundedTechnical.advisoryFiles.map((file) => file.path),
                        ],
                        actionableMetrics,
                        outOfScopeSuggestionMsg,
                    },
                };
            }); // end withTotalBudget critical path
            // SPEC-713: Handle total budget exceeded — return clear error before 60s MCP timeout
            if (criticalResult.timedOut) {
                // SPEC-770: Wait briefly for any in-flight spec write to complete, then scan for duplicates
                await new Promise((resolve) => setTimeout(resolve, 500));
                const recentSpecs = await findRecentlyWrittenSpecs(resolvedPath, 2 * 60 * 1000);
                const possibleDuplicates = recentSpecs.length > 0 ? recentSpecs : undefined;
                const dupNote = possibleDuplicates
                    ? `\n\nA spec may have been created server-side: ${possibleDuplicates.map((s) => s.id).join(', ')}. Retry with the same title to return it instead of creating a duplicate.`
                    : '\n\nThe spec may not have been persisted. Check `planu/specs/` and retry.';
                return {
                    content: [
                        {
                            type: 'text',
                            text: `❌ create_spec exceeded 25s internal budget.\n\n${criticalResult.warning}${dupNote}`,
                        },
                    ],
                    isError: true,
                    ...(possibleDuplicates ? { structuredContent: { possibleDuplicates } } : {}),
                };
            }
            if (!criticalResult.value.ok) {
                return criticalResult.value.earlyReturn;
            }
            // Destructure critical path results for use in post-creation enrichment
            const { spec, specDir: _specDir, specPath, 
            // SPEC-1010 Bug A: no longer surfaced in the response payload (SSR back-migration).
            technicalPath: _technicalPath, estimation, splitSuggestion, duplicate, projectId, agentTeamPlan, knowledge, clarificationSession, constitutionCheck, autopilot, filteredCriteria, advisoryCriteria, actionableMetrics, outOfScopeSuggestionMsg, } = criticalResult.value.data;
            // -----------------------------------------------------------------------
            // Post-creation enrichment (outside 25s ceiling — best-effort, budgeted)
            // -----------------------------------------------------------------------
            // Auto-setup git branch (non-blocking)
            // SPEC-713: withBudget 3s — git subprocess can be slow on large repos
            const gitBudgetResult = await withBudget('setupGitBranch', 3_000, () => measureStep('setupGitBranch', () => setupGitBranch(projectId, spec.id)));
            const gitSetupResult = unwrapBudget(gitBudgetResult, undefined);
            /* v8 ignore start -- requires real git repo */
            if (gitSetupResult) {
                spec.gitBranch = gitSetupResult.branch;
            }
            /* v8 ignore stop */
            // Brief contradiction check (non-blocking)
            // SPEC-713: withBudget 2s — scans all specs for contradiction keywords
            const contradictionResult = await withBudget('checkContradictions', 2_000, () => measureStep('checkContradictions', () => checkContradictions(projectId, spec.id, description)));
            const contradictionHint = unwrapBudget(contradictionResult, undefined);
            // SPEC-555: Enrich estimation with calendar days (best-effort, non-blocking)
            // SPEC-713: withBudget 2s
            const velocityResult = await withBudget('velocityEnrichment', 2_000, () => measureStep('velocityEnrichment', () => enrichEstimationWithVelocity(params.projectPath ?? '', estimation.devHours)));
            const velocityEnrichment = unwrapBudget(velocityResult, {
                calendarDays: null,
                velocityNote: 'velocity data unavailable',
            });
            // SPEC-506: Apply persisted calibration multiplier (best-effort, non-blocking)
            // SPEC-713: withBudget 2s
            const calibrationResult = await withBudget('calibrationEnrichment', 2_000, () => measureStep('calibrationEnrichment', () => enrichEstimationWithCalibration(params.projectPath ?? '', estimation.devHours, spec.scope, spec.type)));
            const calibrationEnrichment = unwrapBudget(calibrationResult, {
                calibratedHours: estimation.devHours,
                applied: false,
                calibrationNote: null,
            });
            // Build result (SPEC-461: lean — no progress, HTML, diagrams, scope filters)
            const result = {
                // SPEC-781: async analysis running in background (external project data)
                pendingAnalysis: true,
                specId: spec.id,
                title: spec.title,
                slug: spec.slug,
                type: spec.type,
                scope: spec.scope,
                difficulty: spec.difficulty,
                risk: spec.risk,
                tags: spec.tags,
                target: spec.target,
                status: spec.status,
                gitBranch: spec.gitBranch,
                specPath,
                // SPEC-1010 Bug A — `technicalPath` no longer leaked in the response.
                // SSR back-migration (SPEC-752) folded technical.md into spec.md as
                // ## Technical; the file is never written and the field would point to
                // a non-existent path. Internal Spec record still carries it for
                // backwards-compat with stored data — that field is removed in PR-C.
                estimation: {
                    devHours: calibrationEnrichment.calibratedHours,
                    reviewHours: estimation.reviewHours,
                    totalCostUsd: estimation.totalCostUsd,
                    recommendedModel: estimation.recommendedModel,
                    executionMode: estimation.tokenOptimization.mode,
                    calendarDays: velocityEnrichment.calendarDays,
                    velocityNote: velocityEnrichment.velocityNote,
                },
                duplicateWarning: duplicate
                    ? ti('spec.duplicateTitle', { title: spec.title, slug: spec.slug })
                    : undefined,
                ...(constitutionCheck.warnings.length > 0
                    ? { constitutionWarnings: constitutionCheck.warnings }
                    : {}),
                ...(clarificationSession
                    ? {
                        linkedClarificationSession: clarificationSession.id,
                        clarificationTopic: clarificationSession.topic,
                        clarificationAnswersUsed: Object.keys(clarificationSession.answers).length,
                    }
                    : {
                        clarificationNote: 'Spec created directly from description — Planu inferred requirements from project context.',
                    }),
                message: ti('tools.create_spec.success', { id: spec.id, title: spec.title }),
                ...(contradictionHint ? { contradictionHint } : {}),
                /* v8 ignore next -- requires real git repo */
                ...(gitSetupResult ? { gitAutoSetup: gitSetupResult.data } : {}),
            };
            const advisorySignals = [];
            if (actionableMetrics.length > 0) {
                result.actionableMetrics = actionableMetrics;
            }
            if (advisoryCriteria.length > 0) {
                advisorySignals.push(makeAdvisorySignal({
                    key: 'ungrounded-contract-items',
                    kind: 'quality',
                    message: `${String(advisoryCriteria.length)} ungrounded contract item(s) kept advisory-only.`,
                    source: 'validator',
                    evidence: advisoryCriteria.slice(0, 10),
                    confidence: 0.8,
                    surface: 'structuredContent',
                    value: advisoryCriteria,
                }));
            }
            const splitResult = buildSplitResult(splitSuggestion, knowledge?.experienceLevel);
            if (splitResult) {
                result.splitSuggestion = splitResult;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'split-suggestion',
                    kind: 'complexity',
                    message: 'Spec may benefit from splitting.',
                    source: 'heuristic',
                    evidence: ['spec-splitter heuristic'],
                    confidence: 0.5,
                    surface: 'structuredContent',
                    value: splitResult,
                    deprecatedAlias: 'splitSuggestion',
                }));
            }
            // Dispatch hook event (fire-and-forget)
            fireSpecCreatedHook(projectId, spec, params.projectPath ?? '');
            // SPEC-781: Fire-and-forget async analysis (external project data)
            runAutopilotAsync(spec.id, params.projectPath ?? '', description);
            // SPEC-713: Track warnings from budgeted post-creation steps
            const budgetWarnings = [];
            // Auto-estimation (best-effort, fire-and-forget)
            // SPEC-713: withBudget 2s — estimation is sync, specStore.updateSpec is a single JSON write
            const autoEstimationResult = await withBudget('auto-estimation', 2_000, async () => {
                const autoEstimation = estimateSpec(spec);
                await measureStep('specStore-updateSpec', () => specStore.updateSpec(projectId, spec.id, { estimation: autoEstimation.estimation }));
                return autoEstimation;
            });
            if (!autoEstimationResult.exceeded) {
                const ae = autoEstimationResult.value;
                result.autoEstimation = {
                    confidence: ae.confidence,
                    reasoning: ae.reasoning,
                    similarSpecs: ae.similarSpecs,
                };
            }
            // SPEC-461: No per-spec HTML reports — lean format
            // SPEC-713: Run post-creation checks in parallel to reduce total wall-clock time.
            // Each has an individual budget; results are merged into `result` after settlement.
            const [readinessResult, qualityResult, duplicatesResult, complexityResult, priorLinksResult, nextStepsResult,] = await Promise.all([
                // Auto-readiness check (best-effort) — SPEC-713: 3s budget
                withBudget('auto-readiness', 3_000, () => measureStep('auto-readiness', () => checkSpecReadiness(spec, 'lenient'))),
                // SPEC-492: Spec quality score (0-100) — SPEC-713: 2s budget
                withBudget('quality-score', 2_000, () => measureStep('quality-score', () => runQualityScore(spec).then((r) => r ?? null))),
                // SPEC-514: Semantic duplicate detection — SPEC-713: 2s budget
                withBudget('duplicate-detection', 2_000, () => measureStep('duplicate-detection', async () => {
                    const allSpecs = await specStore.listSpecs(projectId);
                    const recentSpecs = allSpecs.slice(-200).filter((s) => s.id !== spec.id);
                    return findSimilarSpecs(spec.title, recentSpecs, { threshold: 0.3, topN: 3 });
                })),
                // SPEC-614 AC4: complexity-category hint — SPEC-713: 2s budget
                withBudget('complexity-advice', 2_000, () => measureStep('complexity-advice', () => runComplexityAdvice(projectId, spec.id, spec.tags, filteredCriteria.length))),
                // SPEC-615: prior-decision links — SPEC-713: 2s budget
                withBudget('prior-decisions', 2_000, () => measureStep('prior-decisions', () => runPriorDecisionsHint(projectId, description, spec.tags))),
                // Post-creation suggestions — SPEC-713: 3s budget
                withBudget('post-creation-suggestions', 3_000, () => measureStep('post-creation-suggestions', () => generatePostCreationSuggestions(params.projectPath ?? '', description, knowledge ?? undefined))),
            ]);
            // Merge parallel results into `result`
            if (!readinessResult.exceeded) {
                const readiness = readinessResult.value;
                result.autoReadiness = { score: readiness.score, isReady: readiness.ready };
            }
            else {
                budgetWarnings.push(readinessResult.warning);
            }
            const qualityValue = unwrapBudget(qualityResult, null, budgetWarnings);
            if (qualityValue !== null) {
                result.qualityScore = qualityValue;
            }
            // SPEC-485: Simplicity autopilot — detect over-engineering signals (best-effort, sync)
            const simplicityResult = runSimplicityCheck([params.description, ...filteredCriteria].join('\n'), estimation.devHours);
            if (simplicityResult) {
                result.simplicityCheck = simplicityResult;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'simplicity-check',
                    kind: 'simplicity',
                    message: `Simplicity recommendation: ${simplicityResult.recommendation}.`,
                    source: 'heuristic',
                    evidence: simplicityResult.signals.map((signal) => signal.type),
                    confidence: 0.55,
                    surface: 'structuredContent',
                    value: simplicityResult,
                    deprecatedAlias: 'simplicityCheck',
                }));
            }
            // SPEC-514: duplicate results
            const possibleDuplicates = unwrapBudget(duplicatesResult, [], budgetWarnings);
            if (possibleDuplicates.length > 0) {
                result.possibleDuplicates = possibleDuplicates;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'possible-duplicates',
                    kind: 'duplicate',
                    message: `${String(possibleDuplicates.length)} possible duplicate spec(s) detected.`,
                    source: 'heuristic',
                    evidence: possibleDuplicates.map((dup) => dup.specId),
                    confidence: 0.5,
                    surface: 'structuredContent',
                    value: possibleDuplicates,
                    deprecatedAlias: 'possibleDuplicates',
                }));
            }
            // SPEC-222 Trigger 2: Auto-challenge hint for high-risk specs
            if (spec.risk === 'high' || spec.difficulty >= 4) {
                result.riskWarning = HIGH_RISK_WARNING;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'risk-warning',
                    kind: 'challenge',
                    message: 'High-risk heuristic warning generated.',
                    source: 'heuristic',
                    evidence: [`risk:${spec.risk}`, `difficulty:${String(spec.difficulty)}`],
                    confidence: 0.6,
                    surface: 'structuredContent',
                    value: HIGH_RISK_WARNING,
                    deprecatedAlias: 'riskWarning',
                }));
            }
            // SPEC-614 AC4: complexity advice
            const cAdvice = unwrapBudget(complexityResult, null, budgetWarnings);
            if (cAdvice) {
                result.complexityAdvice = cAdvice;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'complexity-advice',
                    kind: 'complexity',
                    message: cAdvice.reasoning,
                    source: 'history',
                    evidence: [`similarSpecs:${String(cAdvice.similarSpecsCount)}`],
                    confidence: 0.6,
                    surface: 'structuredContent',
                    value: cAdvice,
                    deprecatedAlias: 'complexityAdvice',
                }));
            }
            // SPEC-615: prior decisions
            const priorLinks = unwrapBudget(priorLinksResult, [], budgetWarnings);
            if (priorLinks.length > 0) {
                result.priorDecisions = priorLinks.map((l) => l.decisionId);
                spec.priorDecisions = result.priorDecisions;
                advisorySignals.push(makeAdvisorySignal({
                    key: 'prior-decisions',
                    kind: 'prior-decision',
                    message: `${String(priorLinks.length)} prior decision link(s) found.`,
                    source: 'history',
                    evidence: priorLinks.map((link) => link.decisionId),
                    confidence: 0.65,
                    surface: 'structuredContent',
                    value: result.priorDecisions,
                    deprecatedAlias: 'priorDecisions',
                }));
            }
            // Post-creation suggestions
            const nextSteps = unwrapBudget(nextStepsResult, [], budgetWarnings);
            if (nextSteps.length > 0) {
                result.nextSteps = nextSteps;
            }
            // SPEC-713: Surface budget warnings in result when steps exceeded their budget
            if (budgetWarnings.length > 0) {
                result.budgetWarnings = budgetWarnings;
            }
            // SPEC-1017: No auto-regeneration of project-tree dashboard HTML.
            if (params.projectPath) {
                notifyStoreChange(params.projectPath, 'specs');
            }
            // SPEC-644: Refresh model mapping TTL in background (fire-and-forget)
            void import('../engine/model-tier-resolver.js')
                .then(({ triggerModelMappingRefresh }) => {
                triggerModelMappingRefresh(knowledge?.projectPath ?? params.projectPath ?? '');
            })
                .catch(() => {
                /* best-effort */
            });
            // Auto post-creation pipeline: challenge + readiness (SPEC-445)
            // SPEC-713: Convert to fire-and-forget — pipeline has its own 10s internal timeout
            // but was blocking the MCP response. Now runs in background after response is built.
            const pipelineResult = {
                challengeSummary: null,
                readinessScore: null,
                readinessSuggestion: null,
                error: 'pipeline deferred (SPEC-713 fire-and-forget)',
            };
            // Start pipeline in background — result will be lost after response, but spec is
            // already persisted so this is a best-effort enrichment only.
            void runAutoPostCreatePipeline(spec.id, projectId, knowledge?.projectPath ?? params.projectPath ?? '').catch(() => {
                /* best-effort */
            });
            // Build markdown response
            const lines = [
                `**SPEC-${spec.id}** — ${spec.title}`,
                '',
                `| Field | Value |`,
                `|-------|-------|`,
                `| Type | ${spec.type} |`,
                `| Scope | ${spec.scope} |`,
                `| Difficulty | ${String(spec.difficulty)}/5 |`,
                `| Risk | ${spec.risk} |`,
                `| Target | ${spec.target} |`,
                `| Status | ${spec.status} |`,
                `| Tags | ${spec.tags.join(', ')} |`,
                `| Dev hours | ${String(estimation.devHours)}h |`,
                `| Review hours | ${String(estimation.reviewHours)}h |`,
                `| Cost | $${String(estimation.totalCostUsd)} |`,
            ];
            if (spec.gitBranch) {
                lines.push(`| Branch | \`${spec.gitBranch}\` |`);
            }
            if (duplicate) {
                lines.push('', `⚠️ ${ti('spec.duplicateTitle', { title: spec.title, slug: spec.slug })}`);
            }
            if (constitutionCheck.warnings.length > 0) {
                lines.push('', '⚠️ **Constitution Warnings**');
                constitutionCheck.warnings.forEach((w) => lines.push(`- ${w.description}`));
            }
            if (contradictionHint) {
                lines.push('', `⚠️ ${contradictionHint}`);
            }
            if (possibleDuplicates.length > 0 ||
                result.riskWarning ||
                splitResult ||
                result.qualityScore ||
                simplicityResult) {
                lines.push('', 'Advisory signals were computed and are available in structuredContent.advisorySignals.');
            }
            const allNextSteps = (result.nextSteps ?? []).map((step) => (typeof step === 'string' ? step : formatPostCreationSuggestion(step)));
            const markdownText = allNextSteps.length > 0
                ? addNextSteps(formatSuccess(ti('tools.create_spec.success', { id: spec.id, title: spec.title }), lines.join('\n')), allNextSteps)
                : formatSuccess(ti('tools.create_spec.success', { id: spec.id, title: spec.title }), lines.join('\n'));
            // SPEC-469: Build autopilot summary from analyzer results
            const collector = new AutopilotSummaryCollector();
            // SPEC-612: Record outOfScope auto-suggestion in autopilot summary
            if (outOfScopeSuggestionMsg !== null) {
                collector.pushOk('scope-boundaries', outOfScopeSuggestionMsg);
                advisorySignals.push(makeAdvisorySignal({
                    key: 'out-of-scope-suggestions',
                    kind: 'out-of-scope',
                    message: outOfScopeSuggestionMsg,
                    source: 'heuristic',
                    evidence: ['scope-boundaries suggester'],
                    confidence: 0.45,
                    surface: 'structuredContent',
                    value: spec.outOfScope ?? [],
                }));
            }
            if (autopilot.detectedPatterns.length > 0) {
                collector.pushOk('pattern-detection', `Detected patterns: ${autopilot.detectedPatterns.join(', ')}`);
                advisorySignals.push(makeAdvisorySignal({
                    key: 'detected-patterns',
                    kind: 'domain',
                    message: `Detected patterns: ${autopilot.detectedPatterns.join(', ')}`,
                    source: 'heuristic',
                    evidence: autopilot.detectedPatterns,
                    confidence: 0.5,
                    surface: 'structuredContent',
                    value: autopilot.detectedPatterns,
                }));
            }
            const totalSuggestedFiles = autopilot.suggestedFiles.create.length +
                autopilot.suggestedFiles.modify.length +
                autopilot.suggestedFiles.test.length;
            if (totalSuggestedFiles > 0) {
                collector.pushOk('file-analysis', `Suggested ${String(totalSuggestedFiles)} files (${String(autopilot.suggestedFiles.create.length)} create, ${String(autopilot.suggestedFiles.modify.length)} modify, ${String(autopilot.suggestedFiles.test.length)} test)`);
                advisorySignals.push(makeAdvisorySignal({
                    key: 'suggested-files',
                    kind: 'file',
                    message: `${String(totalSuggestedFiles)} possible related file(s) detected.`,
                    source: 'heuristic',
                    evidence: [
                        ...autopilot.suggestedFiles.create.map((f) => f.path),
                        ...autopilot.suggestedFiles.modify.map((f) => f.path),
                        ...autopilot.suggestedFiles.test.map((f) => f.path),
                    ],
                    confidence: 0.45,
                    surface: 'structuredContent',
                    value: {
                        create: autopilot.suggestedFiles.create.map((f) => f.path),
                        modify: autopilot.suggestedFiles.modify.map((f) => f.path),
                        test: autopilot.suggestedFiles.test.map((f) => f.path),
                    },
                    deprecatedAlias: 'autopilotSummary.suggestedFiles',
                }));
            }
            if (filteredCriteria.length > 0) {
                collector.pushOk('criteria-enrichment', `Added ${String(filteredCriteria.length)} acceptance criteria from project context`);
            }
            if (pipelineResult.challengeSummary) {
                collector.pushOk('challenge', `Challenge analysis complete: ${pipelineResult.challengeSummary}`);
            }
            if (pipelineResult.readinessScore !== null) {
                // SPEC-629: append Opus hint for difficulty >= 3 specs
                const opusHint = spec.difficulty >= 3
                    ? ` — difficulty ${String(spec.difficulty)}: use Opus to add exact file paths + function names`
                    : '';
                collector.pushOk('readiness', `Readiness score: ${String(pipelineResult.readinessScore)}/100${opusHint}`);
                advisorySignals.push(makeAdvisorySignal({
                    key: 'readiness-score',
                    kind: 'readiness',
                    message: `Readiness score: ${String(pipelineResult.readinessScore)}/100.`,
                    source: 'validator',
                    evidence: ['auto post-creation pipeline'],
                    confidence: 0.6,
                    surface: 'structuredContent',
                    value: pipelineResult.readinessScore,
                }));
            }
            else if (spec.difficulty >= 3) {
                // SPEC-629: always surface the Opus hint for high-difficulty specs
                collector.pushOk('recommend_model', `Difficulty ${String(spec.difficulty)} spec — use Opus to add exact file paths, function names and anticipated test breaks.`);
                advisorySignals.push(makeAdvisorySignal({
                    key: 'model-recommendation',
                    kind: 'model',
                    message: 'High-difficulty spec may benefit from a stronger model for review.',
                    source: 'heuristic',
                    evidence: [`difficulty:${String(spec.difficulty)}`],
                    confidence: 0.4,
                    surface: 'structuredContent',
                    value: { recommendedTier: 'max' },
                }));
            }
            if (calibrationEnrichment.calibrationNote) {
                collector.pushOk('calibration', `📐 ${calibrationEnrichment.calibrationNote}`);
            }
            const humanSummary = buildCreateSpecSummary(spec.title, estimation.devHours);
            result.advisorySignals = advisorySignals;
            result.compat = buildAdvisoryCompat();
            const compactResult = compactObj(result);
            // SPEC-722: Issue a planner token for this spec creation.
            // Best-effort — token failure never blocks spec creation.
            let plannerToken;
            try {
                const resolvedProjectPath = resolvedInputParams.projectPath ?? '';
                if (resolvedProjectPath.length > 0) {
                    const sessionId = resolvedInputParams.sessionId ?? 'unknown-session';
                    const modelId = resolvedInputParams.modelId ?? 'unknown-model';
                    const host = resolvedInputParams.host ?? 'unknown-host';
                    plannerToken = await issuePlannerToken(resolvedProjectPath, spec.id, sessionId, modelId, host);
                }
            }
            catch {
                // best-effort — token issuance failure must not block spec creation
            }
            // SPEC-1011 Bug E / fallback hardening: surface local file analysis in the response payload.
            // Those paths are also written into ## Files only when used as technical evidence.
            const suggestedFilesPayload = autopilot.suggestedFiles.create.length +
                autopilot.suggestedFiles.modify.length +
                autopilot.suggestedFiles.test.length >
                0
                ? {
                    create: autopilot.suggestedFiles.create.map((f) => f.path),
                    modify: autopilot.suggestedFiles.modify.map((f) => f.path),
                    test: autopilot.suggestedFiles.test.map((f) => f.path),
                }
                : undefined;
            const baseResult = toolResult(markdownText, {
                ...compactResult,
                advisorySignals,
                compat: buildAdvisoryCompat(),
                humanSummary,
                ...(collector.hasEntries() ? { autopilotSummary: collector.getMessages() } : {}),
                ...(suggestedFilesPayload !== undefined
                    ? { 'autopilotSummary.suggestedFiles': suggestedFilesPayload }
                    : {}),
                ...(agentTeamPlan && agentTeamPlan.roles.length > 0 ? { agentTeamPlan } : {}),
                ...(plannerToken !== undefined ? { plannerToken } : {}),
            });
            return {
                ...baseResult,
                content: [...baseResult.content, { type: 'text', text: humanSummary }],
            };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                content: [{ type: 'text', text: ti('errors.internalError', { message }) }],
                isError: true,
            };
        }
    }); // end trackCost
}
//# sourceMappingURL=create-spec.js.map