import { createHash, randomUUID } from "node:crypto";
import { mkdir, open, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { basename, join, resolve } from "node:path";
const GOALS_BASE_ENV = "GG_GOALS_BASE";
const DEFAULT_PROJECT_DIR_NAME = "projects";
let writeQueue = Promise.resolve();
function goalsBaseDir() {
    return (process.env[GOALS_BASE_ENV] ?? join(homedir(), ".ezcoder", "goals", DEFAULT_PROJECT_DIR_NAME));
}
export function normalizeProjectPath(cwd) {
    return resolve(cwd);
}
function nowIso() {
    return new Date().toISOString();
}
function mergeGoalTasks(existing, input) {
    if (!input)
        return existing;
    const byId = new Map(input.map((task) => [task.id, task]));
    const merged = existing.map((task) => {
        const next = byId.get(task.id);
        if (!next)
            return task;
        return {
            ...task,
            ...next,
            status: task.status !== next.status || task.attempts > next.attempts ? task.status : next.status,
            attempts: Math.max(task.attempts, next.attempts),
            workerId: task.workerId ?? next.workerId,
            verification: task.verification ?? next.verification,
            lastSummary: task.lastSummary ?? next.lastSummary,
        };
    });
    for (const task of input) {
        if (!existing.some((item) => item.id === task.id))
            merged.push(task);
    }
    return merged;
}
function mergeGoalEvidence(existing, input) {
    if (!input)
        return existing;
    const byId = new Map(existing.map((item) => [item.id, item]));
    const merged = [...existing];
    for (const item of input) {
        if (!byId.has(item.id))
            merged.push(item);
    }
    return merged;
}
function isObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function stringArray(value) {
    return Array.isArray(value)
        ? value.filter((item) => typeof item === "string")
        : [];
}
function optionalString(value) {
    return typeof value === "string" && value.length > 0 ? value : undefined;
}
function isRunStatus(value) {
    return (value === "draft" ||
        value === "blocked" ||
        value === "ready" ||
        value === "running" ||
        value === "verifying" ||
        value === "passed" ||
        value === "failed" ||
        value === "paused");
}
function isTaskStatus(value) {
    return (value === "pending" ||
        value === "running" ||
        value === "verifying" ||
        value === "done" ||
        value === "failed" ||
        value === "blocked");
}
function isPrerequisiteStatus(value) {
    return value === "unknown" || value === "met" || value === "missing";
}
function isEvidenceKind(value) {
    return (value === "log" ||
        value === "command" ||
        value === "screenshot" ||
        value === "file" ||
        value === "summary");
}
function isEvidenceMechanism(value) {
    return (value === "command" ||
        value === "test" ||
        value === "script" ||
        value === "fixture" ||
        value === "log" ||
        value === "screenshot" ||
        value === "video" ||
        value === "browser" ||
        value === "device" ||
        value === "source" ||
        value === "file" ||
        value === "manual");
}
function isEvidencePlanStatus(value) {
    return value === "planned" || value === "ready" || value === "blocked";
}
function isVerificationStatus(value) {
    return value === "pass" || value === "fail" || value === "unknown";
}
function normalizeVerification(value) {
    if (!isObject(value))
        return undefined;
    return {
        status: isVerificationStatus(value.status) ? value.status : "unknown",
        summary: typeof value.summary === "string" ? value.summary : "",
        ...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
        ...(typeof value.exitCode === "number" ? { exitCode: value.exitCode } : {}),
        ...(optionalString(value.outputPath) ? { outputPath: optionalString(value.outputPath) } : {}),
        checkedAt: typeof value.checkedAt === "string" ? value.checkedAt : nowIso(),
    };
}
function normalizePrerequisite(value) {
    if (!isObject(value))
        return null;
    const label = typeof value.label === "string" ? value.label : "Prerequisite";
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        label,
        status: isPrerequisiteStatus(value.status) ? value.status : "unknown",
        ...(optionalString(value.checkCommand)
            ? { checkCommand: optionalString(value.checkCommand) }
            : {}),
        ...(optionalString(value.instructions)
            ? { instructions: optionalString(value.instructions) }
            : {}),
        ...(optionalString(value.evidence) ? { evidence: optionalString(value.evidence) } : {}),
    };
}
function normalizeHarnessItem(value) {
    if (!isObject(value))
        return null;
    const label = typeof value.label === "string" ? value.label : "Harness";
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        label,
        ...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
        ...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
        ...(optionalString(value.description)
            ? { description: optionalString(value.description) }
            : {}),
    };
}
function normalizeEvidencePlanItem(value) {
    if (!isObject(value))
        return null;
    const label = typeof value.label === "string" ? value.label : "Evidence path";
    const description = typeof value.description === "string" ? value.description : label;
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        label,
        mechanism: isEvidenceMechanism(value.mechanism) ? value.mechanism : "command",
        description,
        status: isEvidencePlanStatus(value.status) ? value.status : "planned",
        ...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
        ...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
        ...(optionalString(value.instructions)
            ? { instructions: optionalString(value.instructions) }
            : {}),
        ...(optionalString(value.evidence) ? { evidence: optionalString(value.evidence) } : {}),
    };
}
function normalizeTask(value) {
    if (!isObject(value))
        return null;
    const title = typeof value.title === "string" ? value.title : "Goal task";
    const prompt = typeof value.prompt === "string" ? value.prompt : title;
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        title,
        prompt,
        status: isTaskStatus(value.status) ? value.status : "pending",
        ...(optionalString(value.workerId) ? { workerId: optionalString(value.workerId) } : {}),
        attempts: typeof value.attempts === "number" && value.attempts >= 0 ? value.attempts : 0,
        ...(normalizeVerification(value.verification)
            ? { verification: normalizeVerification(value.verification) }
            : {}),
        ...(optionalString(value.lastSummary)
            ? { lastSummary: optionalString(value.lastSummary) }
            : {}),
    };
}
function normalizeEvidence(value) {
    if (!isObject(value))
        return null;
    const label = typeof value.label === "string" ? value.label : "Evidence";
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        kind: isEvidenceKind(value.kind) ? value.kind : "summary",
        label,
        ...(optionalString(value.path) ? { path: optionalString(value.path) } : {}),
        ...(optionalString(value.content) ? { content: optionalString(value.content) } : {}),
        createdAt: typeof value.createdAt === "string" ? value.createdAt : nowIso(),
    };
}
function normalizeVerifier(value) {
    if (!isObject(value))
        return undefined;
    const description = typeof value.description === "string" ? value.description : "Goal verifier";
    return {
        description,
        ...(optionalString(value.command) ? { command: optionalString(value.command) } : {}),
        ...(normalizeVerification(value.lastResult)
            ? { lastResult: normalizeVerification(value.lastResult) }
            : {}),
    };
}
function normalizeCompletionAudit(value) {
    if (!isObject(value))
        return undefined;
    return {
        status: isVerificationStatus(value.status) ? value.status : "unknown",
        summary: typeof value.summary === "string" ? value.summary : "",
        checkedAt: typeof value.checkedAt === "string" ? value.checkedAt : nowIso(),
        ...(optionalString(value.verifierCheckedAt)
            ? { verifierCheckedAt: optionalString(value.verifierCheckedAt) }
            : {}),
        ...(optionalString(value.outputPath) ? { outputPath: optionalString(value.outputPath) } : {}),
    };
}
function normalizeRun(value, fallbackProjectPath) {
    if (!isObject(value))
        return null;
    const title = typeof value.title === "string" ? value.title : "Untitled goal";
    const goal = typeof value.goal === "string" ? value.goal : title;
    const createdAt = typeof value.createdAt === "string" ? value.createdAt : nowIso();
    const projectPath = typeof value.projectPath === "string" ? value.projectPath : fallbackProjectPath;
    const prerequisites = Array.isArray(value.prerequisites)
        ? value.prerequisites
            .map(normalizePrerequisite)
            .filter((item) => !!item)
        : [];
    const tasks = Array.isArray(value.tasks)
        ? value.tasks.map(normalizeTask).filter((item) => !!item)
        : [];
    const computedStatus = deriveRunnableStatus(isRunStatus(value.status) ? value.status : "draft", prerequisites);
    return {
        id: typeof value.id === "string" ? value.id : randomUUID(),
        title,
        goal,
        status: computedStatus,
        createdAt,
        updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : createdAt,
        projectPath,
        successCriteria: stringArray(value.successCriteria),
        prerequisites,
        harness: Array.isArray(value.harness)
            ? value.harness.map(normalizeHarnessItem).filter((item) => !!item)
            : [],
        evidencePlan: Array.isArray(value.evidencePlan)
            ? value.evidencePlan
                .map(normalizeEvidencePlanItem)
                .filter((item) => !!item)
            : [],
        tasks,
        evidence: Array.isArray(value.evidence)
            ? value.evidence.map(normalizeEvidence).filter((item) => !!item)
            : [],
        ...(normalizeVerifier(value.verifier) ? { verifier: normalizeVerifier(value.verifier) } : {}),
        ...(normalizeCompletionAudit(value.completionAudit)
            ? { completionAudit: normalizeCompletionAudit(value.completionAudit) }
            : {}),
        blockers: dedupeGoalBlockers(stringArray(value.blockers)),
        ...(optionalString(value.activeWorkerId)
            ? { activeWorkerId: optionalString(value.activeWorkerId) }
            : {}),
        ...(optionalString(value.continueRequestedAt)
            ? { continueRequestedAt: optionalString(value.continueRequestedAt) }
            : {}),
    };
}
function sortNewestFirst(runs) {
    return [...runs].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
}
function isActiveGoalRun(run) {
    return (run.status === "running" ||
        run.status === "verifying" ||
        run.activeWorkerId !== undefined ||
        run.tasks.some((task) => task.status === "running" || task.status === "verifying"));
}
function omittedActiveGoalRuns(previousRuns, nextRuns) {
    const nextIds = new Set(nextRuns.map((run) => run.id));
    return previousRuns.filter((run) => isActiveGoalRun(run) && !nextIds.has(run.id));
}
export function dedupeGoalBlockers(blockers) {
    return Array.from(new Set(blockers.map((item) => item.trim()).filter(Boolean)));
}
export function appendGoalBlockers(blockers, nextBlockers) {
    const additions = typeof nextBlockers === "string" ? [nextBlockers] : (nextBlockers ?? []);
    return dedupeGoalBlockers([...blockers, ...additions]);
}
function deriveRunnableStatus(requestedStatus, prerequisites) {
    if (requestedStatus === "passed" ||
        requestedStatus === "failed" ||
        requestedStatus === "paused") {
        return requestedStatus;
    }
    if (hasBlockingGoalPrerequisites(prerequisites))
        return "blocked";
    return requestedStatus;
}
function enqueueWrite(fn) {
    const result = writeQueue.then(fn);
    writeQueue = result.then(() => { }, () => { });
    return result;
}
async function writeGoalRunsFile(cwd, runs) {
    const normalizedCwd = normalizeProjectPath(cwd);
    const dir = projectDir(normalizedCwd);
    await withGoalStoreLock(dir, async () => {
        await mkdir(dir, { recursive: true });
        const goalsPath = join(dir, "goals.json");
        const existingRuns = await readGoalRunsFile(normalizedCwd);
        const omittedActive = omittedActiveGoalRuns(existingRuns, runs);
        if (omittedActive.length > 0) {
            const timestamp = nowIso();
            const rejectedIds = new Set(omittedActive.map((run) => run.id));
            const repairedRuns = existingRuns.map((run) => rejectedIds.has(run.id)
                ? {
                    ...run,
                    evidence: [
                        ...run.evidence,
                        createGoalEvidence({
                            kind: "summary",
                            label: "Goal store write rejected",
                            content: "Rejected an attempted Goal overwrite that omitted active work; preserving existing durable state.",
                            createdAt: timestamp,
                        }),
                    ],
                    updatedAt: timestamp,
                }
                : run);
            await atomicWriteJson(goalsPath, sortNewestFirst(repairedRuns));
            await Promise.all(repairedRuns.map((run) => writeGoalProgressJournalFromRun(normalizedCwd, run)));
            return;
        }
        const sorted = sortNewestFirst([...runs]);
        await atomicWriteJson(goalsPath, sorted);
        await atomicWriteJson(join(dir, "meta.json"), {
            path: normalizedCwd,
            name: basename(normalizedCwd),
        });
        await Promise.all(sorted.map((run) => writeGoalProgressJournalFromRun(normalizedCwd, run)));
    });
}
async function withGoalStoreLock(dir, fn) {
    await mkdir(dir, { recursive: true });
    const lockPath = join(dir, "goals.lock");
    const deadline = Date.now() + 10_000;
    for (;;) {
        let handle;
        try {
            handle = await open(lockPath, "wx");
            await handle.writeFile(`${process.pid}\n${new Date().toISOString()}\n`, "utf-8");
            await handle.close();
            try {
                return await fn();
            }
            finally {
                await rm(lockPath, { force: true });
            }
        }
        catch (err) {
            await handle?.close().catch(() => undefined);
            if (err.code !== "EEXIST" || Date.now() > deadline)
                throw err;
            await new Promise((resolve) => setTimeout(resolve, 25));
        }
    }
}
async function atomicWriteJson(path, value) {
    const tmpPath = `${path}.${process.pid}.${Date.now()}.tmp`;
    await writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
    await rename(tmpPath, path);
}
async function readGoalRunsFile(cwd) {
    const normalizedCwd = normalizeProjectPath(cwd);
    try {
        const data = await readFile(join(projectDir(normalizedCwd), "goals.json"), "utf-8");
        const parsed = JSON.parse(data);
        if (!Array.isArray(parsed))
            return [];
        return sortNewestFirst(parsed
            .map((item) => normalizeRun(item, normalizedCwd))
            .filter((run) => run !== null));
    }
    catch {
        return [];
    }
}
export function hashPath(cwd) {
    return createHash("sha256").update(normalizeProjectPath(cwd)).digest("hex").slice(0, 16);
}
export function projectDir(cwd) {
    return join(goalsBaseDir(), hashPath(cwd));
}
async function discoverGoalRunsById(id) {
    try {
        const entries = await readdir(goalsBaseDir(), { withFileTypes: true });
        const matches = [];
        for (const entry of entries) {
            if (!entry.isDirectory())
                continue;
            try {
                const dir = join(goalsBaseDir(), entry.name);
                const meta = await readProjectMeta(dir);
                const fallbackProjectPath = meta?.path ?? dir;
                const data = await readFile(join(dir, "goals.json"), "utf-8");
                const parsed = JSON.parse(data);
                if (!Array.isArray(parsed))
                    continue;
                for (const item of parsed) {
                    const run = normalizeRun(item, fallbackProjectPath);
                    if (run && (run.id === id || run.id.startsWith(id)))
                        matches.push(run);
                }
            }
            catch {
                continue;
            }
        }
        return sortNewestFirst(matches)[0] ?? null;
    }
    catch {
        return null;
    }
}
async function readProjectMeta(dir) {
    try {
        const parsed = JSON.parse(await readFile(join(dir, "meta.json"), "utf-8"));
        return isObject(parsed) && typeof parsed.path === "string" ? { path: parsed.path } : null;
    }
    catch {
        return null;
    }
}
export function createGoalTask(input) {
    return {
        id: input.id ?? randomUUID(),
        title: input.title,
        prompt: input.prompt,
        status: input.status ?? "pending",
        ...(input.workerId ? { workerId: input.workerId } : {}),
        attempts: input.attempts ?? 0,
        ...(input.verification ? { verification: input.verification } : {}),
        ...(input.lastSummary ? { lastSummary: input.lastSummary } : {}),
    };
}
export function createGoalEvidence(input) {
    return {
        id: input.id ?? randomUUID(),
        kind: input.kind,
        label: input.label,
        ...(input.path ? { path: input.path } : {}),
        ...(input.content ? { content: input.content } : {}),
        createdAt: input.createdAt ?? nowIso(),
    };
}
export function createGoalRun(cwd, input) {
    const normalizedCwd = normalizeProjectPath(cwd);
    const timestamp = nowIso();
    const prerequisites = input.prerequisites ?? [];
    const status = deriveRunnableStatus(input.status ?? "draft", prerequisites);
    return {
        id: input.id ?? randomUUID(),
        title: input.title,
        goal: input.goal,
        status,
        createdAt: timestamp,
        updatedAt: timestamp,
        projectPath: normalizedCwd,
        successCriteria: input.successCriteria ?? [],
        prerequisites,
        harness: input.harness ?? [],
        evidencePlan: input.evidencePlan ?? [],
        tasks: input.tasks ?? [],
        evidence: input.evidence ?? [],
        ...(input.verifier ? { verifier: input.verifier } : {}),
        ...(input.completionAudit ? { completionAudit: input.completionAudit } : {}),
        blockers: dedupeGoalBlockers(input.blockers ?? []),
        ...(input.activeWorkerId ? { activeWorkerId: input.activeWorkerId } : {}),
        ...(input.continueRequestedAt ? { continueRequestedAt: input.continueRequestedAt } : {}),
    };
}
export async function loadGoalRuns(cwd) {
    return readGoalRunsFile(cwd);
}
export async function saveGoalRuns(cwd, runs) {
    return enqueueWrite(() => writeGoalRunsFile(cwd, runs));
}
export async function reconcileActiveGoalRuns(cwd, options = {}) {
    return enqueueWrite(async () => {
        const runs = await loadGoalRuns(cwd);
        const timestamp = nowIso();
        const repairedRunIds = [];
        let evidenceCount = 0;
        const nextRuns = runs.map((run) => {
            let next = run;
            const evidence = [];
            const blockers = new Set(run.blockers);
            let repaired = false;
            if (run.activeWorkerId && !options.isWorkerActive?.(run.activeWorkerId, run)) {
                const workerId = run.activeWorkerId;
                next = { ...next, activeWorkerId: undefined };
                evidence.push(createGoalEvidence({
                    kind: "summary",
                    label: "Goal worker reconciled",
                    content: `Cleared stale activeWorkerId ${workerId}; no in-memory Goal worker was observed after startup/runtime reconciliation.`,
                    createdAt: timestamp,
                }));
                repaired = true;
            }
            const tasks = next.tasks.map((task) => {
                if (task.status !== "running" && task.status !== "verifying")
                    return task;
                const workerId = task.workerId;
                if (workerId && options.isWorkerActive?.(workerId, run))
                    return task;
                repaired = true;
                evidence.push(createGoalEvidence({
                    kind: "summary",
                    label: "Goal task reconciled",
                    content: `Reset stale task "${task.title}" (${task.id}) from ${task.status} to pending; no in-memory worker/verifier was observed.`,
                    createdAt: timestamp,
                }));
                return {
                    ...task,
                    status: "pending",
                    lastSummary: `Reset from stale ${task.status} state during Goal reconciliation.`,
                };
            });
            next = { ...next, tasks };
            if (next.status === "running") {
                const hasRunningWorker = next.activeWorkerId !== undefined &&
                    options.isWorkerActive?.(next.activeWorkerId, run) === true;
                const hasActiveTask = next.tasks.some((task) => (task.status === "running" || task.status === "verifying") &&
                    task.workerId !== undefined &&
                    options.isWorkerActive?.(task.workerId, run) === true);
                if (!hasRunningWorker && !hasActiveTask) {
                    next = { ...next, status: "ready" };
                    repaired = true;
                }
            }
            else if (next.status === "verifying" && !options.isVerifierActive?.(next)) {
                const blocker = "Verifier was interrupted; rerun or continue the Goal to verify again.";
                blockers.add(blocker);
                evidence.push(createGoalEvidence({
                    kind: "summary",
                    label: "Goal verifier reconciled",
                    content: "Reset stale verifying state to ready because no in-memory verifier process was observed.",
                    createdAt: timestamp,
                }));
                next = { ...next, status: "ready" };
                repaired = true;
            }
            if (!repaired)
                return run;
            repairedRunIds.push(run.id);
            evidenceCount += evidence.length;
            return {
                ...next,
                evidence: [...next.evidence, ...evidence],
                blockers: dedupeGoalBlockers([...blockers]),
                updatedAt: timestamp,
            };
        });
        if (repairedRunIds.length > 0)
            await writeGoalRunsFile(cwd, nextRuns);
        return { runs: sortNewestFirst(nextRuns), repairedRunIds, evidenceCount };
    });
}
export async function upsertGoalRun(cwd, input) {
    return enqueueWrite(async () => {
        const runs = await loadGoalRuns(cwd);
        const existingIndex = input.id ? runs.findIndex((run) => run.id === input.id) : -1;
        const existing = existingIndex >= 0 ? runs[existingIndex] : undefined;
        const merged = existing
            ? {
                ...existing,
                ...input,
                id: existing.id,
                projectPath: normalizeProjectPath(cwd),
                createdAt: existing.createdAt,
                updatedAt: nowIso(),
                successCriteria: input.successCriteria ?? existing.successCriteria,
                prerequisites: input.prerequisites ?? existing.prerequisites,
                harness: input.harness ?? existing.harness,
                evidencePlan: input.evidencePlan ?? existing.evidencePlan,
                tasks: mergeGoalTasks(existing.tasks, input.tasks),
                evidence: mergeGoalEvidence(existing.evidence, input.evidence),
                blockers: input.blockers
                    ? dedupeGoalBlockers(input.blockers)
                    : dedupeGoalBlockers(existing.blockers),
                status: deriveRunnableStatus(input.status ?? existing.status, input.prerequisites ?? existing.prerequisites),
            }
            : createGoalRun(cwd, input);
        const nextRuns = existingIndex >= 0 ? [...runs] : [merged, ...runs];
        if (existingIndex >= 0)
            nextRuns[existingIndex] = merged;
        await writeGoalRunsFile(cwd, nextRuns);
        return merged;
    });
}
export async function getGoalRun(cwd, id) {
    const runs = await loadGoalRuns(cwd);
    return (runs.find((run) => run.id === id || run.id.startsWith(id)) ?? (await discoverGoalRunsById(id)));
}
export async function getActiveGoalRun(cwd) {
    const runs = await loadGoalRuns(cwd);
    return (runs.find((run) => run.status === "running" || run.status === "verifying") ??
        runs.find((run) => run.status === "ready" || run.status === "blocked") ??
        runs.find((run) => run.status === "draft" || run.status === "paused") ??
        runs[0] ??
        null);
}
export async function appendGoalDecision(cwd, runId, decision) {
    const parts = [`kind=${decision.kind}`];
    if ("reason" in decision && decision.reason)
        parts.push(`reason=${decision.reason}`);
    if ("content" in decision && decision.content)
        parts.push(decision.content);
    if ("task" in decision && decision.task) {
        const task = decision.task;
        parts.push(`task=${task.id}`, `title=${task.title}`);
        if (task.workerId)
            parts.push(`worker=${task.workerId}`);
    }
    if ("attempts" in decision && typeof decision.attempts === "number")
        parts.push(`attempts=${decision.attempts}`);
    if ("workerId" in decision && decision.workerId)
        parts.push(`worker=${decision.workerId}`);
    if ("command" in decision && decision.command)
        parts.push(`verifier=${decision.command}`);
    if ("status" in decision && decision.status)
        parts.push(`status=${decision.status}`);
    parts.push(`timestamp=${nowIso()}`);
    return appendGoalEvidence(cwd, runId, {
        kind: "summary",
        label: `Goal decision: ${decision.kind}`,
        content: parts.join("; "),
    });
}
export async function appendGoalEvidence(cwd, runId, input) {
    const discovered = await getGoalRun(cwd, runId);
    const writeCwd = discovered?.projectPath ?? cwd;
    return enqueueWrite(async () => {
        const runs = await loadGoalRuns(writeCwd);
        const index = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
        if (index === -1)
            return null;
        const run = runs[index];
        const updated = {
            ...run,
            evidence: [...run.evidence, createGoalEvidence(input)],
            updatedAt: nowIso(),
        };
        const nextRuns = [...runs];
        nextRuns[index] = updated;
        await writeGoalRunsFile(writeCwd, nextRuns);
        return updated;
    });
}
export async function updateGoalTask(cwd, runId, taskId, patch) {
    const discovered = await getGoalRun(cwd, runId);
    const writeCwd = discovered?.projectPath ?? cwd;
    return enqueueWrite(async () => {
        const runs = await loadGoalRuns(writeCwd);
        const runIndex = runs.findIndex((run) => run.id === runId || run.id.startsWith(runId));
        if (runIndex === -1)
            return null;
        const run = runs[runIndex];
        const taskIndex = run.tasks.findIndex((task) => task.id === taskId || task.id.startsWith(taskId));
        const tasks = [...run.tasks];
        if (taskIndex === -1) {
            if ("title" in patch && "prompt" in patch && patch.title && patch.prompt) {
                tasks.push(createGoalTask({
                    ...patch,
                    title: patch.title,
                    prompt: patch.prompt,
                }));
            }
            else {
                return null;
            }
        }
        else {
            const existingTask = tasks[taskIndex];
            tasks[taskIndex] = {
                ...existingTask,
                ...patch,
                id: existingTask.id,
                title: patch.title ?? existingTask.title,
                prompt: patch.prompt ?? existingTask.prompt,
            };
        }
        const updated = { ...run, tasks, updatedAt: nowIso() };
        const nextRuns = [...runs];
        nextRuns[runIndex] = updated;
        await writeGoalRunsFile(writeCwd, nextRuns);
        return updated;
    });
}
export function isBlockingGoalPrerequisite(item) {
    return item.status !== "met" || !item.evidence?.trim();
}
export function hasBlockingGoalPrerequisites(prerequisites) {
    return prerequisites.some(isBlockingGoalPrerequisite);
}
export function goalHasBlockingPrerequisites(run) {
    return hasBlockingGoalPrerequisites(run.prerequisites);
}
export function formatGoalPrerequisiteInstruction(item) {
    const instructions = item.instructions?.trim();
    if (instructions)
        return instructions;
    if (item.status === "met" && !item.evidence?.trim()) {
        return "Prerequisite is marked met but has no recorded check evidence; verify it locally and record non-secret evidence.";
    }
    if (item.status === "unknown") {
        return "Check this prerequisite locally and record non-secret evidence before workers can start.";
    }
    return "User must provide this prerequisite.";
}
export function formatGoalBlockingPrerequisiteList(prerequisites) {
    const missing = prerequisites.filter(isBlockingGoalPrerequisite);
    if (missing.length === 0)
        return "Goal has no missing user prerequisites.";
    return missing
        .map((item) => `${item.label}: ${formatGoalPrerequisiteInstruction(item)}`)
        .join("; ");
}
export function formatGoalBlockingPrerequisites(run) {
    return formatGoalBlockingPrerequisiteList(run.prerequisites);
}
export function summarizeGoalCountsFromRuns(runs) {
    const counts = {
        total: runs.length,
        active: 0,
        blocked: 0,
        pending: 0,
        running: 0,
        passed: 0,
        failed: 0,
    };
    for (const run of runs) {
        if (run.status === "blocked")
            counts.blocked++;
        if (run.status === "running" || run.status === "verifying")
            counts.running++;
        if (run.status === "passed")
            counts.passed++;
        if (run.status === "failed")
            counts.failed++;
        if (run.status === "draft" || run.status === "ready" || run.status === "paused")
            counts.pending++;
        if (run.status !== "passed" && run.status !== "failed")
            counts.active++;
    }
    return counts;
}
export async function summarizeGoalCounts(cwd) {
    return summarizeGoalCountsFromRuns(await loadGoalRuns(cwd));
}
async function writeGoalProgressJournalFromRun(cwd, run) {
    const dir = join(projectDir(cwd), "journals");
    await mkdir(dir, { recursive: true });
    const path = join(dir, `${run.id}.md`);
    const lines = [
        `# ${run.title}`,
        "",
        `Status: ${run.status}`,
        `Goal: ${run.goal}`,
        "",
        "## Success criteria",
        ...(run.successCriteria.length
            ? run.successCriteria.map((item) => `- ${item}`)
            : ["- none recorded"]),
        "",
        "## Prerequisites",
        ...(run.prerequisites.length
            ? run.prerequisites.map((item) => `- [${item.status}] ${item.label}${item.evidence ? ` — ${item.evidence}` : ""}`)
            : ["- none"]),
        "",
        "## Tasks",
        ...(run.tasks.length
            ? run.tasks.map((task) => `- [${task.status}] ${task.title} (attempts: ${task.attempts})${task.lastSummary ? ` — ${task.lastSummary}` : ""}`)
            : ["- none"]),
        "",
        "## Verifier",
        run.verifier?.lastResult
            ? `- ${run.verifier.lastResult.status}: ${run.verifier.lastResult.summary}${run.verifier.lastResult.outputPath ? ` (${run.verifier.lastResult.outputPath})` : ""}`
            : `- ${run.verifier?.command ?? "none"}`,
        "",
        "## Final completion audit",
        run.completionAudit
            ? `- ${run.completionAudit.status}: ${run.completionAudit.summary}${run.completionAudit.outputPath ? ` (${run.completionAudit.outputPath})` : ""}`
            : "- none",
        "",
        "## Blockers",
        ...(run.blockers.length ? run.blockers.map((item) => `- ${item}`) : ["- none"]),
        "",
        "## Recent evidence",
        ...run.evidence
            .slice(-10)
            .map((item) => `- ${item.createdAt} [${item.kind}] ${item.label}${item.path ? ` (${item.path})` : ""}${item.content ? ` — ${item.content}` : ""}`),
        "",
    ];
    await writeFile(path, lines.join("\n"), "utf-8");
    return path;
}
export async function writeGoalProgressJournal(cwd, runId) {
    const run = await getGoalRun(cwd, runId);
    if (!run)
        return null;
    return writeGoalProgressJournalFromRun(run.projectPath, run);
}
//# sourceMappingURL=goal-store.js.map