/**
 * Transcript-derived active task (T-ATM ATM.1).
 *
 * THIS Claude Code version keeps the task list in the session transcript
 * (`.jsonl`), NOT at `~/.claude/tasks/<session>/<id>.json` (that path is empty
 * for live sessions). So the active-task signal must be derived from the
 * transcript the PreToolUse hook receives:
 *   - `TaskCreate` tool_use carries `input.{subject,metadata}` + a `tool_use_id`;
 *     the harness-assigned id is in the matching `tool_result` text
 *     ("Task #<id> created …") — correlate by `tool_use_id`.
 *   - `TaskUpdate` tool_use carries `{taskId, status, metadata}`.
 * The active task = the one most-recently set `in_progress` and not since
 * `completed`/`deleted`.
 *
 * Defensive per-line parse (the transcript schema is harness-owned + can shift):
 * any failure resolves to `null` (no active task) — NEVER a wrong task.
 *
 * Imports from: node:fs/promises, ../session_state.js.
 * Imported by: src/runtime/hooks/active_task_mirror.ts.
 */
import { readFile } from 'node:fs/promises';
function resultText(content) {
    if (typeof content === 'string')
        return content;
    if (Array.isArray(content)) {
        return content
            .map((c) => c !== null && typeof c === 'object' && typeof c.type === 'string'
            ? (c.text ?? '')
            : '')
            .map((t) => (typeof t === 'string' ? t : ''))
            .join('');
    }
    return '';
}
/**
 * Shared transcript walk (ATM.1 + ATM.2): parse `TaskCreate`/`TaskUpdate`
 * records into the latest state per harness task id, plus the most-recent
 * `in_progress` id (not subsequently closed). Status is SEEDED to `pending` on
 * `TaskCreate` (the harness default) so a created-but-never-updated task still
 * counts as open. `pending` overlays the in-flight TaskUpdate (H4a), applied
 * last. Defensive per-line parse; an unreadable transcript yields an empty map
 * (fail-open — never a wrong task).
 */
async function parseTranscriptTasks(transcriptPath, pending) {
    let raw;
    try {
        raw = await readFile(transcriptPath, 'utf8');
    }
    catch {
        return { taskById: new Map(), activeId: null };
    }
    const createByToolUse = new Map();
    const idByToolUse = new Map(); // tool_use_id → harness task id (from result)
    const updates = [];
    for (const line of raw.split('\n')) {
        if (line.trim().length === 0)
            continue;
        let entry;
        try {
            entry = JSON.parse(line);
        }
        catch {
            continue; // a malformed line must not abort the walk
        }
        const content = entry.message?.content;
        if (!Array.isArray(content))
            continue;
        for (const b of content) {
            if (b === null || typeof b !== 'object')
                continue;
            if (b.type === 'tool_use' && b.name === 'TaskCreate' && typeof b.id === 'string') {
                const input = b.input ?? {};
                if (typeof input.subject === 'string') {
                    const md = input.metadata !== null && typeof input.metadata === 'object'
                        ? input.metadata
                        : {};
                    createByToolUse.set(b.id, { subject: input.subject, metadata: md });
                }
            }
            else if (b.type === 'tool_use' && b.name === 'TaskUpdate') {
                const input = b.input ?? {};
                const status = typeof input.status === 'string' ? input.status : undefined;
                const md = input.metadata !== null && typeof input.metadata === 'object'
                    ? input.metadata
                    : undefined;
                // T-FIX-TASKSTART-GUARD-MIRROR: a metadata-only TaskUpdate (no status)
                // is a real mutation — dropping it left the mirror serving stale
                // metadata, which false-fired the FU.11 task_unscoped reset mid-flow
                // (observed twice, 2026-06-10).
                if (typeof input.taskId === 'string' && (status !== undefined || md !== undefined)) {
                    updates.push({
                        taskId: input.taskId,
                        ...(status !== undefined ? { status } : {}),
                        ...(md ? { metadata: md } : {}),
                    });
                }
            }
            else if (b.type === 'tool_result' && typeof b.tool_use_id === 'string') {
                const m = /Task #(\S+) created/.exec(resultText(b.content));
                if (m?.[1] !== undefined)
                    idByToolUse.set(b.tool_use_id, m[1]);
            }
        }
    }
    const taskById = new Map();
    for (const [tuid, c] of createByToolUse) {
        const id = idByToolUse.get(tuid);
        if (id !== undefined)
            taskById.set(id, { subject: c.subject, status: 'pending', metadata: { ...c.metadata } });
    }
    // H4a: apply the in-flight TaskUpdate LAST (it's not in the transcript yet).
    if (pending !== undefined)
        updates.push(pending);
    let activeId = null;
    for (const u of updates) {
        let t = taskById.get(u.taskId);
        if (t === undefined) {
            t = { subject: '', status: 'pending', metadata: {} };
            taskById.set(u.taskId, t);
        }
        if (u.metadata !== undefined)
            t.metadata = { ...t.metadata, ...u.metadata };
        // Status applied only when present — a metadata-only update mutates
        // metadata without touching activation bookkeeping.
        if (u.status !== undefined) {
            t.status = u.status;
            if (u.status === 'in_progress')
                activeId = u.taskId;
            else if ((u.status === 'completed' || u.status === 'deleted') && activeId === u.taskId) {
                activeId = null;
            }
        }
    }
    return { taskById, activeId };
}
/**
 * Resolve the active task from the session transcript, or `null` if none /
 * unreadable. Most-recent `in_progress` (not later closed) wins. `pending`
 * overlays the in-flight TaskUpdate (H4a) so a task being activated THIS tick
 * wins even though disk/transcript still lags.
 */
