"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.planningScaffoldContentRevision = planningScaffoldContentRevision;
exports.inspectAcceptanceScopedTaskScaffold = inspectAcceptanceScopedTaskScaffold;
exports.inspectRuntimeCriterionOwnership = inspectRuntimeCriterionOwnership;
exports.applyPlanningScaffoldSync = applyPlanningScaffoldSync;
exports.assertPlanningScaffoldApplied = assertPlanningScaffoldApplied;
const node_crypto_1 = require("node:crypto");
const mutation_preconditions_1 = require("../filesystem/mutation-preconditions");
const repository_transaction_1 = require("../filesystem/repository-transaction");
const canonical_write_target_1 = require("../paths/canonical-write-target");
const scaffold_target_path_policy_1 = require("./scaffold-target-path-policy");
function planningScaffoldContentRevision(contents) {
    return (0, node_crypto_1.createHash)("sha256").update(contents).digest("hex");
}
/**
 * Projects one generated task contract from only that child's scoped scaffold
 * block and reviewed criterion assignment. Explicit target fields are parsed
 * independently from validation commands so launcher paths cannot leak into
 * implementation targets.
 */
function inspectAcceptanceScopedTaskScaffold(options) {
    const scopedLines = readScopedScaffoldLines(options.milestoneMarkdownText, options.childSlug);
    const issues = [];
    const explicitAcceptance = semanticScaffoldValues(scopedLines, ["acceptance", "acceptance_criterion"]);
    const explicitCurrent = semanticScaffoldValues(scopedLines, ["current_behavior", "current"]);
    const explicitDesired = semanticScaffoldValues(scopedLines, ["desired_behavior", "desired"]);
    const explicitTargets = semanticScaffoldValues(scopedLines, ["target", "targets"]);
    const explicitValidation = semanticScaffoldValues(scopedLines, ["validation"]);
    const explicitRollback = semanticScaffoldValues(scopedLines, ["rollback"]);
    const explicitDependencies = semanticScaffoldValues(scopedLines, ["dependency", "dependencies"]);
    const ownership = semanticScaffoldValues(scopedLines, ["ownership"]);
    const targetPaths = uniqueValues(explicitTargets.flatMap((value) => {
        if (looksLikeExecutableCommand(value)) {
            issues.push({
                code: "PLANNING_SCAFFOLD_TARGET_VALIDATION_CROSSOVER",
                evidenceKind: "target",
                message: `Task '${options.childSlug}' declares executable validation launcher '${value}' as target evidence.`
            });
            return [];
        }
        return extractRepositoryPaths(value);
    }));
    const validationCommands = uniqueValues(explicitValidation);
    const rollback = uniqueValues(explicitRollback);
    const dependencies = uniqueValues(explicitDependencies.flatMap(extractRepositoryPaths));
    if (targetPaths.length === 0) {
        issues.push({
            code: "PLANNING_SCAFFOLD_TARGET_EVIDENCE_MISSING",
            evidenceKind: "target",
            message: `Task '${options.childSlug}' must declare at least one explicit repository-relative target separate from validation evidence.`
        });
    }
    if (validationCommands.length === 0) {
        issues.push({
            code: "PLANNING_SCAFFOLD_VALIDATION_EVIDENCE_MISSING",
            evidenceKind: "validation",
            message: `Task '${options.childSlug}' must declare at least one explicit validation command.`
        });
    }
    if (rollback.length === 0) {
        issues.push({
            code: "PLANNING_SCAFFOLD_ROLLBACK_EVIDENCE_MISSING",
            evidenceKind: "rollback",
            message: `Task '${options.childSlug}' must declare explicit rollback evidence.`
        });
    }
    for (const [kind, values] of [
        ["acceptance", explicitAcceptance],
        ["current_behavior", explicitCurrent],
        ["desired_behavior", explicitDesired],
        ["target", explicitTargets],
        ["validation", explicitValidation],
        ["rollback", explicitRollback],
        ["scaffold", [...explicitDependencies, ...ownership]]
    ]) {
        for (const value of values) {
            if (!isUnresolvedScaffoldValue(value))
                continue;
            issues.push({
                code: "PLANNING_SCAFFOLD_GENERIC_CONTENT",
                evidenceKind: kind,
                message: `Task '${options.childSlug}' contains unresolved ${kind.replace("_", " ")} scaffold evidence: '${value}'.`
            });
        }
    }
    const desiredSource = firstConcreteValue([
        ...explicitDesired,
        ...explicitAcceptance,
        options.taskDescription,
        ...ownership
    ]);
    const desiredBehavior = desiredSource === null
        ? ""
        : sentence(desiredSource);
    if (desiredBehavior === "") {
        issues.push({
            code: "PLANNING_SCAFFOLD_DESIRED_BEHAVIOR_GENERIC",
            evidenceKind: "desired_behavior",
            message: `Task '${options.childSlug}' does not provide a concrete child-scoped desired behavior.`
        });
    }
    const currentSource = firstConcreteValue(explicitCurrent);
    const currentBehavior = currentSource === null && desiredBehavior !== ""
        ? `The parent milestone does not yet satisfy the child-owned outcome for \`${options.childSlug}\`: ${desiredBehavior}`
        : currentSource === null
            ? ""
            : sentence(currentSource);
    if (currentBehavior === "") {
        issues.push({
            code: "PLANNING_SCAFFOLD_CURRENT_BEHAVIOR_GENERIC",
            evidenceKind: "current_behavior",
            message: `Task '${options.childSlug}' does not provide enough concrete evidence to project current behavior.`
        });
    }
    const acceptanceCriteria = projectOwnedAcceptanceCriteria({
        assignedCriterionIds: options.assignedCriterionIds,
        parentAcceptanceCriteria: options.parentAcceptanceCriteria,
        explicitAcceptance,
        desiredBehavior
    });
    if (acceptanceCriteria.length === 0) {
        issues.push({
            code: "PLANNING_SCAFFOLD_ACCEPTANCE_SCOPE_MISSING",
            evidenceKind: "acceptance",
            message: `Task '${options.childSlug}' does not resolve to any child-owned acceptance evidence.`
        });
    }
    return {
        childSlug: options.childSlug,
        criterionIds: [...options.assignedCriterionIds],
        acceptanceCriteria,
        currentBehavior,
        desiredBehavior,
        dependencies,
        targetPaths,
        validationCommands,
        rollback,
        issues: uniqueIssues(issues),
        complete: issues.length === 0
            && options.scaffoldEvidence.complete
    };
}
/**
 * Reviews the criterion_ids assigned to every declared task in milestone
 * Scaffold Evidence. Existing parents with no assignments retain the legacy
 * full-set projection; once any assignment is present, every child must own a
 * non-empty subset and the ordered union must exactly cover the parent.
 */
