// engine/staleness/stale-implementing.ts — SPEC-753: Stale-implementing detector
//
// Finds specs in 'implementing' status that have been there for >N days and have
// no progress.md file. These are candidates for "done but untransitioned".
import { readFile, access, readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { constants } from 'node:fs';
import { hashProjectPath } from '../../storage/base-store.js';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function fileExists(p) {
    try {
        await access(p, constants.F_OK);
        return true;
    }
    catch {
        return false;
    }
}
async function getLastTransitionForSpec(projectPath, projectId, specId) {
    const logPath = join(projectPath, 'planu', 'data', 'projects', projectId, 'transition-log.jsonl');
    try {
        const raw = await readFile(logPath, 'utf-8');
        const lines = raw.trim().split('\n').filter(Boolean);
        let lastAt = null;
        for (const line of lines) {
            try {
                const entry = JSON.parse(line);
                if (typeof entry === 'object' &&
                    entry !== null &&
                    'specId' in entry &&
                    entry.specId === specId) {
                    const at = entry.timestamp;
                    if (typeof at === 'string') {
                        lastAt = at;
                    }
                }
            }
            catch {
                // Skip malformed lines
            }
        }
        return lastAt;
    }
    catch {
        return null;
    }
}
async function getDaysInImplementing(projectPath, projectId, specId) {
    const logPath = join(projectPath, 'planu', 'data', 'projects', projectId, 'transition-log.jsonl');
    try {
        const raw = await readFile(logPath, 'utf-8');
        const lines = raw.trim().split('\n').filter(Boolean);
        let implementingAt = null;
        for (const line of lines) {
            try {
                const entry = JSON.parse(line);
                if (typeof entry === 'object' &&
                    entry !== null &&
                    'specId' in entry &&
                    'to' in entry &&
                    entry.specId === specId &&
                    entry.to === 'implementing') {
                    const at = entry.timestamp;
                    if (typeof at === 'string') {
                        implementingAt = at;
                    }
                }
            }
            catch {
                // Skip
            }
        }
        if (implementingAt) {
            const then = new Date(implementingAt).getTime();
            const now = Date.now();
            return Math.floor((now - then) / (1000 * 60 * 60 * 24));
        }
    }
    catch {
        // Fall through
    }
    return -1;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Find specs that are in 'implementing' status for more than `daysThreshold` days
 * and have no progress.md file. These are likely "done but untransitioned".
 */
export async function findStaleImplementing(params) {
    const { projectPath, daysThreshold = 7 } = params;
    const projectId = hashProjectPath(projectPath);
    const { specStore } = await import('../../storage/index.js').catch(() => ({ specStore: null }));
    if (!specStore) {
        return [];
    }
    let allSpecs;
    try {
        allSpecs = await specStore.listSpecs(projectId);
    }
    catch {
        return [];
    }
    const specs = allSpecs.filter((s) => s.status === 'implementing').map((s) => ({ id: s.id }));
    const results = [];
    for (const spec of specs) {
        const specsDir = join(projectPath, 'planu', 'specs');
        let specDir = null;
        try {
            const entries = await readdir(specsDir);
            const match = entries.find((e) => e.startsWith(`${spec.id}-`) || e === spec.id);
            if (match) {
                specDir = join(specsDir, match);
            }
        }
        catch {
            // No specs dir
        }
        const hasProgress = specDir !== null && (await fileExists(join(specDir, 'progress.md')));
        const daysInStatus = await getDaysInImplementing(projectPath, projectId, spec.id);
        const lastTransition = await getLastTransitionForSpec(projectPath, projectId, spec.id);
        if (daysInStatus >= daysThreshold || daysInStatus === -1) {
            results.push({
                specId: spec.id,
                daysInStatus,
                hasProgress,
                lastTransition,
            });
        }
    }
    return results;
}
//# sourceMappingURL=stale-implementing.js.map