// engine/autopilot/handlers-b2.ts — SPEC-B2: remaining autopilot action handlers
// inject_criteria, rewrite_criteria_ears, verify_spec_compliance, analyze_code_impact
// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
/** Locate the first spec.md path matching {specId}-* under planu/specs/. */
async function findSpecMd(projectPath, specId) {
    const { join } = await import('node:path');
    const { glob } = await import('glob');
    const specDir = join(projectPath, 'planu', 'specs');
    const matches = await glob(`${specId}-*/spec.md`, { cwd: specDir, absolute: false });
    const rel = matches[0];
    return rel !== undefined ? join(specDir, rel) : null;
}
/** Locate the first spec directory matching {specId}-* under planu/specs/. */
async function findSpecDir(projectPath, specId) {
    const { join } = await import('node:path');
    const { glob } = await import('glob');
    const specDir = join(projectPath, 'planu', 'specs');
    const matches = await glob(`${specId}-*`, { cwd: specDir, absolute: false });
    const dir = matches[0];
    return dir !== undefined ? join(specDir, dir) : null;
}
/** Extract file path references from spec content (backtick-quoted paths with slashes). */
function extractFileRefs(content) {
    const regex = /`([^`]*\/[^`]+\.[a-zA-Z]+)`/g;
    const seen = new Set();
    const results = [];
    let m;
    while ((m = regex.exec(content)) !== null) {
        const ref = m[1];
        if (ref !== undefined && !seen.has(ref)) {
            seen.add(ref);
            results.push(ref);
        }
    }
    return results;
}
// ---------------------------------------------------------------------------
// inject_criteria
// ---------------------------------------------------------------------------
/** GIVEN/WHEN/THEN stubs to inject when criteria section is empty or sparse. */
function buildStubs(title) {
    return [
        `- [ ] GIVEN the system is running, WHEN ${title} is triggered, THE SYSTEM SHALL complete the operation successfully`,
        `- [ ] GIVEN invalid input, WHEN ${title} is triggered, THE SYSTEM SHALL return a descriptive error message`,
        `- [ ] GIVEN the operation completes, WHEN ${title} finishes, THE SYSTEM SHALL persist the result durably`,
    ];
}
/**
 * inject_criteria: Reads spec.md and, when the Criteria section has fewer than
 * 3 items, appends GIVEN/WHEN/THEN stubs derived from the spec title.
 */
export async function injectCriteria(ctx) {
    if (!ctx.specId) {
        return { success: false, summary: 'No specId', durationMs: 0 };
    }
    const { readFile, writeFile } = await import('node:fs/promises');
    const specPath = await findSpecMd(ctx.projectPath, ctx.specId);
    if (!specPath) {
        return { success: false, summary: `spec.md not found for ${ctx.specId}`, durationMs: 0 };
    }
    const content = await readFile(specPath, 'utf-8');
    // Count existing criteria (checkbox lines)
    const existing = (content.match(/^\s*-\s+\[[ xX]\]\s+.+$/gm) ?? []).length;
    if (existing >= 3) {
        return {
            success: true,
            summary: `inject_criteria: ${String(existing)} criteria already present — no injection needed`,
            durationMs: 0,
        };
    }
    const titleMatch = /^#\s+(.+)$/m.exec(content);
    const title = titleMatch?.[1]?.trim() ?? ctx.specId;
    const stubs = buildStubs(title);
    let updated;
    if (/^## Criteria/m.test(content)) {
        // Inject after ## Criteria heading; locate section end by next ## or end of string
        const criteriaIdx = content.indexOf('\n## Criteria');
        const nextSectionIdx = content.indexOf('\n## ', criteriaIdx + 1);
        const insertAt = nextSectionIdx === -1 ? content.length : nextSectionIdx;
        const before = content.slice(0, insertAt).trimEnd();
        const after = nextSectionIdx === -1 ? '' : content.slice(nextSectionIdx);
        updated = `${before}\n${stubs.join('\n')}\n${after}`;
    }
    else {
        updated = `${content.trimEnd()}\n\n## Criteria\n\n${stubs.join('\n')}\n`;
    }
    await writeFile(specPath, updated, 'utf-8');
    return {
        success: true,
        summary: `inject_criteria: added ${String(stubs.length)} GIVEN/WHEN/THEN stubs to ${ctx.specId} (was ${String(existing)})`,
        durationMs: 0,
    };
}
// ---------------------------------------------------------------------------
// rewrite_criteria_ears
// ---------------------------------------------------------------------------
/**
 * rewrite_criteria_ears: Rewrites non-EARS acceptance criteria to EARS format.
 * Lines that already comply are left unchanged.
 */
