// engine/agile-config-builder.ts — Build full AgileConfig from detection results (SPEC-033)
// Extends the existing agile-detector with AgileConfig construction.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// === Detection signals ===
const SPRINT_BRANCH_RE = /^(?:sprint[-/])(\d+)/i;
const KANBAN_WIP_RE = /wip[-_]?limit\s*[:=]\s*(\d+)/i;
const PAIR_FILE_RE = /\.pairs?(?:\.json|\.yml|\.yaml)?$/;
const COMMIT_MULTIPLE_DAILY_THRESHOLD = 3;
// === Internal helpers ===
/** Reads a file safely, returns empty string on error */
async function safeRead(filePath) {
    try {
        return await readFile(filePath, 'utf-8');
    }
    catch {
        return '';
    }
}
/** Extracts sprint number from a branch name like "sprint-5" or "sprint/2025-01-15" */
export function extractSprintNumber(branchName) {
    const match = SPRINT_BRANCH_RE.exec(branchName);
    if (match?.[1]) {
        const n = parseInt(match[1], 10);
        /* v8 ignore next 1 */ // regex uses \d+ so parseInt never returns NaN
        return isNaN(n) ? undefined : n;
    }
    return undefined;
}
/** Extracts WIP limit from kanban config file content */
export function extractWipLimit(content) {
    const match = KANBAN_WIP_RE.exec(content);
    if (match?.[1]) {
        const n = parseInt(match[1], 10);
        /* v8 ignore next 1 */ // regex uses \d+ so parseInt never returns NaN
        return isNaN(n) ? undefined : n;
    }
    // Try JSON parse for .kanban.json format
    try {
        const json = JSON.parse(content);
        const wip = json.wipLimit ?? json.wip_limit ?? json['wip-limit'];
        if (typeof wip === 'number') {
            return wip;
        }
    }
    catch {
        // not JSON
    }
    return undefined;
}
// === Process detection helpers ===
/** Detects Scrum signals from branch names and files */
async function detectScrumSignals(projectPath) {
    const signals = [];
    // Check for sprint-backlog.md or SPRINT.md
    for (const f of ['sprint-backlog.md', 'SPRINT.md']) {
        const content = await safeRead(join(projectPath, f));
        if (content.length > 0) {
            signals.push(`sprint-file: ${f}`);
        }
    }
    // Check .github/ISSUE_TEMPLATE for story-points
    try {
        const templates = await glob('.github/ISSUE_TEMPLATE/**/*.{md,yml,yaml}', {
            cwd: projectPath,
            nodir: true,
        });
        for (const t of templates) {
            const content = await safeRead(join(projectPath, t));
            if (/story[-\s]?point/i.test(content)) {
                signals.push('issue-template-story-points');
                break;
            }
        }
    }
    catch {
        // ignore
    }
    return signals;
}
/** Detects Kanban signals from config files */
async function detectKanbanSignals(projectPath) {
    const signals = [];
    let wipLimit;
    for (const f of ['KANBAN.md', '.kanban.json', 'kanban-config.json']) {
        const content = await safeRead(join(projectPath, f));
        if (content.length > 0) {
            signals.push(`kanban-file: ${f}`);
            const extracted = extractWipLimit(content);
            if (extracted !== undefined) {
                wipLimit = extracted;
            }
        }
    }
    return { signals, wipLimit };
}
/** Detects Shape Up signals from pitch directories and files */
async function detectShapeUpSignals(projectPath) {
    const signals = [];
    for (const dir of ['shapeup', 'pitches', 'cycles']) {
        try {
            const files = await glob(`${dir}/**`, { cwd: projectPath, nodir: true });
            if (files.length > 0) {
                signals.push(`shapeup-directory: ${dir}`);
                break;
            }
        }
        catch {
            // ignore
        }
    }
    for (const f of ['PITCH.md', 'pitch.md', 'appetite.md']) {
        const content = await safeRead(join(projectPath, f));
        if (content.length > 0 && /appetite|pitch/i.test(content)) {
            signals.push(`pitch-file: ${f}`);
        }
    }
    return signals;
}
/** Detects SAFe signals from PI planning files */
async function detectSafeSignals(projectPath) {
    const signals = [];
    for (const f of ['pi-objectives.md', 'PI-PLANNING.md', 'program-backlog.md']) {
        const content = await safeRead(join(projectPath, f));
        if (content.length > 0) {
            signals.push(`safe-file: ${f}`);
        }
    }
    // Check docs/safe/ directory
    try {
        const safeFiles = await glob('docs/safe/**', { cwd: projectPath, nodir: true });
        if (safeFiles.length > 0) {
            signals.push('docs-safe-directory');
        }
    }
    catch {
        // ignore
    }
    return signals;
}
/** Detects XP signals from pair config and feature flags */
async function detectXpSignals(projectPath) {
    const signals = [];
    // Check for pair programming config files
    try {
        const pairFiles = await glob('.pair*', { cwd: projectPath });
        const hasPairFile = pairFiles.some((f) => PAIR_FILE_RE.test(f));
        if (hasPairFile) {
            signals.push('pair-programming-config');
        }
    }
    catch {
        // ignore
    }
    // Check package.json for feature flag dependencies
    const pkg = await safeRead(join(projectPath, 'package.json'));
    for (const dep of ['launchdarkly-js-client-sdk', 'statsig-js', 'growthbook']) {
        if (pkg.includes(dep)) {
            signals.push(`feature-flags: ${dep}`);
            break;
        }
    }
    return signals;
}
// === Main detector ===
/**
 * Detects the agile process from repository signals (files, configs).
 * Returns an AgileProcessDetection with the detected process and config.
 */
