// engine/session-handoff-generator.ts — SPEC-398: Pure helpers for session handoff packet.
// No storage imports — all data is passed in. Storage fetching is done by the tool handler.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
/** ≤200 tokens budget — ~4 chars/token. Leave buffer for whitespace. */
const MAX_CHARS = 720;
// ---------------------------------------------------------------------------
// Pure helpers (no I/O)
// ---------------------------------------------------------------------------
/** Parse pending (unchecked) acceptance criteria from spec.md content. */
export function extractPendingCriteria(specMd, max) {
    const pending = [];
    for (const line of specMd.split('\n')) {
        if (line.includes('- [ ]')) {
            const text = line.replace(/^.*-\s*\[\s*\]\s*/, '').trim();
            if (text) {
                pending.push(text.slice(0, 65));
                if (pending.length >= max) {
                    break;
                }
            }
        }
    }
    return pending;
}
/** Compute age in minutes from a savedAt ISO string. */
export function computeSessionAgeMinutes(savedAt) {
    return Math.floor((Date.now() - new Date(savedAt).getTime()) / 60_000);
}
/** Format the handoff packet from pre-fetched data. Output ≤MAX_CHARS. */
export function buildHandoffText(data) {
    const lines = ['=== PLANU HANDOFF ==='];
    if (data.activeSpec !== null) {
        lines.push(`ACTIVE  ${data.activeSpec.id} — ${data.activeSpec.title.slice(0, 45)}`);
        for (const c of data.pendingCriteria) {
            lines.push(`        [ ] ${c}`);
        }
    }
    else {
        lines.push('ACTIVE  (none)');
    }
    if (data.nextSpec !== null) {
        lines.push(`NEXT    ${data.nextSpec.id} — ${data.nextSpec.title.slice(0, 45)}`);
    }
    if (data.gitBranch !== null) {
        const dirtyTag = data.gitStaged > 0 || data.gitModified > 0
            ? ` (${String(data.gitStaged)} staged, ${String(data.gitModified)} modified)`
            : '';
        lines.push(`GIT     ${data.gitBranch.slice(0, 45)}${dirtyTag}`);
    }
    if (data.nextStep !== null) {
        lines.push(`STEP    ${data.nextStep.slice(0, 80)}`);
    }
    lines.push('=== PASTE AT NEW SESSION START ===');
    const text = lines.join('\n');
    return text.length > MAX_CHARS ? `${text.slice(0, MAX_CHARS)}...` : text;
}
// ---------------------------------------------------------------------------
// I/O helpers (no storage — only filesystem & git)
// ---------------------------------------------------------------------------
/** Find and read pending criteria from a spec's spec.md file. */
export async function fetchPendingCriteria(projectPath, specId, max) {
    try {
        const files = await glob(`planu/specs/${specId}-*/spec.md`, { cwd: projectPath });
        if (files.length === 0) {
            return [];
        }
        const specFile = files[0];
        if (specFile === undefined) {
            return [];
        }
        const content = await readFile(join(projectPath, specFile), 'utf-8');
        return extractPendingCriteria(content, max);
    }
    catch {
        return [];
    }
}
//# sourceMappingURL=session-handoff-generator.js.map