export async function rewriteCriteriaEars(ctx) {
    if (!ctx.specId) {
        return { success: false, summary: 'No specId', durationMs: 0 };
    }
    const { readFile, writeFile } = await import('node:fs/promises');
    const { isEarsCompliant } = await import('../ears/pattern-matcher.js');
    const { generateRewrites } = await import('../ears/rewriter.js');
    const specPath = await findSpecMd(ctx.projectPath, ctx.specId);
    if (!specPath) {
        return { success: false, summary: `spec.md not found for ${ctx.specId}`, durationMs: 0 };
    }
    const content = await readFile(specPath, 'utf-8');
    const AC_LINE_REGEX = /^(\s*-\s+\[[ xX]\]\s+)(.+)$/;
    let rewrittenCount = 0;
    const updated = content
        .split('\n')
        .map((line) => {
        const match = AC_LINE_REGEX.exec(line);
        if (!match) {
            return line;
        }
        const prefix = match[1] ?? '';
        const criterion = (match[2] ?? '').trim();
        if (isEarsCompliant(criterion)) {
            return line;
        }
        const rewrites = generateRewrites(criterion, 'event-driven');
        const best = rewrites[0];
        if (!best) {
            return line;
        }
        rewrittenCount++;
        return `${prefix}${best}`;
    })
        .join('\n');
    if (rewrittenCount > 0) {
        await writeFile(specPath, updated, 'utf-8');
    }
    return {
        success: true,
        summary: `rewrite_criteria_ears: rewrote ${String(rewrittenCount)} non-EARS criteria in ${ctx.specId}`,
        durationMs: 0,
    };
}
// ---------------------------------------------------------------------------
// verify_spec_compliance
// ---------------------------------------------------------------------------
/**
 * verify_spec_compliance: Checks that a spec has spec.md with criteria,
 * inline file references in spec.md, and that those files exist on disk.
 */
export async function verifySpecCompliance(ctx) {
    if (!ctx.specId) {
        return { success: false, summary: 'No specId', durationMs: 0 };
    }
    const { readFile } = await import('node:fs/promises');
    const { existsSync } = await import('node:fs');
    const { join } = await import('node:path');
    const specDirPath = await findSpecDir(ctx.projectPath, ctx.specId);
    if (!specDirPath) {
        return {
            success: false,
            summary: `verify_spec_compliance: spec directory not found for ${ctx.specId}`,
            durationMs: 0,
        };
    }
    const findings = [];
    const specMdPath = join(specDirPath, 'spec.md');
    let specContent = '';
    // Check 1: spec.md has acceptance criteria
    if (!existsSync(specMdPath)) {
        findings.push('spec.md is missing');
    }
    else {
        specContent = await readFile(specMdPath, 'utf-8');
        const criteriaCount = (specContent.match(/^\s*-\s+\[[ xX]\]\s+.+$/gm) ?? []).length;
        if (criteriaCount === 0) {
            findings.push('spec.md has no acceptance criteria');
        }
    }
    // Check 2: spec.md inline file references
    const implementedFiles = [];
    if (specContent.length > 0) {
        const refs = extractFileRefs(specContent);
        implementedFiles.push(...refs);
        if (refs.length === 0) {
            findings.push('spec.md inline Technical/Files sections have no file references');
        }
    }
    // Check 3: referenced files exist on disk
    const missingFiles = [];
    for (const filePath of implementedFiles) {
        const absPath = filePath.startsWith('/') ? filePath : join(ctx.projectPath, filePath);
        if (!existsSync(absPath)) {
            missingFiles.push(filePath);
        }
    }
    if (missingFiles.length > 0) {
        const listed = missingFiles.slice(0, 3).join(', ');
        findings.push(`${String(missingFiles.length)} file(s) listed in spec.md not found: ${listed}`);
    }
    const passed = findings.length === 0;
    const summary = passed
        ? `verify_spec_compliance: ${ctx.specId} passed all checks (${String(implementedFiles.length)} files verified)`
        : `verify_spec_compliance: ${ctx.specId} has ${String(findings.length)} finding(s): ${findings.join('; ')}`;
    return { success: passed, summary, durationMs: 0 };
}
// ---------------------------------------------------------------------------
// analyze_code_impact
// ---------------------------------------------------------------------------
/**
 * analyze_code_impact: Counts and categorizes files listed in spec.md.
 * Flags specs with >10 affected files as HIGH IMPACT.
 */
export async function analyzeCodeImpact(ctx) {
    if (!ctx.specId) {
        return { success: false, summary: 'No specId', durationMs: 0 };
    }
    const { readFile } = await import('node:fs/promises');
    const { existsSync } = await import('node:fs');
    const { join } = await import('node:path');
    const specDirPath = await findSpecDir(ctx.projectPath, ctx.specId);
    if (!specDirPath) {
        return {
            success: false,
            summary: `analyze_code_impact: spec directory not found for ${ctx.specId}`,
            durationMs: 0,
        };
    }
    const specMdPath = join(specDirPath, 'spec.md');
    if (!existsSync(specMdPath)) {
        return {
            success: false,
            summary: `analyze_code_impact: spec.md not found for ${ctx.specId}`,
            durationMs: 0,
        };
    }
    const specContent = await readFile(specMdPath, 'utf-8');
    const allFiles = extractFileRefs(specContent);
    // Breakdown by extension — normalize .test.ts/.test.js as separate categories
    const breakdown = {};
    for (const filePath of allFiles) {
        let ext;
        if (filePath.endsWith('.test.ts')) {
            ext = '.test.ts';
        }
        else if (filePath.endsWith('.test.js')) {
            ext = '.test.js';
        }
        else {
            ext = `.${filePath.split('.').pop() ?? 'unknown'}`;
        }
        breakdown[ext] = (breakdown[ext] ?? 0) + 1;
    }
    const total = allFiles.length;
    const highImpact = total > 10;
    const breakdownStr = Object.entries(breakdown)
        .map(([ext, count]) => `${String(count)} ${ext}`)
        .join(', ');
    const summary = highImpact
        ? `analyze_code_impact: HIGH IMPACT — ${String(total)} files affected (${breakdownStr}). Consider splitting the spec.`
        : `analyze_code_impact: ${String(total)} files affected (${breakdownStr})`;
    return { success: true, summary, durationMs: 0 };
}
//# sourceMappingURL=handlers-b2.js.map