"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskScopeRecoveryError = void 0;
exports.previewTaskScopeRecoverySync = previewTaskScopeRecoverySync;
exports.applyTaskScopeRecoverySync = applyTaskScopeRecoverySync;
const node_child_process_1 = require("node:child_process");
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_util_1 = require("node:util");
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const lifecycle_run_context_1 = require("../lifecycle-run-context");
const paths_1 = require("../paths");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const task_attempts_1 = require("./task-attempts");
const task_contract_impact_1 = require("./task-contract-impact");
const task_cycle_runtime_attribution_1 = require("./task-cycle-runtime-attribution");
const task_lifecycle_1 = require("./task-lifecycle");
const task_lifecycle_timestamps_1 = require("./task-lifecycle-timestamps");
const SHA256_PATTERN = /^(?:sha256:)?[a-f0-9]{64}$/;
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/;
class TaskScopeRecoveryError extends Error {
    code;
    classification;
    constructor(code, message, classification = null) {
        super(message);
        this.name = "TaskScopeRecoveryError";
        this.code = code;
        this.classification = classification;
    }
}
exports.TaskScopeRecoveryError = TaskScopeRecoveryError;
const plans = new WeakMap();
function previewTaskScopeRecoverySync(request) {
    validateRequest(request);
    const pair = resolvePair(request.repositoryRoot, request.taskDefinitionPath);
    const taskText = readUtf8(pair.task.runtimePath, pair.task.repositoryRelativePath);
    const summaryText = readUtf8(pair.summary.runtimePath, pair.summary.repositoryRelativePath);
    const observedTaskRevision = sha256(taskText);
    const observedSummaryRevision = sha256(summaryText);
    const declaredPaths = readSectionBullets(taskText, "Implementation Target Paths");
    const inspection = (0, task_contract_impact_1.inspectTaskContractImpact)({
        repositoryRoot: pair.repositoryRoot,
        targetPaths: declaredPaths,
        validationCommandsText: taskText
    });
    const expectedFingerprint = normalizeSha(request.contractImpactFingerprint, true);
    assertRunEligible(pair.repositoryRoot, request.runDirectory, pair.task.repositoryRelativePath);
    const replay = readScopeRecoveryHistory(taskText).find((entry) => entry.attemptId === request.attemptId
        && entry.taskRevisionBefore === normalizeSha(request.taskRevision, false)
        && entry.contractImpactFingerprint === expectedFingerprint
        && entry.baselineId === normalizeSha(request.baselineId, true)
        && entry.recordedBy === request.recordedBy.trim()
        && entry.recordedAt === request.recordedAt
        && entry.reason === request.reason.trim()
        && entry.successorAttemptId === normalizeOptionalId(request.successorAttemptId));
    if (replay !== undefined) {
        return replayResult({
            request,
            pair,
            taskText,
            summaryText,
            inspection,
            record: replay
        });
    }
    if (inspection.fingerprint !== expectedFingerprint) {
        throw new TaskScopeRecoveryError("stale-impact-fingerprint", `Scope recovery expected contract-impact fingerprint '${expectedFingerprint}', but current evidence is '${inspection.fingerprint}'.`);
    }
    if (observedTaskRevision !== normalizeSha(request.taskRevision, false)) {
        throw new TaskScopeRecoveryError("stale-task-revision", `Scope recovery expected task revision '${normalizeSha(request.taskRevision, false)}', but current bytes are '${observedTaskRevision}'.`);
    }
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: taskText,
        implementationSummary: summaryText
    });
    if (projection.blockingFindings.length > 0) {
        throw new TaskScopeRecoveryError("pair-invalid", `Task pair is not structurally recoverable: ${projection.blockingFindings.map((finding) => finding.message).join(" ")}`);
    }
    const current = projection.summary?.currentAttempt ?? null;
    if (projection.task.status !== "draft"
        || projection.task.readiness !== "ready_to_implement"
        || current?.attemptId !== request.attemptId
        || current.closedAt !== null
        || current.reviewDisposition !== "pending"
        || (current.implementationStatus !== "pending" && current.implementationStatus !== "in_progress")) {
        throw new TaskScopeRecoveryError("attempt-mismatch", "Scope recovery requires one selected open pending attempt on a draft ready_to_implement task.");
    }
    if (current.openedAt !== null && Date.parse(request.recordedAt) < Date.parse(current.openedAt)) {
        throw new TaskScopeRecoveryError("invalid-input", "Scope recovery cannot be recorded before the selected attempt opened.");
    }
    const baseline = (0, task_cycle_runtime_attribution_1.readAttemptRuntimeBaseline)(pair.summary.repositoryRelativePath, summaryText, request.attemptId);
    if (baseline === null || baseline.baselineId !== normalizeSha(request.baselineId, true)) {
        throw new TaskScopeRecoveryError("baseline-mismatch", "Scope recovery requires the exact immutable current-attempt baseline identity.");
    }
    const delta = classifyDelta(inspection, declaredPaths, request.proposedPaths);
    const validationDelta = classifyValidationDelta(inspection, taskText, request.proposedValidationCommands);
    assertMaterialBoundary(inspection, delta, validationDelta);
    const additions = delta.filter((entry) => entry.eligible && entry.classification !== "already-declared")
        .map((entry) => entry.path)
        .sort();
    if (additions.length === 0 && validationDelta.length === 0) {
        throw new TaskScopeRecoveryError("incomplete-delta", "Current impact evidence exposes no deterministic additive path or validation obligation to recover.");
    }
    const observations = additions.map((targetPath) => inspectTarget(pair.repositoryRoot, targetPath));
    const changedAdditions = observations.filter((entry) => entry.dirty).map((entry) => entry.path).sort();
    const untouchedAdditions = observations.filter((entry) => !entry.dirty).map((entry) => entry.path).sort();
    const mode = changedAdditions.length === 0 ? "amendment" : "successor";
    const successorAttemptId = normalizeOptionalId(request.successorAttemptId);
    if (mode === "successor" && successorAttemptId === null) {
        throw new TaskScopeRecoveryError("invalid-input", "Already-changed additive obligations require --successor-attempt-id for the attribution-preserving successor.");
    }
    if (successorAttemptId !== null
        && projection.summary?.attempts.some((attempt) => attempt.attemptId === successorAttemptId)) {
        throw new TaskScopeRecoveryError("attempt-mismatch", `Successor attempt identity '${successorAttemptId}' already exists.`);
    }
    const recoveryId = createRecoveryId({
        taskPath: pair.task.repositoryRelativePath,
        attemptId: request.attemptId,
        successorAttemptId,
        taskRevision: observedTaskRevision,
        baselineId: baseline.baselineId,
        fingerprint: expectedFingerprint,
        additions,
        validations: validationDelta,
        recordedAt: request.recordedAt
    });
    const record = {
        recoveryId,
        attemptId: request.attemptId,
        successorAttemptId: mode === "successor" ? successorAttemptId : null,
        mode,
        taskRevisionBefore: observedTaskRevision,
        summaryRevisionBefore: observedSummaryRevision,
        contractImpactFingerprint: expectedFingerprint,
        baselineId: baseline.baselineId,
        addedPaths: additions,
        addedValidationCommands: validationDelta,
        recordedBy: request.recordedBy.trim(),
        recordedAt: request.recordedAt,
        reason: request.reason.trim()
    };
    let nextTaskText = appendSectionBullets(taskText, "Implementation Target Paths", additions);
    nextTaskText = appendValidationCommands(nextTaskText, validationDelta);
    nextTaskText = removeEmptyManualException(nextTaskText);
    nextTaskText = appendScopeRecoveryHistory(nextTaskText, record);
    const rebound = (0, task_lifecycle_1.rebindTaskReadinessAuthorizationRevision)(nextTaskText);
    if (!rebound.changed || rebound.revision === null) {
        throw new TaskScopeRecoveryError("pair-invalid", "Scope recovery could not bind the deterministic additive revision to the preserved readiness authorization.");
    }
    nextTaskText = rebound.text;
    let nextSummaryText = summaryText;
    const sharedAttemptFields = {
        scope_recovery_id: recoveryId,
        scope_recovery_fingerprint: expectedFingerprint,
        scope_recovery_impact_fingerprint: expectedFingerprint,
        scope_recovery_added_paths: JSON.stringify(additions),
        scope_recovery_added_validations: JSON.stringify(validationDelta),
        scope_recovery_recorded_by: request.recordedBy.trim(),
        scope_recovery_recorded_at: request.recordedAt,
        scope_recovery_reason: request.reason.trim()
    };
    if (mode === "amendment") {
        nextSummaryText = updateAttemptFields(nextSummaryText, request.attemptId, {
            ...sharedAttemptFields,
            scope_recovery_mode: "amendment",
            supplemental_runtime_attribution_baseline: JSON.stringify({
                schemaVersion: 1,
                scopeRecoveryId: recoveryId,
                taskCycleId: request.attemptId,
                contractImpactFingerprint: expectedFingerprint,
                capturedBy: request.recordedBy.trim(),
                capturedAt: request.recordedAt,
                targets: observations
            })
        });
    }
    else {
        const successor = successorAttemptId;
        nextSummaryText = updateAttemptFields(nextSummaryText, request.attemptId, {
            ...sharedAttemptFields,
            scope_recovery_mode: "successor",
            scope_recovery_successor_attempt_id: successor,
            review_disposition: "contract_gap",
            closed_at: request.recordedAt,
            disposition_by: request.recordedBy.trim(),
            disposition_at: request.recordedAt,
            disposition_reason: request.reason.trim()
        });
        const readinessId = projection.task.readinessAuthorization.id;
        const authorizationKind = projection.task.readinessAuthorization.kind;
        if (readinessId === null || authorizationKind === null) {
            throw new TaskScopeRecoveryError("pair-invalid", "Scope recovery requires a complete preserved readiness identity.");
        }
        nextSummaryText = (0, task_attempts_1.materializeTaskAttemptSummary)(nextSummaryText, {
            attemptId: successor,
            readinessReviewId: readinessId,
            readinessAuthorizationKind: authorizationKind,
            openedAt: request.recordedAt,
            predecessorAttemptId: request.attemptId,
            implementationStatus: "in_progress",
            legacyReviewDisposition: null,
            successorKind: "scope_recovery",
            scopeRecoveryId: recoveryId
        });
        nextSummaryText = updateAttemptFields(nextSummaryText, successor, {
            ...sharedAttemptFields,
            scope_recovery_mode: "successor"
        });
    }
    nextSummaryText = appendScopeRecoveryHistory(nextSummaryText, record);
    const proposed = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: nextTaskText,
        implementationSummary: nextSummaryText
    });
    if (proposed.blockingFindings.length > 0) {
        throw new TaskScopeRecoveryError("pair-invalid", `Scope recovery would violate task-pair invariants: ${proposed.blockingFindings.map((finding) => finding.message).join(" ")}`);
    }
    const preconditions = [];
    for (const target of [pair.task, pair.summary]) {
        preconditions.push((0, mutation_preconditions_1.captureDestinationPresencePreconditionSync)(target));
        preconditions.push((0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target));
    }
    const writes = [
        { path: pair.task.repositoryRelativePath, contents: nextTaskText },
        { path: pair.summary.repositoryRelativePath, contents: nextSummaryText }
    ].sort((left, right) => left.path.localeCompare(right.path));
    const invalidatedValidationIds = mode === "successor"
        ? readValidationIds(summaryText, request.attemptId)
        : [];
    const nextAction = mode === "successor"
        ? successorBaselineAction({
            repositoryRoot: pair.repositoryRoot,
            runDirectory: request.runDirectory,
            taskPath: pair.task.repositoryRelativePath,
            taskText: nextTaskText,
            attemptId: successorAttemptId,
            checkpointId: projection.task.readinessAuthorization.id,
            fingerprint: expectedFingerprint,
            actor: request.recordedBy.trim(),
            capturedAt: request.recordedAt,
            recoveryId
        })
        : {
            reasonCode: "resume-amended-attempt",
            command: "resume run resume",
            argv: [
                "day-shift", "resume", "run", "resume",
                "--run-directory", request.runDirectory,
                "--artifact", pair.task.repositoryRelativePath,
                "--format", "json"
            ],
            callable: true,
            writeCapable: false
        };
    const snapshot = {
        recoveryId,
        taskId: projection.task.taskId,
        taskDefinitionPath: pair.task.repositoryRelativePath,
        implementationSummaryPath: pair.summary.repositoryRelativePath,
        runDirectory: request.runDirectory,
        attemptId: request.attemptId,
        successorAttemptId: mode === "successor" ? successorAttemptId : null,
        mode,
        contractImpactFingerprint: expectedFingerprint,
        baselineId: baseline.baselineId,
        classifications: delta,
        addedPaths: additions,
        addedValidationCommands: validationDelta,
        changedAdditions,
        untouchedAdditions,
        invalidatedValidationIds,
        taskRevision: observedTaskRevision,
        implementationSummaryRevision: observedSummaryRevision,
        nextTaskRevision: sha256(nextTaskText),
        nextImplementationSummaryRevision: sha256(nextSummaryText),
        idempotent: false,
        intendedPaths: writes.map((write) => write.path),
        changedPaths: [],
        expected: preconditions.map(toExpected),
        writeOutcome: "previewed",
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
        nextAction
    };
    plans.set(snapshot, {
        repositoryRoot: pair.repositoryRoot,
        writes,
        preconditions,
        snapshot: cloneResult(snapshot),
        recordedAt: request.recordedAt
    });
    return snapshot;
}
function applyTaskScopeRecoverySync(preview, options = {}) {
    if (preview.idempotent && preview.writeOutcome === "no-write")
        return cloneResult(preview);
    const plan = plans.get(preview);
    if (plan === undefined) {
        throw new TaskScopeRecoveryError("stale-preview", "Scope-recovery apply requires the original in-process preview. Refresh the preview.");
    }
    (0, task_lifecycle_timestamps_1.revalidateObservedLifecycleTimestamp)(plan.recordedAt, "recorded_at", "Scope-recovery time");
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(plan.repositoryRoot, { writes: plan.writes, preconditions: plan.preconditions }, options.transaction);
    return {
        ...cloneResult(plan.snapshot),
        changedPaths: transaction.entries.slice(0, transaction.canonicalWrites).map((entry) => entry.path),
        writeOutcome: transaction.outcome,
        transactionId: transaction.transactionId,
        conflicts: transaction.conflicts,
        recoveryPath: transaction.outcome === "incomplete" ? transaction.transactionPath : null,
        failure: transaction.failure ?? (transaction.cleanupErrors.length > 0 ? transaction.cleanupErrors.join(" ") : null)
    };
}
function replayResult(options) {
    const projection = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: options.taskText,
        implementationSummary: options.summaryText
    });
    if (projection.blockingFindings.length > 0) {
        throw new TaskScopeRecoveryError("pair-invalid", "Recorded scope recovery exists, but the current task pair is invalid.");
    }
    return {
        recoveryId: options.record.recoveryId,
        taskId: projection.task.taskId,
        taskDefinitionPath: options.pair.task.repositoryRelativePath,
        implementationSummaryPath: options.pair.summary.repositoryRelativePath,
        runDirectory: options.request.runDirectory,
        attemptId: options.record.attemptId,
        successorAttemptId: options.record.successorAttemptId,
        mode: options.record.mode,
        contractImpactFingerprint: options.record.contractImpactFingerprint,
        baselineId: options.record.baselineId,
        classifications: options.record.addedPaths.map((entry) => ({
            path: entry,
            classification: "already-declared",
            relationshipIds: [],
            eligible: true
        })),
        addedPaths: options.record.addedPaths,
        addedValidationCommands: options.record.addedValidationCommands,
        changedAdditions: [],
        untouchedAdditions: [],
        invalidatedValidationIds: [],
        taskRevision: sha256(options.taskText),
        implementationSummaryRevision: sha256(options.summaryText),
        nextTaskRevision: sha256(options.taskText),
        nextImplementationSummaryRevision: sha256(options.summaryText),
        idempotent: true,
        intendedPaths: [],
        changedPaths: [],
        expected: [],
        writeOutcome: "no-write",
        transactionId: null,
        conflicts: [],
        recoveryPath: null,
        failure: null,
        nextAction: {
            reasonCode: "already-recovered",
            command: "resume run resume",
            argv: [
                "day-shift", "resume", "run", "resume",
                "--run-directory", options.request.runDirectory,
                "--artifact", options.pair.task.repositoryRelativePath,
                "--format", "json"
            ],
            callable: true,
            writeCapable: false
        }
    };
}
function classifyDelta(inspection, declaredPaths, proposedPaths) {
    const required = unique([
        ...requiredPaths(inspection.missingWritable),
        ...requiredPaths(inspection.missingGenerated)
    ]).sort();
    const requested = proposedPaths === undefined || proposedPaths.length === 0
        ? required
        : unique(proposedPaths.map(canonicalPath)).sort();
    const declared = new Set(declaredPaths);
    const result = requested.map((candidate) => {
        if (declared.has(candidate)) {
            return { path: candidate, classification: "already-declared", relationshipIds: [], eligible: true };
        }
        const writable = obligationsForPath(inspection.missingWritable, candidate);
        if (writable.length > 0) {
            return { path: candidate, classification: "writable", relationshipIds: ids(writable), eligible: true };
        }
        const generated = obligationsForPath(inspection.missingGenerated, candidate);
        if (generated.length > 0) {
            return { path: candidate, classification: "generated", relationshipIds: ids(generated), eligible: true };
        }
        if (obligationsForPath(inspection.readOnlyDependencies, candidate).length > 0) {
            return { path: candidate, classification: "read-only", relationshipIds: [], eligible: false };
        }
        if (obligationsForPath(inspection.frozenDependencies, candidate).length > 0) {
            return { path: candidate, classification: "frozen", relationshipIds: [], eligible: false };
        }
        if (inspection.unknown.includes(candidate)) {
            return { path: candidate, classification: "unknown", relationshipIds: [], eligible: false };
        }
        return { path: candidate, classification: "materially-ineligible", relationshipIds: [], eligible: false };
    });
    const requestedEligible = result
        .filter((entry) => entry.eligible && entry.classification !== "already-declared")
        .map((entry) => entry.path)
        .sort();
    if (JSON.stringify(requestedEligible) !== JSON.stringify(required)) {
        throw new TaskScopeRecoveryError("incomplete-delta", `Proposed additive paths must equal the exact current deterministic closure. Expected ${JSON.stringify(required)}, received ${JSON.stringify(requestedEligible)}.`);
    }
    return result;
}
function classifyValidationDelta(inspection, taskText, proposed) {
    const required = unique(inspection.validationCommands
        .filter((entry) => entry.requirement === "required" && !entry.satisfied && entry.command !== null)
        .map((entry) => entry.command)).sort();
    const requested = proposed === undefined || proposed.length === 0
        ? required
        : unique(proposed.map((entry) => requireText(entry, "Proposed validation command"))).sort();
    const declared = new Set(readValidationCommands(taskText));
    const additions = requested.filter((entry) => !declared.has(entry));
    if (JSON.stringify(additions) !== JSON.stringify(required)) {
        throw new TaskScopeRecoveryError("incomplete-delta", `Proposed validations must equal the exact current deterministic closure. Expected ${JSON.stringify(required)}, received ${JSON.stringify(additions)}.`);
    }
    return additions;
}
function assertMaterialBoundary(inspection, classifications, validationDelta) {
    if (inspection.contractState !== "valid" || inspection.schemaVersion !== 2) {
        throw new TaskScopeRecoveryError("material-scope-change", "Deterministic additive recovery requires a valid schema v2 contract-impact authority.", "materially-ineligible");
    }
    const blockedClassification = classifications.find((entry) => !entry.eligible);
    if (blockedClassification !== undefined) {
        throw new TaskScopeRecoveryError("material-scope-change", `Path '${blockedClassification.path}' crosses the '${blockedClassification.classification}' authority boundary.`, blockedClassification.classification);
    }
    if (inspection.immutableTargets.length > 0) {
        const immutable = inspection.immutableTargets[0];
        throw new TaskScopeRecoveryError("material-scope-change", `Declared target '${immutable.path ?? immutable.producerPath}' is immutable under posture '${immutable.posture ?? "unknown"}'.`, immutable.posture === "frozen-dependency" ? "frozen" : "read-only");
    }
    if (inspection.unknown.length > 0 && inspection.unknownPolicy === "blocking") {
        throw new TaskScopeRecoveryError("material-scope-change", `Unknown producer '${inspection.unknown[0]}' has no deterministic recovery authority.`, "unknown");
    }
    const eligibleKeys = new Set([
        ...classifications.filter((entry) => entry.eligible).map((entry) => `path:${entry.path}`),
        ...validationDelta.map((entry) => `command:${entry}`)
    ]);
    const unhandled = inspection.missingRequired.filter((entry) => {
        if (entry.path !== null && eligibleKeys.has(`path:${entry.path}`))
            return false;
        if (entry.command !== null && eligibleKeys.has(`command:${entry.command}`))
            return false;
        return true;
    });
    if (unhandled.length > 0) {
        const entry = unhandled[0];
        throw new TaskScopeRecoveryError("material-scope-change", `Required obligation '${entry.path ?? entry.command ?? entry.relationshipId}' is not a deterministic writable/generated addition.`, "materially-ineligible");
    }
}
function successorBaselineAction(options) {
    const argv = [
        "day-shift", "--run-directory", options.runDirectory,
        "--run-gate", "task-baseline-plan",
        "--run-artifact", options.taskPath,
        "task", "baseline",
        "--task-definition", options.taskPath,
        "--task-cycle-id", options.attemptId,
        "--checkpoint-id", options.checkpointId,
        "--captured-by", options.actor,
        "--captured-at", options.capturedAt,
        "--contract-impact-fingerprint", options.fingerprint
    ];
    for (const targetPath of readSectionBullets(options.taskText, "Implementation Target Paths")) {
        const target = inspectTarget(options.repositoryRoot, targetPath);
        if (!target.dirty)
            continue;
        argv.push("--adopt", `${targetPath}::Preserved scope-recovery predecessor delta ${options.recoveryId}; current bytes are the successor attribution boundary.`);
    }
    argv.push("--plan", "--format", "json");
    return {
        reasonCode: "capture-successor-baseline",
        command: "task baseline",
        argv,
        callable: true,
        writeCapable: false
    };
}
function inspectTarget(repositoryRoot, targetPath) {
    let status;
    try {
        status = (0, node_child_process_1.execFileSync)("git", ["status", "--porcelain=v1", "--untracked-files=all", "--", targetPath], { cwd: repositoryRoot, encoding: "utf8", maxBuffer: 2_000_000, stdio: ["ignore", "pipe", "pipe"] });
    }
    catch (error) {
        throw new TaskScopeRecoveryError("git-inspection-unavailable", `Git inspection was unavailable for additive target '${targetPath}'; no change classification was inferred.${error instanceof Error ? ` ${error.message}` : ""}`);
    }
    const absolute = node_path_1.default.join(repositoryRoot, targetPath);
    const stat = node_fs_1.default.lstatSync(absolute, { throwIfNoEntry: false });
    if (stat === undefined) {
        return { path: targetPath, exists: false, kind: "absent", contentSha256: null, dirty: status !== "" };
    }
    if (stat.isSymbolicLink() || (!stat.isFile() && !stat.isDirectory())) {
        throw new TaskScopeRecoveryError("material-scope-change", `Additive target '${targetPath}' is not a bounded regular file, directory, or absent path.`, "materially-ineligible");
    }
    return {
        path: targetPath,
        exists: true,
        kind: stat.isFile() ? "file" : "directory",
        contentSha256: stat.isFile() ? hashFile(absolute) : hashDirectory(absolute),
        dirty: status !== ""
    };
}
function appendSectionBullets(markdown, heading, additions) {
    if (additions.length === 0)
        return markdown;
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const start = lines.findIndex((line) => line.trim() === `## ${heading}`);
    if (start < 0)
        throw new TaskScopeRecoveryError("artifact-unreadable", `Task contract is missing '## ${heading}'.`);
    let end = start + 1;
    while (end < lines.length && !/^##\s+/.test(lines[end]))
        end += 1;
    const existing = new Set(lines.slice(start + 1, end)
        .map((line) => line.match(/^\s*-\s+(?:`([^`]+)`|([^\s]+))\s*$/))
        .filter((match) => match !== null)
        .map((match) => canonicalPath(match[1] ?? match[2])));
    const insertions = additions.filter((entry) => !existing.has(entry)).map((entry) => `- ${entry}`);
    if (insertions.length === 0)
        return markdown;
    while (end > start + 1 && lines[end - 1].trim() === "")
        end -= 1;
    lines.splice(end, 0, ...insertions, "");
    return preserveFinalNewline(markdown, lines.join(newline), newline);
}
function appendValidationCommands(markdown, additions) {
    if (additions.length === 0)
        return markdown;
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const heading = lines.findIndex((line) => line.trim() === "## Validation Commands");
    if (heading < 0)
        throw new TaskScopeRecoveryError("artifact-unreadable", "Task contract is missing '## Validation Commands'.");
    const fenceStart = lines.findIndex((line, index) => index > heading && /^(?:```|~~~)(?:sh|bash|shell)?\s*$/.test(line));
    if (fenceStart < 0)
        throw new TaskScopeRecoveryError("artifact-unreadable", "Validation Commands must contain a shell fence.");
    const marker = lines[fenceStart].slice(0, 3);
    const fenceEnd = lines.findIndex((line, index) => index > fenceStart && line.startsWith(marker));
    if (fenceEnd < 0)
        throw new TaskScopeRecoveryError("artifact-unreadable", "Validation Commands shell fence is not closed.");
    lines.splice(fenceEnd, 0, ...additions);
    return preserveFinalNewline(markdown, lines.join(newline), newline);
}
function removeEmptyManualException(markdown) {
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    if (closing < 0)
        return markdown;
    for (let index = closing - 1; index > 0; index -= 1) {
        if (/^workflow_manual_exception:\s*(?:""|'')\s*$/.test(lines[index]))
            lines.splice(index, 1);
    }
    return preserveFinalNewline(markdown, lines.join(newline), newline);
}
function appendScopeRecoveryHistory(markdown, record) {
    const history = readScopeRecoveryHistory(markdown);
    if (history.some((entry) => entry.recoveryId === record.recoveryId))
        return markdown;
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    if (lines[0] !== "---" || closing < 0) {
        throw new TaskScopeRecoveryError("artifact-unreadable", "Scope-recovery artifact has malformed front matter.");
    }
    const rendered = JSON.stringify(JSON.stringify([...history, record]));
    const index = lines.findIndex((line, lineIndex) => lineIndex > 0 && lineIndex < closing && /^scope_recovery_history:/.test(line));
    if (index >= 0)
        lines[index] = `scope_recovery_history: ${rendered}`;
    else
        lines.splice(closing, 0, `scope_recovery_history: ${rendered}`);
    return preserveFinalNewline(markdown, lines.join(newline), newline);
}
function readScopeRecoveryHistory(markdown) {
    const lines = frontMatterLines(markdown);
    const raw = lines.find((line) => /^scope_recovery_history:/.test(line))
        ?.match(/^scope_recovery_history:\s*(.*)$/)?.[1];
    if (raw === undefined)
        return [];
    try {
        const scalar = JSON.parse(raw);
        if (typeof scalar !== "string")
            throw new TypeError("history scalar is not a string");
        const decoded = JSON.parse(scalar);
        if (!Array.isArray(decoded))
            throw new TypeError("history value is not an array");
        return decoded;
    }
    catch {
        throw new TaskScopeRecoveryError("artifact-unreadable", "scope_recovery_history is malformed.");
    }
}
function updateAttemptFields(markdown, attemptId, values) {
    const newline = markdown.includes("\r\n") ? "\r\n" : "\n";
    const lines = markdown.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    const starts = lines
        .map((line, index) => ({ index, match: line.match(/^\s{2}-\s+attempt_id:\s*(.*)$/) }))
        .filter((entry) => entry.index > 0 && entry.index < closing && entry.match !== null);
    const selected = starts.find((entry) => parseYamlScalar(entry.match?.[1] ?? "") === attemptId);
    if (selected === undefined)
        throw new TaskScopeRecoveryError("attempt-mismatch", `Attempt '${attemptId}' is not materialized.`);
    const nextStart = starts.find((entry) => entry.index > selected.index)?.index ?? closing;
    const nextTopLevel = lines.findIndex((line, lineIndex) => lineIndex > selected.index
        && lineIndex < closing
        && /^[A-Za-z_][A-Za-z0-9_-]*\s*:/.test(line));
    let end = Math.min(nextStart, nextTopLevel >= 0 ? nextTopLevel : closing, closing);
    for (const [field, value] of Object.entries(values)) {
        const pattern = new RegExp(`^\\s{4}${field}:`);
        const index = lines.findIndex((line, lineIndex) => lineIndex > selected.index && lineIndex < end && pattern.test(line));
        const rawToken = field === "review_disposition" || field === "scope_recovery_mode";
        const rendered = `    ${field}: ${rawToken ? value : JSON.stringify(value)}`;
        if (index >= 0)
            lines[index] = rendered;
        else {
            lines.splice(end, 0, rendered);
            end += 1;
        }
    }
    return preserveFinalNewline(markdown, lines.join(newline), newline);
}
function readValidationIds(markdown, attemptId) {
    const lines = frontMatterLines(markdown);
    const start = lines.findIndex((line) => /^\s{2}-\s+attempt_id:/.test(line)
        && parseYamlScalar(line.match(/^\s{2}-\s+attempt_id:\s*(.*)$/)?.[1] ?? "") === attemptId);
    if (start < 0)
        return [];
    let end = start + 1;
    while (end < lines.length && !/^\s{2}-\s+attempt_id:/.test(lines[end]) && /^\s/.test(lines[end]))
        end += 1;
    const raw = lines.slice(start, end).find((line) => /^\s{4}validation_results:/.test(line))
        ?.match(/^\s{4}validation_results:\s*(.*)$/)?.[1];
    if (raw === undefined || raw.trim() === "[]")
        return [];
    try {
        const scalar = JSON.parse(raw);
        const decoded = typeof scalar === "string" ? JSON.parse(scalar) : scalar;
        return Array.isArray(decoded)
            ? decoded.map((entry) => isObject(entry) && typeof entry.validationId === "string" ? entry.validationId : null)
                .filter((entry) => entry !== null)
            : [];
    }
    catch {
        return [];
    }
}
function readSectionBullets(markdown, heading) {
    const lines = markdown.split(/\r?\n/);
    const start = lines.findIndex((line) => line.trim() === `## ${heading}`);
    if (start < 0)
        return [];
    const values = [];
    for (let index = start + 1; index < lines.length && !/^##\s+/.test(lines[index]); index += 1) {
        const match = lines[index].match(/^\s*-\s+(?:`([^`]+)`|([^\s]+))\s*$/);
        if (match !== null)
            values.push(canonicalPath(match[1] ?? match[2]));
    }
    return unique(values);
}
function readValidationCommands(markdown) {
    const lines = markdown.split(/\r?\n/);
    const heading = lines.findIndex((line) => line.trim() === "## Validation Commands");
    if (heading < 0)
        return [];
    const start = lines.findIndex((line, index) => index > heading && /^(?:```|~~~)/.test(line));
    if (start < 0)
        return [];
    const marker = lines[start].slice(0, 3);
    const commands = [];
    for (let index = start + 1; index < lines.length && !lines[index].startsWith(marker); index += 1) {
        if (lines[index].trim() !== "")
            commands.push(lines[index].trim());
    }
    return unique(commands);
}
function assertRunEligible(repositoryRoot, runDirectory, taskPath) {
    const inspection = (0, lifecycle_run_context_1.inspectLifecycleRunContext)({
        repositoryRoot,
        runDirectory,
        selectedArtifactPath: taskPath
    });
    if (inspection.state !== "fresh"
        || inspection.conflict !== null
        || inspection.manifest.lifecycleProfile !== "full-cycle-strict"
        || inspection.manifest.activeArtifactPath !== taskPath) {
        throw new TaskScopeRecoveryError("run-ineligible", `Scope recovery requires the existing fresh full-cycle-strict run bound to '${taskPath}'. Observed '${inspection.state}'.`);
    }
}
function resolvePair(repositoryRoot, taskDefinitionPath) {
    const root = node_fs_1.default.realpathSync(node_path_1.default.resolve(requireText(repositoryRoot, "Repository root")));
    const parsed = (0, paths_1.parseRepositoryRelativePath)(taskDefinitionPath);
    if (parsed.segments.at(-1) !== "task-definition.md") {
        throw new TaskScopeRecoveryError("invalid-input", "Scope-recovery task paths must end in task-definition.md.");
    }
    const task = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, parsed.normalizedPath);
    const summary = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(root, [...parsed.segments.slice(0, -1), "implementation-summary.md"].join("/"));
    if (!task.exists || !summary.exists) {
        throw new TaskScopeRecoveryError("artifact-unreadable", "Scope recovery requires the canonical task pair.");
    }
    return { repositoryRoot: root, task, summary };
}
function validateRequest(request) {
    if (request === null || typeof request !== "object") {
        throw new TaskScopeRecoveryError("invalid-input", "A scope-recovery request is required.");
    }
    requireId(request.attemptId, "Attempt identity");
    normalizeOptionalId(request.successorAttemptId);
    normalizeSha(request.taskRevision, false);
    normalizeSha(request.baselineId, true);
    normalizeSha(request.contractImpactFingerprint, true);
    requireText(request.recordedBy, "Recovery actor");
    requireText(request.reason, "Recovery reason");
    if (!(0, task_lifecycle_1.isTaskLifecycleTimestamp)(request.recordedAt)) {
        throw new TaskScopeRecoveryError("invalid-input", "Recovery time must be ISO-8601 with an explicit timezone.");
    }
    (0, task_lifecycle_timestamps_1.requireObservedLifecycleTimestamp)({
        value: request.recordedAt,
        field: "recorded_at",
        label: "Scope-recovery time"
    });
}
function createRecoveryId(value) {
    return `task-scope-recovery-${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 24)}`;
}
function normalizeSha(value, prefix) {
    const normalized = requireText(value, "SHA-256 identity");
    if (!SHA256_PATTERN.test(normalized))
        throw new TaskScopeRecoveryError("invalid-input", `Invalid SHA-256 identity '${normalized}'.`);
    const bare = normalized.startsWith("sha256:") ? normalized.slice(7) : normalized;
    return prefix ? `sha256:${bare}` : bare;
}
function normalizeOptionalId(value) {
    if (value === undefined || value === null || value.trim() === "")
        return null;
    return requireId(value, "Successor attempt identity");
}
function requireId(value, label) {
    const normalized = requireText(value, label);
    if (!ID_PATTERN.test(normalized))
        throw new TaskScopeRecoveryError("invalid-input", `${label} must use 3-128 machine-safe characters.`);
    return normalized;
}
function requireText(value, label) {
    if (typeof value !== "string" || value.trim() === "" || value.includes("\n") || value.includes("\r")) {
        throw new TaskScopeRecoveryError("invalid-input", `${label} must be non-empty, bounded, single-line text.`);
    }
    if (Buffer.byteLength(value.trim()) > 1024)
        throw new TaskScopeRecoveryError("invalid-input", `${label} is too long.`);
    return value.trim();
}
function canonicalPath(value) {
    try {
        return (0, paths_1.parseRepositoryRelativePath)(value).normalizedPath;
    }
    catch (error) {
        throw new TaskScopeRecoveryError("invalid-input", error instanceof Error ? error.message : "Invalid repository path.");
    }
}
function readUtf8(absolutePath, relativePath) {
    try {
        return new node_util_1.TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(node_fs_1.default.readFileSync(absolutePath));
    }
    catch (error) {
        throw new TaskScopeRecoveryError("artifact-unreadable", `Scope-recovery artifact '${relativePath}' must be bounded UTF-8: ${error instanceof Error ? error.message : String(error)}`);
    }
}
function requiredPaths(entries) {
    return entries
        .filter((entry) => entry.requirement === "required" && !entry.satisfied && entry.path !== null)
        .map((entry) => entry.path);
}
function obligationsForPath(entries, candidate) {
    return entries.filter((entry) => entry.path === candidate);
}
function ids(entries) {
    return unique(entries.map((entry) => entry.relationshipId)).sort();
}
function toExpected(precondition) {
    return (0, mutation_preconditions_1.serializeMutationPrecondition)(precondition);
}
function sha256(text) {
    return (0, node_crypto_1.createHash)("sha256").update(Buffer.from(text)).digest("hex");
}
function hashFile(absolute) {
    return `sha256:${(0, node_crypto_1.createHash)("sha256").update(node_fs_1.default.readFileSync(absolute)).digest("hex")}`;
}
function hashDirectory(directory) {
    const pending = [directory];
    const files = [];
    while (pending.length > 0) {
        const current = pending.pop();
        for (const name of node_fs_1.default.readdirSync(current).sort().reverse()) {
            const entry = node_path_1.default.join(current, name);
            const stat = node_fs_1.default.lstatSync(entry);
            if (stat.isSymbolicLink())
                continue;
            if (stat.isDirectory())
                pending.push(entry);
            else if (stat.isFile())
                files.push(entry);
            if (files.length > 5_000)
                throw new TaskScopeRecoveryError("material-scope-change", "Directory recovery exceeds the bounded file limit.");
        }
    }
    const hash = (0, node_crypto_1.createHash)("sha256");
    for (const file of files.sort()) {
        const relative = node_path_1.default.relative(directory, file).split(node_path_1.default.sep).join("/");
        const bytes = node_fs_1.default.readFileSync(file);
        hash.update(`${relative.length}:${relative}:${bytes.byteLength}:`);
        hash.update(bytes);
    }
    return `sha256:${hash.digest("hex")}`;
}
function frontMatterLines(markdown) {
    const lines = markdown.split(/\r?\n/);
    const closing = lines.findIndex((line, index) => index > 0 && line === "---");
    return lines[0] === "---" && closing >= 0 ? lines.slice(1, closing) : [];
}
function parseYamlScalar(raw) {
    const value = raw.trim();
    if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
        try {
            return value.startsWith("\"") ? JSON.parse(value) : value.slice(1, -1);
        }
        catch {
            return value.slice(1, -1);
        }
    }
    return value;
}
function preserveFinalNewline(original, rendered, newline) {
    return original.endsWith("\n") && !rendered.endsWith(newline) ? `${rendered}${newline}` : rendered;
}
function unique(values) {
    return [...new Set(values)];
}
function isObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function cloneResult(result) {
    return JSON.parse(JSON.stringify(result));
}