function inspectRuntimeCriterionOwnership(options) {
    if (options.parentCriterionIds.length === 0 || options.declaredChildSlugs.length === 0) {
        return {
            mode: "not-applicable",
            coverageStatus: "not-applicable",
            parentCriterionIds: [...options.parentCriterionIds],
            orderedUnionCriterionIds: [],
            childOwnership: [],
            unknownCriterionIds: [],
            omittedCriterionIds: [],
            overlappingCriterionIds: [],
            issues: [],
            complete: true
        };
    }
    const parsedAssignments = options.declaredChildSlugs.map((childSlug) => readRuntimeCriterionAssignment(options.milestoneMarkdownText, childSlug));
    const decomposed = parsedAssignments.some((assignment) => assignment.declared);
    if (!decomposed) {
        return {
            mode: "legacy-full-set",
            coverageStatus: "legacy-full-set",
            parentCriterionIds: [...options.parentCriterionIds],
            orderedUnionCriterionIds: [...options.parentCriterionIds],
            childOwnership: options.declaredChildSlugs.map((childSlug) => ({
                childSlug,
                criterionIds: [...options.parentCriterionIds],
                sharedCriterionIds: [],
                source: "legacy-full-set"
            })),
            unknownCriterionIds: [],
            omittedCriterionIds: [],
            overlappingCriterionIds: options.declaredChildSlugs.length > 1
                ? [...options.parentCriterionIds]
                : [],
            issues: [],
            complete: true
        };
    }
    const issues = [];
    const parentIds = new Set(options.parentCriterionIds);
    const childOwnership = parsedAssignments.map((assignment) => {
        const duplicateIds = duplicateValues(assignment.criterionIds);
        if (!assignment.declared || assignment.criterionIds.length === 0) {
            issues.push(`Task '${assignment.childSlug}' must declare a non-empty Scaffold Evidence criterion_ids subset.`);
        }
        if (duplicateIds.length > 0) {
            issues.push(`Task '${assignment.childSlug}' repeats criterion_ids within its assignment: ${duplicateIds.join(", ")}.`);
        }
        const unownedSharedIds = assignment.sharedCriterionIds.filter((criterionId) => !assignment.criterionIds.includes(criterionId));
        if (unownedSharedIds.length > 0) {
            issues.push(`Task '${assignment.childSlug}' marks shared_criterion_ids that it does not own: ${uniqueValues(unownedSharedIds).join(", ")}.`);
        }
        return {
            childSlug: assignment.childSlug,
            criterionIds: uniqueValues(assignment.criterionIds),
            sharedCriterionIds: uniqueValues(assignment.sharedCriterionIds),
            source: "scaffold-evidence"
        };
    });
    const unknownCriterionIds = uniqueValues(childOwnership.flatMap((ownership) => ownership.criterionIds.filter((criterionId) => !parentIds.has(criterionId))));
    if (unknownCriterionIds.length > 0) {
        issues.push(`Child criterion assignments contain IDs outside the authoritative parent contract: ${unknownCriterionIds.join(", ")}.`);
    }
    const ownersByCriterion = new Map();
    for (const ownership of childOwnership) {
        for (const criterionId of ownership.criterionIds) {
            const owners = ownersByCriterion.get(criterionId) ?? [];
            owners.push(ownership);
            ownersByCriterion.set(criterionId, owners);
        }
    }
    const omittedCriterionIds = options.parentCriterionIds.filter((criterionId) => (ownersByCriterion.get(criterionId)?.length ?? 0) === 0);
    if (omittedCriterionIds.length > 0) {
        issues.push(`Child criterion assignments omit authoritative parent IDs: ${omittedCriterionIds.join(", ")}.`);
    }
    const overlappingCriterionIds = options.parentCriterionIds.filter((criterionId) => (ownersByCriterion.get(criterionId)?.length ?? 0) > 1);
    for (const criterionId of overlappingCriterionIds) {
        const owners = ownersByCriterion.get(criterionId) ?? [];
        const unreviewedOwners = owners
            .filter((owner) => !owner.sharedCriterionIds.includes(criterionId))
            .map((owner) => owner.childSlug);
        if (unreviewedOwners.length > 0) {
            issues.push(`Criterion '${criterionId}' overlaps across ${owners.map((owner) => owner.childSlug).join(", ")} without shared_criterion_ids review from: ${unreviewedOwners.join(", ")}.`);
        }
    }
    const orderedUnionCriterionIds = uniqueValues(childOwnership.flatMap((ownership) => ownership.criterionIds)).filter((criterionId) => parentIds.has(criterionId));
    if (unknownCriterionIds.length === 0
        && omittedCriterionIds.length === 0
        && !sameOrderedValues(orderedUnionCriterionIds, options.parentCriterionIds)) {
        issues.push(`The ordered child criterion union must preserve authoritative parent order: ${options.parentCriterionIds.join(", ")}.`);
    }
    return {
        mode: "decomposed",
        coverageStatus: issues.length === 0 ? "exact" : "blocked",
        parentCriterionIds: [...options.parentCriterionIds],
        orderedUnionCriterionIds,
        childOwnership,
        unknownCriterionIds,
        omittedCriterionIds,
        overlappingCriterionIds,
        issues: uniqueValues(issues),
        complete: issues.length === 0
    };
}
/**
 * Applies one planning scaffold under the shared repository lock. Create
 * destinations are always bound to absence. Replace destinations must carry
 * the revision of the exact bytes that passed caller-side eligibility review.
 * Parents may likewise carry the revision read by the planner.
 */
