// tools/challenge-spec.ts — Devil's advocate resilience testing (SPEC-595: elicitation added)
// Analyzes a spec from adversarial perspectives: failure scenarios,
// concurrency issues, scale limits, security holes, and data consistency.
import { specStore, knowledgeStore } from '../storage/index.js';
// SPEC-1011 Bug F: fallback resolver using disk fingerprints
import { resolveProjectFromPath } from '../storage/project-resolver.js';
import { updateSpec } from '../storage/spec-store.js';
import { resolveProjectId, missingProjectIdError } from './resolve-project-id.js';
import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
import { t, ti } from '../i18n/index.js';
import { generateFailureScenarios, generateSecurityScenarios, generateScaleScenarios, generateDataConsistencyScenarios, generateGameChallenges, generateDesktopAutoUpdateChallenges, } from './challenge-spec-scenarios.js';
import { generateConcurrencyAnalysis, buildScalabilityAssessment, calculateOverallRisk, readSpecContent, } from './challenge-spec-helpers.js';
import { prioritizeScenarios, buildPrioritizedSummary } from '../engine/challenge-prioritizer.js';
import { checkContradictions as checkScopeContradictions } from '../engine/scope-boundaries/index.js';
import { buildChallengeSpecSummary } from '../engine/human-summary.js';
import { generateAgentChallengeScenarios, isAgentSpec, } from './challenge-spec/agent-challenge-scenarios.js';
import { generateImplementationContractChallengeScenarios } from './challenge-spec/implementation-contract-challenge-scenarios.js';
import { getPlatformChallenges } from './challenge-spec/platform-challenge-scenarios.js';
import { generateSecurityChallengeScenarios } from './challenge-spec/security-challenge-scenarios.js';
import { generatePrivacyChallengeScenarios } from './challenge-spec/privacy-challenge-scenarios.js';
import { generateEventChallengeScenarios } from './challenge-spec/event-challenge-scenarios.js';
import { generateDddChallenges, generateXpPairClarityChallenge, } from './challenge-spec/architecture-challenge-scenarios.js';
import { generateResilienceChallengeScenarios } from './challenge-spec/resilience-challenge-scenarios.js';
import { detectContradictions as detectDecisionContradictions, searchPriorDecisions, } from '../engine/prior-decisions/index.js';
import { getDecisions } from '../storage/decision-store.js';
import { calculateTokenBudget, injectBudgetIntoPrompt } from '../engine/token-budget/index.js';
const ALL_FOCUS_AREAS = [
    'failures',
    'concurrency',
    'scale',
    'security',
    'data-consistency',
];
/**
 * SPEC-615 AC3: detect contradictions between spec criteria and prior decisions.
 * Mutates failureScenarios in place. Best-effort — never throws.
 */
