// engine/session/checkpoint-writer.ts — Writes a compact session checkpoint to
// planu/session-context.md (SPEC-563). Fire-and-forget safe — never throws to caller.
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { sessionTracker } from './session-tracker.js';
const MAX_DISPLAYED_CALLS = 5;
const MAX_DISPLAYED_ERRORS = 3;
const MAX_DISPLAYED_FILES = 10;
const CHECKPOINT_HEADER_PREFIX = '## Session Checkpoint ';
/** Build the compact markdown block for a checkpoint (max ~300 tokens). */
export function buildCheckpointBlock(snapshot) {
    const ts = new Date()
        .toISOString()
        .replace('T', ' ')
        .replace(/\.\d+Z$/, ' UTC');
    const lines = [`${CHECKPOINT_HEADER_PREFIX}${ts}`, ''];
    lines.push(`**Calls this session:** ${String(snapshot.callCount)}`);
    const recent = snapshot.lastCalls.slice(-MAX_DISPLAYED_CALLS);
    if (recent.length > 0) {
        lines.push('');
        lines.push('**Recent calls:**');
        for (const c of recent) {
            const icon = c.success ? '✓' : '✗';
            lines.push(`- ${icon} ${c.tool}`);
        }
    }
    const files = snapshot.modifiedFiles.slice(0, MAX_DISPLAYED_FILES);
    if (files.length > 0) {
        lines.push('');
        lines.push('**Modified files:**');
        for (const f of files) {
            lines.push(`- ${f}`);
        }
        if (snapshot.modifiedFiles.length > MAX_DISPLAYED_FILES) {
            lines.push(`  … and ${String(snapshot.modifiedFiles.length - MAX_DISPLAYED_FILES)} more`);
        }
    }
    const errors = snapshot.errors.slice(-MAX_DISPLAYED_ERRORS);
    if (errors.length > 0) {
        lines.push('');
        lines.push('**Errors:**');
        for (const e of errors) {
            lines.push(`- ${e}`);
        }
    }
    lines.push('');
    return lines.join('\n');
}
/** Remove old checkpoint sections and prepend the new one to keep the file small. */
export function replaceLatestCheckpoint(existing, newBlock) {
    // Remove any previous "## Session Checkpoint ..." sections (including their body)
    const pattern = new RegExp(CHECKPOINT_HEADER_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +
        '[^\n]*\n[\\s\\S]*?(?=\n## |$)', 'g');
    const withoutOld = existing.replace(pattern, '');
    const trimmed = withoutOld.trimStart();
    return `${newBlock}\n${trimmed}`.trimEnd() + '\n';
}
/**
 * Write a compact checkpoint to `<projectPath>/planu/session-context.md`.
 * Replaces any previous checkpoint section — idempotent.
 * May throw — callers should catch or use maybeWriteCheckpoint.
 */
export async function writeCheckpoint(projectPath, snapshot) {
    const planuDir = join(projectPath, 'planu');
    const filePath = join(planuDir, 'session-context.md');
    let existing = '';
    try {
        existing = await readFile(filePath, 'utf8');
    }
    catch {
        // File may not exist yet — fine
    }
    const newBlock = buildCheckpointBlock(snapshot);
    const updated = replaceLatestCheckpoint(existing, newBlock);
    await mkdir(planuDir, { recursive: true });
    await writeFile(filePath, updated, 'utf8');
    void (async () => {
        try {
            const { planuAutoCommit } = await import('../git/planu-autocommit.js');
            await planuAutoCommit({ projectPath, reason: 'session-checkpoint' });
        }
        catch {
            /* best-effort — never blocks checkpoint writer */
        }
    })();
}
/**
 * Fire-and-forget helper: records the tool call, then writes a checkpoint if
 * the policy threshold is reached and projectPath is available.
 *
 * Call this from safe-handler.ts after each tool response.
 */
export function maybeWriteCheckpoint(toolName, success, projectPath) {
    sessionTracker.recordCall(toolName, success);
    if (sessionTracker.shouldCheckpoint() && projectPath !== undefined) {
        void writeCheckpoint(projectPath, sessionTracker.getSnapshot()).catch(() => {
            /* resilient — never affects tool response */
        });
    }
}
//# sourceMappingURL=checkpoint-writer.js.map