function applyPlanningScaffoldSync(repositoryRoot, writes, options = {}) {
    const canonicalWrites = [];
    const preconditions = [];
    for (const write of writes) {
        const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, write.path);
        const mode = write.mode ?? "create";
        if (mode !== "create" && mode !== "replace") {
            throw new TypeError(`Unsupported planning scaffold write mode '${String(mode)}'.`);
        }
        if (mode === "replace" && !isContentRevision(write.expectedRevision)) {
            throw new TypeError(`Planning scaffold replacement '${target.repositoryRelativePath}' requires its plan-bound expected revision.`);
        }
        canonicalWrites.push({
            path: target.repositoryRelativePath,
            contents: write.contents
        });
        preconditions.push({
            kind: "destination-presence",
            repositoryRelativePath: target.repositoryRelativePath,
            runtimePath: target.runtimePath,
            expectedPresence: mode === "replace" ? "present" : "absent"
        });
        if (mode === "replace") {
            preconditions.push({
                kind: "content-revision",
                repositoryRelativePath: target.repositoryRelativePath,
                runtimePath: target.runtimePath,
                expectedRevision: write.expectedRevision
            });
        }
    }
    for (const parent of options.parentPaths ?? []) {
        const parentPath = typeof parent === "string" ? parent : parent.path;
        const target = (0, canonical_write_target_1.resolveCanonicalWriteTargetSync)(repositoryRoot, parentPath);
        if (typeof parent === "string") {
            preconditions.push((0, mutation_preconditions_1.captureContentRevisionPreconditionSync)(target));
            continue;
        }
        if (!isContentRevision(parent.expectedRevision)) {
            throw new TypeError(`Planning scaffold parent '${target.repositoryRelativePath}' requires its plan-bound expected revision.`);
        }
        preconditions.push({
            kind: "content-revision",
            repositoryRelativePath: target.repositoryRelativePath,
            runtimePath: target.runtimePath,
            expectedRevision: parent.expectedRevision
        });
    }
    if (canonicalWrites.length === 0) {
        return {
            outcome: "no-write",
            intendedPaths: [],
            changedPaths: [],
            transactionId: null,
            conflicts: [],
            recoveryPath: null,
            failure: null
        };
    }
    const transaction = (0, repository_transaction_1.executeRepositoryTransactionSync)(repositoryRoot, {
        writes: canonicalWrites,
        preconditions
    }, options.transaction);
    const changedPaths = transaction.entries
        .slice(0, transaction.canonicalWrites)
        .map((entry) => entry.path);
    return {
        outcome: transaction.outcome,
        intendedPaths: canonicalWrites.map((write) => write.path),
        changedPaths,
        transactionId: transaction.transactionId,
        conflicts: transaction.conflicts,
        recoveryPath: transaction.outcome === "incomplete" ? transaction.transactionPath : null,
        failure: transaction.failure
    };
}
function isContentRevision(value) {
    return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
}
function assertPlanningScaffoldApplied(result) {
    if (result.outcome === "committed" || result.outcome === "no-write") {
        return;
    }
    const evidence = [
        `Write outcome: ${result.outcome}.`,
        `Intended paths: ${result.intendedPaths.join(", ") || "(none)"}.`,
        `Changed paths: ${result.changedPaths.join(", ") || "(none)"}.`
    ];
    if (result.failure !== null) {
        evidence.push(`Failure: ${result.failure}`);
    }
    if (result.recoveryPath !== null) {
        evidence.push(`Recovery path: ${result.recoveryPath}`);
    }
    for (const conflict of result.conflicts) {
        evidence.push(`Conflict at ${conflict.path}: expected ${conflict.expectedState}; observed ${conflict.observedState}.`);
    }
    throw new Error(`Planning scaffold application did not complete.\n${evidence.join("\n")}`);
}
function readRuntimeCriterionAssignment(markdownText, childSlug) {
    const scopedLines = readScopedScaffoldLines(markdownText, childSlug);
    if (scopedLines.length === 0) {
        return { childSlug, criterionIds: [], sharedCriterionIds: [], declared: false };
    }
    const criterionFields = semanticListValues(scopedLines, "criterion_ids");
    const sharedFields = semanticListValues(scopedLines, "shared_criterion_ids");
    return {
        childSlug,
        criterionIds: criterionFields.values,
        sharedCriterionIds: sharedFields.values,
        declared: criterionFields.present || sharedFields.present
    };
}
function semanticListValues(lines, key) {
    const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const values = [];
    let present = false;
    for (const line of lines) {
        const match = line.match(new RegExp(`^${escapedKey}\\s*:\\s*(.*)$`, "iu"));
        if (match === null)
            continue;
        present = true;
        values.push(...match[1]
            .split(/\s*[;,]\s*/u)
            .map((value) => unwrapMatchingScaffoldWrapper(value.trim()))
            .filter((value) => value !== ""));
    }
    return { present, values };
}
function readScopedScaffoldLines(markdownText, childSlug) {
    const lines = markdownText.split(/\r?\n/);
    const sectionStart = lines.findIndex((line) => line.trim() === "## Scaffold Evidence");
    if (sectionStart === -1)
        return [];
    const scopedLines = [];
    let active = false;
    for (let index = sectionStart + 1; index < lines.length; index += 1) {
        const line = lines[index];
        if (/^##\s+/u.test(line))
            break;
        const scope = line.trim().match(/^-\s+(?:`([^`]+)`|\[([^\]]+)\])\s*:\s*(.*)$/u);
        if (scope !== null) {
            active = (scope[1] ?? scope[2]) === childSlug;
            if (active && scope[3] !== "")
                scopedLines.push(scope[3]);
            continue;
        }
        if (active && (/^\s{2,}-\s+/u.test(line) || /^\s{2,}[A-Za-z0-9_-]+:/u.test(line))) {
            scopedLines.push(line.trim().replace(/^-\s+/u, ""));
            continue;
        }
        if (line.trim() !== "")
            active = false;
    }
    return scopedLines;
}
function semanticScaffoldValues(lines, keys) {
    const values = [];
    for (const line of lines) {
        for (const key of keys) {
            const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
            const match = line.match(new RegExp(`^${escapedKey}\\s*:\\s*(.+)$`, "iu"));
            if (match === null)
                continue;
            values.push(stripRepeatedSemanticPrefixes(match[1], keys));
            break;
        }
    }
    return uniqueValues(values.filter((value) => value !== ""));
}
function stripRepeatedSemanticPrefixes(value, keys) {
    let normalized = unwrapMatchingScaffoldWrapper(value.trim());
    const aliases = keys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
    const repeatedPrefix = new RegExp(`^(?:${aliases})\\s*:\\s*`, "iu");
    while (repeatedPrefix.test(normalized)) {
        normalized = normalized.replace(repeatedPrefix, "").trim();
    }
    return unwrapMatchingScaffoldWrapper(normalized);
}
function unwrapMatchingScaffoldWrapper(value) {
    const hasBacktickWrapper = value.length >= 2
        && value.startsWith("`")
        && value.endsWith("`")
        && !value.slice(1, -1).includes("`");
    const hasBracketWrapper = value.length >= 2
        && value.startsWith("[")
        && value.endsWith("]");
    if (hasBacktickWrapper || hasBracketWrapper) {
        return value.slice(1, -1);
    }
    return value;
}
function projectOwnedAcceptanceCriteria(options) {
    if (options.assignedCriterionIds.length === 0) {
        const scoped = options.explicitAcceptance.length > 0
            ? options.explicitAcceptance
            : options.parentAcceptanceCriteria;
        return uniqueValues(scoped.length > 0
            ? scoped.map(sentence)
            : options.desiredBehavior === ""
                ? []
                : [options.desiredBehavior]);
    }
    return options.assignedCriterionIds.map((criterionId) => {
        const scoped = options.explicitAcceptance.find((entry) => acceptanceCriterionId(entry) === criterionId);
        const parent = options.parentAcceptanceCriteria.find((entry) => acceptanceCriterionId(entry) === criterionId);
        const source = scoped ?? parent ?? options.desiredBehavior;
        const withoutExistingId = source.replace(new RegExp(`^\\[?${escapeRegExp(criterionId)}\\]?\\s*[:\\-]?\\s*`, "u"), "");
        return `[${criterionId}] ${sentence(withoutExistingId)}`;
    });
}
function acceptanceCriterionId(value) {
    const match = value.trim().match(/^(?:\[([A-Za-z0-9_-]+-AC-\d+)\]|`([A-Za-z0-9_-]+-AC-\d+)`|([A-Za-z0-9_-]+-AC-\d+))(?:\s|:|-)/u);
    return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
}
function extractRepositoryPaths(value) {
    return [...(0, scaffold_target_path_policy_1.extractPlanningScaffoldRepositoryPaths)(value, {
            allowBareBasename: true
        })];
}
function looksLikeExecutableCommand(value) {
    return /^(?:node|pnpm|npm|yarn|bun|deno|cargo|go|pytest|python|ruby|java|dotnet|make)\b/u
        .test(value.trim());
}
function isUnresolvedScaffoldValue(value) {
    const normalized = value.trim();
    return normalized === ""
        || /^(?:TBD|TODO)(?:\s*[:=-]\s*.*)?[.!?]?$/iu.test(normalized)
        || /^(?:placeholder|pending)(?:\s+(?:content|text|evidence|prose|review))?[.!?]?$/iu
            .test(normalized)
        || /^pending\.\s+/iu.test(normalized)
        || /^pending review(?:\s+(?:after|until)\b|[.!?]?$)/iu.test(normalized)
        || /^(?:produce|create|add|write|use)\s+(?:a\s+|the\s+)?placeholder(?:\s+(?:evidence|content|text|prose|artifact|value))?[.!?]?$/iu
            .test(normalized)
        || /\{\{[^}]+\}\}/u.test(normalized)
        || /^(?:implement|update|support|handle|fix)\s+(?:the\s+)?(?:task|behavior|change)\.?$/iu.test(normalized);
}
function firstConcreteValue(values) {
    return values.find((value) => !isUnresolvedScaffoldValue(value)) ?? null;
}
function sentence(value) {
    const normalized = value.trim();
    if (normalized === "")
        return "";
    const capitalized = `${normalized[0].toUpperCase()}${normalized.slice(1)}`;
    return /[.!?]$/u.test(capitalized) ? capitalized : `${capitalized}.`;
}
function uniqueIssues(issues) {
    const seen = new Set();
    return issues.filter((issue) => {
        const key = `${issue.code}:${issue.message}`;
        if (seen.has(key))
            return false;
        seen.add(key);
        return true;
    });
}
function escapeRegExp(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function duplicateValues(values) {
    return uniqueValues(values.filter((value, index) => values.indexOf(value) !== index));
}
function uniqueValues(values) {
    return [...new Set(values)];
}
function sameOrderedValues(left, right) {
    return left.length === right.length && left.every((value, index) => value === right[index]);
}