export async function detectAgileProcess(projectPath) {
    const [scrumSignals, kanbanResult, shapeUpSignals, safeSignals, xpSignals] = await Promise.all([
        detectScrumSignals(projectPath),
        detectKanbanSignals(projectPath),
        detectShapeUpSignals(projectPath),
        detectSafeSignals(projectPath),
        detectXpSignals(projectPath),
    ]);
    const { signals: kanbanSignals, wipLimit } = kanbanResult;
    const allIndicators = {
        scrum: scrumSignals,
        kanban: kanbanSignals,
        'shape-up': shapeUpSignals,
        safe: safeSignals,
        xp: xpSignals,
    };
    // Priority: SAFe > Shape Up > Scrum > Kanban > XP
    let process = 'unknown';
    let confidence = 'none';
    let indicators = [];
    if (safeSignals.length >= 1) {
        process = 'safe';
        confidence = 'certain';
        indicators = safeSignals;
    }
    else if (shapeUpSignals.length >= 2) {
        process = 'shape-up';
        confidence = 'certain';
        indicators = shapeUpSignals;
    }
    else if (shapeUpSignals.length === 1) {
        process = 'shape-up';
        confidence = 'probable';
        indicators = shapeUpSignals;
    }
    else if (scrumSignals.length >= 2) {
        process = 'scrum';
        confidence = 'certain';
        indicators = scrumSignals;
    }
    else if (scrumSignals.length === 1) {
        process = 'scrum';
        confidence = 'probable';
        indicators = scrumSignals;
    }
    else if (kanbanSignals.length >= 1) {
        process = 'kanban';
        confidence = 'probable';
        indicators = kanbanSignals;
    }
    else if (xpSignals.length >= 2) {
        process = 'xp';
        confidence = 'probable';
        indicators = xpSignals;
    }
    else {
        // Collect all signals for reporting
        const allSignals = Object.values(allIndicators).flat();
        if (allSignals.length > 0) {
            confidence = 'inferred';
            indicators = allSignals;
        }
    }
    const agileConfig = buildAgileConfig(process, wipLimit);
    return { process, confidence, indicators, agileConfig };
}
/**
 * Builds an AgileConfig with default parameters for the detected process.
 * WIP limit is passed when available from Kanban config files.
 */
export function buildAgileConfig(process, wipLimit) {
    const base = { process };
    switch (process) {
        case 'scrum':
            return { ...base, sprintDuration: 14 }; // Default 2-week sprint
        case 'kanban':
            return { ...base, wipLimit };
        case 'shape-up':
            return { ...base, cycleDuration: 6 }; // 6-week cycles
        case 'unknown':
        case 'safe':
        case 'xp':
        case 'dual-track':
            return base;
        /* v8 ignore next 2 */ // TypeScript exhaustiveness — all AgileProcess values are handled above
        default:
            return base;
    }
}
/**
 * Estimates story points from devHours using the Fibonacci scale.
 * Maps: 0-4h → 1, 4-8h → 3, 1-2d → 5, 2-4d → 8, 4-8d → 13, 8d+ → 21
 */
export function estimateStoryPoints(devHours) {
    if (devHours <= 4) {
        return 1;
    }
    if (devHours <= 8) {
        return 3;
    }
    if (devHours <= 16) {
        return 5;
    }
    if (devHours <= 32) {
        return 8;
    }
    if (devHours <= 64) {
        return 13;
    }
    return 21;
}
/**
 * Returns the daily commit count threshold used to detect XP.
 */
export function getXpCommitThreshold() {
    return COMMIT_MULTIPLE_DAILY_THRESHOLD;
}
//# sourceMappingURL=agile-config-builder.js.map