export async function readActiveTaskFromTranscript(transcriptPath, pending) {
    const { taskById, activeId } = await parseTranscriptTasks(transcriptPath, pending);
    if (activeId === null)
        return null;
    const t = taskById.get(activeId);
    if (t === undefined)
        return null;
    const meta = t.metadata;
    return {
        id: activeId,
        subject: t.subject,
        started_at: new Date().toISOString(),
        ...(typeof meta.taskId === 'string' ? { taskId: meta.taskId } : {}),
        ...(typeof meta.spec === 'string' ? { spec: meta.spec } : {}),
    };
}
/**
 * Latest transcript status for one harness task id (with the in-flight TaskUpdate
 * overlaid, H4a), or `null` if the id is absent/unreadable. Reuses the ATM.1 walk —
 * no forked semantics. FC.6 uses this to tell genuine transcript lag (prior still
 * `pending`/`in_progress`) from a real completion (clear).
 */
export async function transcriptTaskStatus(transcriptPath, id, pending) {
    const { taskById } = await parseTranscriptTasks(transcriptPath, pending);
    return taskById.get(id)?.status ?? null;
}
/**
 * A CLOSED status is the only case where a stale prior active-task signal must be
 * cleared. `null` (transcript hasn't caught up — genuine lag) and the open statuses
 * (`pending`/`in_progress`) both KEEP, so the ATM.3 lag-keep is preserved; only an
 * explicit `completed`/`deleted` clears (FC.6).
 */
export const isClosedStatus = (s) => s === 'completed' || s === 'deleted';
/**
 * The OPEN tasks from the transcript (latest status ∈ {`pending`,
 * `in_progress`}), each with its `metadata.taskId` provenance if present.
 * Consumed by Gate B (`task_list_generated`) to flag tasks smuggled into the
 * list without going through scope→task-spec-author. Empty on an unreadable
 * transcript (fail-open). Shares the ATM.1 walk via `parseTranscriptTasks`.
 */
export async function readOpenTasksFromTranscript(transcriptPath, pending) {
    const { taskById } = await parseTranscriptTasks(transcriptPath, pending);
    const open = [];
    for (const [id, t] of taskById) {
        if (t.status === 'completed' || t.status === 'deleted')
            continue;
        const taskId = typeof t.metadata.taskId === 'string' ? t.metadata.taskId : undefined;
        open.push({ id, status: t.status, ...(taskId !== undefined ? { taskId } : {}) });
    }
    return open;
}
//# sourceMappingURL=transcript_tasks.js.map