async function runPriorDecisionContradictions(projectId, specTitle, tags, specContent, failureScenarios) {
    try {
        const decisions = await getDecisions(projectId);
        if (decisions.length === 0) {
            return;
        }
        const query = tags.length > 0 ? `${specTitle} ${tags.join(' ')}` : specTitle;
        const priorLinks = searchPriorDecisions(query, decisions);
        if (priorLinks.length === 0) {
            return;
        }
        const decisionTexts = new Map(decisions
            .filter((d) => priorLinks.some((l) => l.decisionId === d.id))
            .map((d) => [d.id, `${d.what} ${d.why}`]));
        const criteriaTexts = specContent
            .split('\n')
            .filter((line) => line.trim().startsWith('-') || line.trim().startsWith('*'))
            .map((line) => line.replace(/^[\s\-*]+/, '').trim())
            .filter((line) => line.length > 0);
        if (criteriaTexts.length === 0) {
            return;
        }
        const findings = detectDecisionContradictions(criteriaTexts, priorLinks, decisionTexts);
        for (const f of findings) {
            failureScenarios.push({
                scenario: `Contradicts prior decision: ${f.decisionId}`,
                probability: f.severity === 'hard' ? 'high' : 'low',
                impact: 'low',
                currentHandling: `Criterion: "${f.criterion}"`,
                requiredHandling: f.reason,
                dataConsistency: 'Prior decision alignment risk',
                userExperience: 'May conflict with an architectural decision already logged',
            });
        }
    }
    catch {
        /* best-effort — never block challenge-spec */
    }
}
/** Build the fallback interactiveQuestion for apply-suggestions decision. */
function buildApplySuggestionsFallback(criticalCount) {
    return {
        header: 'Apply',
        question: `Found ${criticalCount} critical scenario(s). How should suggestions be applied?`,
        multiSelect: false,
        options: [
            { label: 'all', description: 'Apply all suggestions to the spec criteria' },
            {
                label: 'critical',
                description: 'Apply only critical/high-impact suggestions (Recommended)',
            },
            { label: 'none', description: 'Review manually — do not auto-apply anything' },
        ],
    };
}
// SPEC-595: server? param enables elicitation for apply-suggestions decision
export async function handleChallengeSpec(args, server) {
    const { specId, focus } = args;
    const projectId = resolveProjectId(args);
    if (!projectId) {
        return missingProjectIdError;
    }
    // 1. Load spec
    const spec = await specStore.getSpec(projectId, specId);
    if (!spec) {
        return {
            content: [{ type: 'text', text: ti('spec.notFound', { id: specId }) }],
            isError: true,
        };
    }
    // 2. Load project knowledge — SPEC-1011 Bug F: use disk fingerprint fallback
    let knowledge = await knowledgeStore.getKnowledge(projectId);
    if (!knowledge && args.projectPath) {
        const resolved = await resolveProjectFromPath(args.projectPath);
        if (resolved !== null) {
            knowledge = resolved.knowledge;
        }
    }
    if (!knowledge) {
        return {
            content: [{ type: 'text', text: t('project.notFound') }],
            isError: true,
        };
    }
    // 2b. Load Constitution for compliance context
    const constitution = await knowledgeStore.getConstitution(projectId);
    // 3. Read spec content
    const specContent = await readSpecContent(spec);
    // 4. Determine focus areas
    const focusAreas = focus && focus.length > 0 ? focus : ALL_FOCUS_AREAS;
    // 5. Generate failure scenarios
    const failureScenarios = [];
    if (focusAreas.includes('failures')) {
        failureScenarios.push(...generateFailureScenarios(spec, specContent, knowledge));
    }
    if (focusAreas.includes('security')) {
        failureScenarios.push(...generateSecurityScenarios(spec, specContent, knowledge));
    }
    if (focusAreas.includes('scale')) {
        failureScenarios.push(...generateScaleScenarios(spec, specContent, knowledge));
    }
    if (focusAreas.includes('data-consistency')) {
        failureScenarios.push(...generateDataConsistencyScenarios(spec, specContent, knowledge));
    }
    // 5e. Game-specific challenges (performance, balance, cheating, physics)
    if (knowledge.projectCategory === 'game' ||
        (knowledge.framework !== null &&
            ['unity', 'godot', 'unreal', 'bevy', 'phaser', 'pygame'].includes(knowledge.framework))) {
        failureScenarios.push(...generateGameChallenges(spec, specContent, knowledge));
    }
    // 5f. SPEC-014b: Desktop auto-update edge cases
    failureScenarios.push(...generateDesktopAutoUpdateChallenges(spec, specContent, knowledge));
    // 5g. SPEC-016a: AI agent-specific failure modes
    if (isAgentSpec(spec, specContent)) {
        failureScenarios.push(...generateAgentChallengeScenarios(spec, specContent, knowledge));
    }
    if (focusAreas.includes('failures')) {
        failureScenarios.push(...generateImplementationContractChallengeScenarios(spec, specContent));
    }
    // 5h. SPEC-015a: Smart contract, bot, and IoT challenges
    failureScenarios.push(...getPlatformChallenges(spec, specContent, knowledge));
    // 5i. SPEC-029: Security authorization and STRIDE challenges
    if (focusAreas.includes('security')) {
        failureScenarios.push(...generateSecurityChallengeScenarios(spec, specContent, knowledge));
    }
    // 5j. SPEC-030: Privacy challenges (mandatory when spec has PII)
    failureScenarios.push(...generatePrivacyChallengeScenarios(spec, specContent, knowledge));
    // 5k. SPEC-031: Event-driven challenges (contract, idempotence, DLQ)
    failureScenarios.push(...generateEventChallengeScenarios(spec, specContent, knowledge));
    // 5l. SPEC-033: DDD aggregate integrity challenges
    const hasDdd = knowledge.architecture.primary === 'ddd' || knowledge.architecture.secondary.includes('ddd');
    if (hasDdd) {
        failureScenarios.push(...generateDddChallenges(spec, specContent));
    }
    // 5n. SPEC-034: Resilience, validation, concurrency, and saga challenges
    failureScenarios.push(...generateResilienceChallengeScenarios(spec, specContent, knowledge));
    // 5m. SPEC-033: XP pair programming clarity challenge
    // `agileConfig` may be present on enriched knowledge objects (AgileProcessDetection mixin)
    // but is not declared on the base ProjectKnowledge interface — intentional duck-type access.
    // ProjectKnowledge has no index signature, so `as unknown` intermediate is required.
    const agileConfig = knowledge.agileConfig;
    if (agileConfig?.process === 'xp') {
        failureScenarios.push(...generateXpPairClarityChallenge(spec, specContent));
    }
    // SPEC-612: Check for contradictions between outOfScope and acceptance criteria
    if (spec.outOfScope !== undefined && spec.outOfScope.length > 0) {
        const criteriaTexts = specContent
            .split('\n')
            .filter((line) => line.trim().startsWith('-') || line.trim().startsWith('*'))
            .map((line) => line.replace(/^[\s\-*]+/, '').trim())
            .filter((line) => line.length > 0);
        const contradictionResult = checkScopeContradictions(spec.outOfScope, criteriaTexts);
        for (const c of contradictionResult.contradictions) {
            failureScenarios.push({
                scenario: `Criterion contradicts outOfScope declaration: "${c.contradicts}"`,
                probability: 'medium',
                impact: 'low',
                currentHandling: `Criterion: "${c.criterion}"`,
                requiredHandling: `Remove or revise the criterion — it conflicts with the declared out-of-scope item: "${c.contradicts}"`,
                dataConsistency: 'Scope drift risk',
                userExperience: 'Scope creep may delay delivery or cause rework',
            });
        }
    }
    // SPEC-615 AC3: Check criteria against prior decisions for technology contradictions
    await runPriorDecisionContradictions(projectId, spec.title, spec.tags, specContent, failureScenarios);
    // 6. Generate concurrency analysis
    const concurrencyAnalysis = focusAreas.includes('concurrency')
        ? generateConcurrencyAnalysis(spec, specContent, knowledge)
        : { hotPaths: [], raceConditions: [], sharedState: [], recommendations: [] };
    // 7. Build scalability assessment
    const scalabilityAssessment = buildScalabilityAssessment(spec, knowledge, failureScenarios);
    // 8. Calculate overall risk
    const overallRisk = calculateOverallRisk(failureScenarios, concurrencyAnalysis);
    // 9. Compute relevance scores and select top-3 (SPEC-338)
    const prioritized = prioritizeScenarios(failureScenarios, spec);
    const prioritizedSummary = buildPrioritizedSummary(prioritized);
    // Attach relevanceScore to each scenario in the full list
    const failureScenariosScored = failureScenarios.map((s) => {
        const match = prioritized.find((p) => p.scenario === s.scenario);
        return match !== undefined ? { ...s, relevanceScore: match.relevanceScore } : s;
    });
    const analysis = {
        failureScenarios: failureScenariosScored,
        concurrencyAnalysis,
        scalabilityAssessment,
        overallRisk,
    };
    const rawHumanSummary = buildChallengeSpecSummary(prioritized, overallRisk);
    // SPEC-620: Inject token budget tag into the LLM-facing summary prompt
    const specDevHours = spec.estimation.devHours;
    const challengeBudget = calculateTokenBudget(specDevHours > 0 ? specDevHours : 2, 'architecture');
    const humanSummary = injectBudgetIntoPrompt(rawHumanSummary, challengeBudget);
    const analysisPayload = {
        ...analysis,
        prioritized,
        simplicity: {
            question: 'Is there a simpler way to achieve the same result? What would happen if we removed the most complex layer?',
            hint: 'Challenge every abstraction: does it exist because we need it NOW, or because we think we might need it later?',
        },
        summary: {
            totalScenarios: failureScenarios.length,
            shownByDefault: 3,
            mustAddressBeforeCoding: prioritizedSummary,
            criticalImpact: failureScenarios.filter((s) => s.impact === 'critical').length,
            highImpact: failureScenarios.filter((s) => s.impact === 'high').length,
            raceConditions: concurrencyAnalysis.raceConditions.length,
            overallRisk,
            focusAreas: focusAreas,
            warnings: overallRisk === 'critical' || overallRisk === 'high'
                ? [
                    `⚠️ HIGH RISK spec: overall risk is "${overallRisk}". Review all critical/high scenarios before implementation.`,
                    overallRisk === 'critical'
                        ? 'BLOCK: Resolve critical scenarios before merging to main branch.'
                        : 'CAUTION: Add extra code review and integration tests for high-risk paths.',
                ]
                : [],
        },
        constitutionCompliance: constitution
            ? {
                principlesCount: constitution.principles.length,
                strictCount: constitution.principles.filter((p) => p.enforceLevel === 'strict').length,
                checkedAt: new Date().toISOString(),
            }
            : null,
    };
    // SPEC-964: Persist challenge report so update_status can gate on it
    try {
        await updateSpec(projectId, specId, {
            challengeReport: {
                runAt: new Date().toISOString(),
                totalScenarios: failureScenarios.length,
                addressedCount: failureScenarios.length, // All generated scenarios are considered "addressed" by running challenge_spec
                focusAreas: focusAreas,
                overallRisk: overallRisk,
                passed: true,
            },
        });
    }
    catch {
        // Best-effort: don't fail the challenge if persistence fails
    }
    // SPEC-595: Elicit how user wants to apply suggestions when critical issues exist
    const criticalCount = failureScenarios.filter((s) => s.impact === 'critical').length;
    if (server !== undefined && criticalCount > 0) {
        const { field, property } = buildEnumSchema('apply', ['all', 'critical', 'none'], ['Apply all suggestions', 'Apply critical only (Recommended)', 'Review manually'], 'Apply suggestions', 'critical');
        const schema = { type: 'object', properties: { [field]: property } };
        const fallback = buildApplySuggestionsFallback(criticalCount);
        const outcome = await elicitOrFallback(server, `Found ${criticalCount} critical scenario(s). How should the suggestions be applied?`, schema, [fallback]);
        if (outcome.mode === 'fallback') {
            return {
                content: [
                    {
                        type: 'text',
                        text: ti('tools.challenge_spec.success', {
                            scenarioCount: String(failureScenarios.length),
                        }),
                    },
                    { type: 'text', text: humanSummary },
                    {
                        type: 'text',
                        text: '⚡ INTERACTIVE — call AskUserQuestion now with the interactiveQuestions from structuredContent',
                    },
                ],
                structuredContent: {
                    ...analysisPayload,
                    interactiveQuestions: outcome.interactiveQuestions,
                },
            };
        }
        const chosenApply = outcome.action === 'accept'
            ? (outcome.content?.apply ?? 'none')
            : 'none';
        return {
            content: [
                {
                    type: 'text',
                    text: ti('tools.challenge_spec.success', {
                        scenarioCount: String(failureScenarios.length),
                    }),
                },
                { type: 'text', text: humanSummary },
            ],
            structuredContent: { ...analysisPayload, applySuggestions: chosenApply },
        };
    }
    return {
        content: [
            {
                type: 'text',
                text: ti('tools.challenge_spec.success', {
                    scenarioCount: String(failureScenarios.length),
                }),
            },
            { type: 'text', text: JSON.stringify(analysisPayload, null, 2) },
            { type: 'text', text: humanSummary },
        ],
    };
}
//# sourceMappingURL=challenge-spec.js.map