"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LEGACY_COMPATIBILITY_PROJECTION_PROTOCOL_VERSION = void 0;
exports.projectLegacyPlanningCompatibility = projectLegacyPlanningCompatibility;
const node_crypto_1 = require("node:crypto");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../errors");
const paths_1 = require("../paths");
const workspace_discovery_1 = require("../workspace-discovery");
const artifact_policy_projection_1 = require("./artifact-policy-projection");
const historical_compatibility_disposition_1 = require("./historical-compatibility-disposition");
const planning_artifact_document_1 = require("./planning-artifact-document");
const task_lifecycle_1 = require("./task-lifecycle");
exports.LEGACY_COMPATIBILITY_PROJECTION_PROTOCOL_VERSION = 1;
const DEFAULT_SCAN_BUDGETS = Object.freeze({
    files: 25_000,
    directories: 10_000,
    metadataBytes: 32_000_000,
    contentBytes: 1,
    elapsedMs: 5_000,
    evidenceEntries: 50_000
});
const ORDERED_SLICE_ID_PATTERN = /^(?:0\d|[1-9]\d+)-[a-z0-9]+(?:-[a-z0-9]+)*$/;
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
/**
 * Produces an advisory, deterministic view of legacy planning layouts.
 *
 * The projection never writes, never selects a migration destination, and never
 * turns ambiguous source evidence into a convenience workflow-level default.
 */
function projectLegacyPlanningCompatibility(options) {
    const planningRootPath = (0, paths_1.parseRepositoryRelativePath)(options.planningRootPath ?? ".day-shift/planning").normalizedPath;
    const absolutePlanningRoot = (0, paths_1.resolveRepositoryRelativePath)(options.repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(planningRootPath));
    if (!node_fs_1.default.existsSync(absolutePlanningRoot) || !node_fs_1.default.statSync(absolutePlanningRoot).isDirectory()) {
        throw (0, errors_1.createUsageError)(`Planning root '${planningRootPath}' does not exist or is not a directory.`);
    }
    const scan = (0, workspace_discovery_1.discoverWorkspacePaths)({
        workspaceRoot: absolutePlanningRoot,
        budgets: { ...DEFAULT_SCAN_BUDGETS, ...options.budgets },
        generatedAt: options.generatedAt ?? "not-recorded",
        include: (entry) => entry.kind === "file"
            && (entry.path === "task-definition.md" || entry.path.endsWith("/task-definition.md"))
    });
    const discovery = {
        ...scan.evidence,
        usage: { ...scan.evidence.usage, elapsedMs: 0 }
    };
    const disposition = (0, historical_compatibility_disposition_1.loadHistoricalCompatibilityDispositionSync)(options.repositoryRoot, {
        enabled: options.historicalCompatibilityDisposition !== "ignore"
    });
    const taskPaths = scan.paths
        .map((relativePath) => repositoryPath(node_path_1.default.posix.join(planningRootPath, relativePath)))
        .sort((left, right) => left.localeCompare(right));
    const drafts = taskPaths.map((taskPath) => projectPair(options.repositoryRoot, planningRootPath, taskPath, disposition));
    applyDuplicateIdentityAmbiguity(drafts);
    applyDuplicateSliceIdentityAmbiguity(drafts);
    const workUnits = includeUnclaimedLegacyFiles(options.repositoryRoot, planningRootPath, groupWorkUnits(drafts));
    const findings = [];
    const dispositionSummary = disposition.summary();
    if (dispositionSummary.status === "invalid") {
        findings.push({
            code: "INVALID_HISTORICAL_COMPATIBILITY_DISPOSITION",
            paths: [dispositionSummary.path],
            message: dispositionSummary.invalidReason
                ?? "Historical compatibility disposition is invalid and no gaps were suppressed."
        });
    }
    for (const unit of workUnits) {
        if (unit.proposalStatus === "ambiguous") {
            const ambiguityCodes = new Set(unit.ambiguityReasons.map((entry) => entry.code));
            findings.push({
                code: ambiguityCodes.has("duplicate-active-identity")
                    ? "DUPLICATE_ACTIVE_IDENTITY"
                    : ambiguityCodes.has("duplicate-slice-identity")
                        ? "DUPLICATE_SLICE_IDENTITY"
                        : "AMBIGUOUS_LEGACY_WORK",
                paths: unit.sourcePaths,
                message: `Legacy work unit '${unit.workUnitId}' remains ambiguous; no workflow level is proposed.`
            });
        }
    }
    if (!scan.evidence.complete) {
        findings.push({
            code: "INCOMPLETE_DISCOVERY",
            paths: scan.evidence.omission?.nextPath === undefined
                ? []
                : [repositoryPath(node_path_1.default.posix.join(planningRootPath, scan.evidence.omission.nextPath))],
            message: `Legacy compatibility discovery exhausted its ${scan.evidence.exhaustedBudget ?? "configured"} budget; results are incomplete.`
        });
    }
    const proposalStatus = workUnits.some((unit) => unit.proposalStatus === "ambiguous") || !scan.evidence.complete
        ? "ambiguous"
        : "proposed";
    return {
        protocolVersion: exports.LEGACY_COMPATIBILITY_PROJECTION_PROTOCOL_VERSION,
        planningRootPath,
        proposalStatus,
        readOnly: true,
        migrationApplied: false,
        canonicalDestination: null,
        workUnits,
        findings: sortFindings(findings),
        discovery,
        historicalCompatibilityDisposition: disposition.summary()
    };
}
function projectPair(repositoryRoot, planningRootPath, taskDefinitionPath, disposition) {
    const taskText = readRepositoryFile(repositoryRoot, taskDefinitionPath);
    const summaryPath = repositoryPath(node_path_1.default.posix.join(node_path_1.default.posix.dirname(taskDefinitionPath), "implementation-summary.md"));
    const summaryText = readRepositoryFileIfExists(repositoryRoot, summaryPath);
    const taskPolicy = (0, artifact_policy_projection_1.projectArtifactPolicy)(taskText);
    const summaryPolicy = summaryText === null ? null : (0, artifact_policy_projection_1.projectArtifactPolicy)(summaryText);
    const lifecycle = (0, task_lifecycle_1.projectTaskLifecyclePair)({
        taskDefinition: taskText,
        implementationSummary: summaryText
    });
    const taskDocument = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(taskText);
    const evidence = [
        ...policyEvidence(taskDefinitionPath, taskPolicy.compatibilityEvidence),
        ...policyEvidence(summaryPath, summaryPolicy?.compatibilityEvidence ?? []),
        ...lifecycleEvidence(taskDefinitionPath, summaryPath, lifecycle.compatibilityEvidence)
    ];
    const evidenceGaps = [
        ...policyGaps(taskDefinitionPath, taskPolicy.blockingFindings),
        ...policyGaps(summaryPath, summaryPolicy?.blockingFindings ?? []),
        ...lifecycle.blockingFindings.map((finding) => ({
            code: `lifecycle-${finding.code}`,
            field: finding.field,
            paths: [finding.artifact === "implementation_summary" ? summaryPath : taskDefinitionPath],
            message: finding.message
        }))
    ].filter((gap) => !disposition.matches(gap.code, gap.paths));
    if (summaryText === null) {
        const gap = {
            code: "missing-implementation-summary",
            field: "implementation_summary",
            paths: [summaryPath],
            message: "The legacy task has no paired implementation summary."
        };
        if (!disposition.matches(gap.code, gap.paths))
            evidenceGaps.push(gap);
    }
    else if (summaryPolicy?.taskId !== null
        && taskPolicy.taskId !== null
        && summaryPolicy?.taskId !== taskPolicy.taskId) {
        const gap = {
            code: "paired-task-identity-mismatch",
            field: "id",
            paths: [taskDefinitionPath, summaryPath],
            message: `Paired task identities '${taskPolicy.taskId}' and '${summaryPolicy?.taskId}' do not match.`
        };
        if (!disposition.matches(gap.code, gap.paths))
            evidenceGaps.push(gap);
    }
    const layout = projectLayout({
        repositoryRoot,
        planningRootPath,
        taskDefinitionPath,
        taskDocument,
        workflowLevel: taskPolicy.workflowLevel,
        workflowLevelSource: taskPolicy.workflowLevelSource,
        parentKinds: taskPolicy.parents.map((parent) => parent.kind),
        policyFindings: [
            ...taskPolicy.blockingFindings,
            ...(summaryPolicy?.blockingFindings ?? [])
        ]
    });
    evidenceGaps.push(...layout.evidenceGaps.filter((gap) => !disposition.matches(gap.code, gap.paths)));
    return {
        pair: {
            taskId: taskPolicy.taskId,
            taskDefinitionPath,
            implementationSummaryPath: summaryPath,
            summaryPresent: summaryText !== null,
            policy: {
                workflowLevel: taskPolicy.workflowLevel,
                workflowLevelSource: taskPolicy.workflowLevelSource,
                declaredAgentMode: taskPolicy.declaredAgentMode,
                effectiveAgentMode: taskPolicy.effectiveAgentMode,
                evidenceProfile: taskPolicy.evidenceProfile,
                evidenceProfileSource: taskPolicy.evidenceProfileSource
            },
            lifecycle: lifecycleProjection(lifecycle),
            compatibilityEvidence: sortEvidence(evidence),
            evidenceGaps: sortGaps(evidenceGaps)
        },
        groupKey: layout.groupKey,
        proposedWorkflowLevel: layout.ambiguityReasons.length > 0
            ? null
            : layout.proposedWorkflowLevel,
        layoutKind: layout.layoutKind,
        sliceId: layout.sliceId,
        formerContainerIdentity: layout.formerContainerIdentity,
        sourcePaths: uniqueSorted([
            taskDefinitionPath,
            ...(summaryText === null ? [] : [summaryPath]),
            ...layout.sourcePaths
        ]),
        ambiguityReasons: sortGaps(layout.ambiguityReasons),
        active: lifecycle.task.status !== "completed" && lifecycle.task.status !== "cancelled"
    };
}
function projectLayout(options) {
    const sourcePaths = [];
    const evidenceGaps = [];
    const ambiguityReasons = [];
    const taskPath = options.taskDefinitionPath;
    const relativeTaskPath = node_path_1.default.posix.relative(options.planningRootPath, taskPath);
    const governedLayout = classifyGovernedPhysicalLayout(relativeTaskPath);
    const directMatch = relativeTaskPath.match(/^(.+)\/tasks\/([^/]+)\/task-definition\.md$/);
    const hasStructuredParent = options.parentKinds.includes("work_overview");
    const hasGovernedParent = ["slice_overview", "phase_overview", "milestone_overview"].some((kind) => options.parentKinds.includes(kind));
    if (hasStructuredParent && hasGovernedParent) {
        ambiguityReasons.push(gap("mixed-parent-layout", "upstream", [taskPath], "Legacy task mixes Structured and Governed parent relationships."));
    }
    for (const finding of options.policyFindings) {
        if (policyFindingPreventsLevelProposal(finding)) {
            const destination = options.workflowLevelSource === "artifact"
                && options.workflowLevel !== null
                && !policyFindingInvalidatesExplicitLevel(finding)
                ? evidenceGaps
                : ambiguityReasons;
            destination.push(gap(`policy-${finding.code}`, finding.field, [taskPath], finding.message));
        }
    }
    if (governedLayout !== null) {
        const workRoot = repositoryPath(node_path_1.default.posix.join(options.planningRootPath, governedLayout.relativeRoot));
        const overviewCandidates = [
            {
                expectedType: "slice_overview",
                candidates: [
                    node_path_1.default.posix.join(workRoot, "slice-overview.md"),
                    node_path_1.default.posix.join(workRoot, "overview.md")
                ]
            },
            {
                expectedType: "phase_overview",
                candidates: [
                    node_path_1.default.posix.join(workRoot, "phases", governedLayout.phaseId, "phase-overview.md"),
                    node_path_1.default.posix.join(workRoot, "phases", governedLayout.phaseId, "overview.md")
                ]
            },
            {
                expectedType: "milestone_overview",
                candidates: [
                    node_path_1.default.posix.join(workRoot, "phases", governedLayout.phaseId, "milestones", governedLayout.milestoneId, "milestone-overview.md"),
                    node_path_1.default.posix.join(workRoot, "phases", governedLayout.phaseId, "milestones", governedLayout.milestoneId, "overview.md")
                ]
            }
        ];
        for (const overview of overviewCandidates) {
            const resolved = overview.candidates.find((candidate) => repositoryFileExists(options.repositoryRoot, candidate));
            if (resolved === undefined) {
                ambiguityReasons.push(gap("incomplete-governed-hierarchy", "layout", [overview.candidates[0]], `Legacy Governed layout is missing its ${overview.expectedType.replace(/_/g, " ")}.`));
                continue;
            }
            sourcePaths.push(resolved);
            const artifactType = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(readRepositoryFile(options.repositoryRoot, resolved)).frontMatterFields.artifact_type ?? null;
            if (artifactType !== overview.expectedType) {
                ambiguityReasons.push(gap("unsupported-overview-content", "artifact_type", [resolved], `Expected '${overview.expectedType}' but found '${artifactType ?? "(missing)"}'.`));
            }
        }
        const validPathSliceId = ORDERED_SLICE_ID_PATTERN.test(governedLayout.sliceIdentityCandidate)
            ? governedLayout.sliceIdentityCandidate
            : null;
        if (validPathSliceId === null) {
            const missingOrder = SLUG_PATTERN.test(governedLayout.sliceIdentityCandidate);
            evidenceGaps.push(gap(missingOrder ? "missing-ordered-slice-order" : "invalid-ordered-slice-identity", "slice", [workRoot], missingOrder
                ? `Governed layout identity '${governedLayout.sliceIdentityCandidate}' has no implementation order prefix of at least two digits.`
                : `Governed layout identity '${governedLayout.sliceIdentityCandidate}' is not a valid ordered slice id.`));
        }
        const declaredSliceId = options.taskDocument.frontMatterFields.slice ?? null;
        if (declaredSliceId === null) {
            evidenceGaps.push(gap("missing-declared-slice-identity", "slice", [taskPath], "Governed task metadata does not declare its slice identity."));
        }
        else if (!ORDERED_SLICE_ID_PATTERN.test(declaredSliceId)) {
            evidenceGaps.push(gap("invalid-declared-slice-identity", "slice", [taskPath], `Governed task declares non-ordered slice identity '${declaredSliceId}'.`));
        }
        if (declaredSliceId !== null
            && declaredSliceId !== governedLayout.sliceIdentityCandidate) {
            ambiguityReasons.push(gap("conflicting-slice-identity", "slice", [taskPath, workRoot], `Governed path identity '${governedLayout.sliceIdentityCandidate}' conflicts with declared slice '${declaredSliceId}'.`));
        }
        if (options.workflowLevelSource === "artifact"
            && options.workflowLevel !== null
            && options.workflowLevel !== "governed") {
            ambiguityReasons.push(gap("workflow-level-layout-mismatch", "workflow_level", [taskPath], `Physical Governed hierarchy conflicts with explicit workflow level '${options.workflowLevel}'.`));
        }
        return {
            groupKey: `governed:${workRoot}`,
            proposedWorkflowLevel: "governed",
            layoutKind: governedLayout.layoutKind,
            sliceId: validPathSliceId,
            formerContainerIdentity: governedLayout.formerContainerIdentity,
            sourcePaths,
            evidenceGaps,
            ambiguityReasons
        };
    }
    if (options.workflowLevelSource === "artifact" && options.workflowLevel !== null) {
        return {
            groupKey: `${options.workflowLevel}:${deriveExplicitGroup(relativeTaskPath, options.workflowLevel)}`,
            proposedWorkflowLevel: options.workflowLevel,
            layoutKind: options.workflowLevel === "structured"
                ? "structured-work-root"
                : options.workflowLevel === "governed"
                    ? "legacy-governed-root"
                    : "basic-task-root",
            sliceId: null,
            formerContainerIdentity: null,
            sourcePaths,
            evidenceGaps,
            ambiguityReasons
        };
    }
    const planningDepth = options.taskDocument.frontMatterFields.planning_depth ?? null;
    if (directMatch !== null) {
        const [, relativeRoot] = directMatch;
        const workRoot = repositoryPath(node_path_1.default.posix.join(options.planningRootPath, relativeRoot));
        const workOverview = [
            node_path_1.default.posix.join(workRoot, "work-overview.md"),
            node_path_1.default.posix.join(workRoot, "slice-overview.md"),
            node_path_1.default.posix.join(workRoot, "overview.md")
        ].find((candidate) => repositoryFileExists(options.repositoryRoot, candidate));
        const directStructured = hasStructuredParent
            || (hasGovernedParent && planningDepth === "lightweight")
            || workOverview !== undefined;
        if (directStructured) {
            if (workOverview === undefined) {
                ambiguityReasons.push(gap("missing-direct-work-overview", "layout", [node_path_1.default.posix.join(workRoot, "overview.md")], "Legacy direct/lightweight work has no readable overview."));
            }
            else {
                sourcePaths.push(workOverview);
                const artifactType = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(readRepositoryFile(options.repositoryRoot, workOverview)).frontMatterFields.artifact_type ?? null;
                if (artifactType !== "work_overview" && artifactType !== "slice_overview") {
                    ambiguityReasons.push(gap("unsupported-overview-content", "artifact_type", [workOverview], `Direct legacy work overview has unsupported artifact type '${artifactType ?? "(missing)"}'.`));
                }
            }
            return {
                groupKey: `structured:${workRoot}`,
                proposedWorkflowLevel: "structured",
                layoutKind: "structured-work-root",
                sliceId: null,
                formerContainerIdentity: null,
                sourcePaths,
                evidenceGaps,
                ambiguityReasons
            };
        }
    }
    if (hasGovernedParent) {
        ambiguityReasons.push(gap("incomplete-governed-hierarchy", "layout", [taskPath], "Governed parent evidence exists outside a complete Governed hierarchy."));
    }
    return {
        groupKey: `basic:${node_path_1.default.posix.dirname(taskPath)}`,
        proposedWorkflowLevel: "basic",
        layoutKind: "basic-task-root",
        sliceId: null,
        formerContainerIdentity: null,
        sourcePaths,
        evidenceGaps,
        ambiguityReasons
    };
}
function classifyGovernedPhysicalLayout(relativeTaskPath) {
    const former = relativeTaskPath.match(/^governed\/([^/]+)\/slices\/([^/]+)\/phases\/([^/]+)\/milestones\/([^/]+)\/tasks\/[^/]+\/task-definition\.md$/);
    if (former !== null) {
        const [, formerContainerIdentity, sliceIdentityCandidate, phaseId, milestoneId] = former;
        return {
            layoutKind: "former-governed-container-slice",
            relativeRoot: node_path_1.default.posix.join("governed", formerContainerIdentity, "slices", sliceIdentityCandidate),
            sliceIdentityCandidate,
            formerContainerIdentity,
            phaseId,
            milestoneId
        };
    }
    const canonical = relativeTaskPath.match(/^governed\/([^/]+)\/phases\/([^/]+)\/milestones\/([^/]+)\/tasks\/[^/]+\/task-definition\.md$/);
    if (canonical !== null) {
        const [, sliceIdentityCandidate, phaseId, milestoneId] = canonical;
        return {
            layoutKind: "canonical-governed-numbered-root",
            relativeRoot: node_path_1.default.posix.join("governed", sliceIdentityCandidate),
            sliceIdentityCandidate,
            formerContainerIdentity: null,
            phaseId,
            milestoneId
        };
    }
    const topLevel = relativeTaskPath.match(/^([^/]+)\/phases\/([^/]+)\/milestones\/([^/]+)\/tasks\/[^/]+\/task-definition\.md$/);
    if (topLevel !== null) {
        const [, sliceIdentityCandidate, phaseId, milestoneId] = topLevel;
        return {
            layoutKind: ORDERED_SLICE_ID_PATTERN.test(sliceIdentityCandidate)
                ? "legacy-top-level-numbered-root"
                : "legacy-governed-root",
            relativeRoot: sliceIdentityCandidate,
            sliceIdentityCandidate,
            formerContainerIdentity: null,
            phaseId,
            milestoneId
        };
    }
    const generic = relativeTaskPath.match(/^(.+)\/phases\/([^/]+)\/milestones\/([^/]+)\/tasks\/[^/]+\/task-definition\.md$/);
    if (generic === null)
        return null;
    const [, relativeRoot, phaseId, milestoneId] = generic;
    return {
        layoutKind: "legacy-governed-root",
        relativeRoot,
        sliceIdentityCandidate: node_path_1.default.posix.basename(relativeRoot),
        formerContainerIdentity: null,
        phaseId,
        milestoneId
    };
}
function groupWorkUnits(drafts) {
    const groups = new Map();
    for (const draft of drafts) {
        const group = groups.get(draft.groupKey) ?? [];
        group.push(draft);
        groups.set(draft.groupKey, group);
    }
    const units = [];
    for (const [groupKey, group] of groups) {
        const levels = [...new Set(group
                .map((draft) => draft.proposedWorkflowLevel)
                .filter((level) => level !== null))]
            .sort((left, right) => left.localeCompare(right));
        const ambiguityReasons = sortGaps(group.flatMap((draft) => draft.ambiguityReasons));
        if (levels.length > 1) {
            ambiguityReasons.push(gap("conflicting-layout-levels", "workflow_level", group.flatMap((draft) => draft.sourcePaths), `Grouped legacy work projects conflicting levels: ${levels.join(", ")}.`));
        }
        const layoutKinds = [...new Set(group.map((draft) => draft.layoutKind))]
            .sort((left, right) => left.localeCompare(right));
        if (layoutKinds.length > 1) {
            ambiguityReasons.push(gap("conflicting-layout-kinds", "layout", group.flatMap((draft) => draft.sourcePaths), `Grouped legacy work has conflicting physical layouts: ${layoutKinds.join(", ")}.`));
        }
        const sliceIds = uniqueSorted(group
            .map((draft) => draft.sliceId)
            .filter((sliceId) => sliceId !== null));
        if (sliceIds.length > 1) {
            ambiguityReasons.push(gap("conflicting-slice-identity", "slice", group.flatMap((draft) => draft.sourcePaths), `Grouped legacy work has conflicting ordered slice identities: ${sliceIds.join(", ")}.`));
        }
        const formerContainerIdentities = uniqueSorted(group
            .map((draft) => draft.formerContainerIdentity)
            .filter((identity) => identity !== null));
        if (formerContainerIdentities.length > 1) {
            ambiguityReasons.push(gap("conflicting-former-container-identity", "layout", group.flatMap((draft) => draft.sourcePaths), `Grouped legacy work has conflicting former container identities: ${formerContainerIdentities.join(", ")}.`));
        }
        const ambiguous = ambiguityReasons.length > 0
            || group.some((draft) => draft.proposedWorkflowLevel === null)
            || levels.length !== 1;
        const taskPairs = [...group]
            .map((draft) => draft.pair)
            .sort((left, right) => left.taskDefinitionPath.localeCompare(right.taskDefinitionPath));
        units.push({
            workUnitId: `legacy-unit-${(0, node_crypto_1.createHash)("sha256").update(groupKey).digest("hex").slice(0, 12)}`,
            proposalStatus: ambiguous ? "ambiguous" : "proposed",
            proposedWorkflowLevel: ambiguous ? null : levels[0],
            layoutKind: layoutKinds.length === 1 ? layoutKinds[0] : null,
            sliceId: sliceIds.length === 1 ? sliceIds[0] : null,
            formerContainerIdentity: layoutKinds.length === 1
                && layoutKinds[0] === "former-governed-container-slice"
                && formerContainerIdentities.length === 1
                ? formerContainerIdentities[0]
                : null,
            sourcePaths: uniqueSorted(group.flatMap((draft) => draft.sourcePaths)),
            taskPairs,
            compatibilityEvidence: sortEvidence(taskPairs.flatMap((pair) => pair.compatibilityEvidence)),
            evidenceGaps: sortGaps(taskPairs.flatMap((pair) => pair.evidenceGaps)),
            ambiguityReasons: sortGaps(ambiguityReasons)
        });
    }
    return units.sort((left, right) => (left.sourcePaths[0] ?? left.workUnitId).localeCompare(right.sourcePaths[0] ?? right.workUnitId));
}
function includeUnclaimedLegacyFiles(repositoryRoot, planningRootPath, units) {
    const claimed = new Set(units.flatMap((unit) => unit.sourcePaths));
    const byNumericRoot = new Map();
    units.forEach((unit, index) => {
        for (const sourcePath of unit.sourcePaths) {
            const relative = node_path_1.default.posix.relative(planningRootPath, sourcePath);
            const rootName = relative.split("/")[0];
            if (!/^\d{2}-/.test(rootName))
                continue;
            const rootPath = repositoryPath(node_path_1.default.posix.join(planningRootPath, rootName));
            const indexes = byNumericRoot.get(rootPath) ?? [];
            if (!indexes.includes(index))
                indexes.push(index);
            byNumericRoot.set(rootPath, indexes);
        }
    });
    const residualsByUnit = new Map();
    for (const [rootPath, candidateIndexes] of byNumericRoot) {
        const rootAbsolute = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(rootPath));
        const unclaimed = walkRegularFiles(rootAbsolute)
            .map((absolutePath) => repositoryPath(node_path_1.default.posix.join(rootPath, node_path_1.default.relative(rootAbsolute, absolutePath).split(node_path_1.default.sep).join("/"))))
            .filter((sourcePath) => !claimed.has(sourcePath))
            .sort();
        if (unclaimed.length === 0)
            continue;
        const selectedIndex = [...candidateIndexes].sort((left, right) => units[right].sourcePaths.length - units[left].sourcePaths.length
            || units[left].workUnitId.localeCompare(units[right].workUnitId))[0];
        residualsByUnit.set(selectedIndex, unclaimed);
    }
    return units.map((unit, index) => ({
        ...unit,
        sourcePaths: uniqueSorted([
            ...unit.sourcePaths,
            ...(residualsByUnit.get(index) ?? [])
        ])
    }));
}
function walkRegularFiles(root) {
    const files = [];
    const visit = (directory) => {
        for (const entry of node_fs_1.default.readdirSync(directory, { withFileTypes: true })) {
            const absolutePath = node_path_1.default.join(directory, entry.name);
            if (entry.isDirectory())
                visit(absolutePath);
            if (entry.isFile())
                files.push(absolutePath);
        }
    };
    visit(root);
    return files.sort();
}
function applyDuplicateIdentityAmbiguity(drafts) {
    const byIdentity = new Map();
    for (const draft of drafts) {
        if (!draft.active || draft.pair.taskId === null)
            continue;
        const group = byIdentity.get(draft.pair.taskId) ?? [];
        group.push(draft);
        byIdentity.set(draft.pair.taskId, group);
    }
    for (const [identity, group] of byIdentity) {
        if (group.length < 2)
            continue;
        const paths = uniqueSorted(group.map((draft) => draft.pair.taskDefinitionPath));
        for (const draft of group) {
            draft.ambiguityReasons.push(gap("duplicate-active-identity", "id", paths, `Active legacy task identity '${identity}' appears at multiple locations.`));
            draft.proposedWorkflowLevel = null;
        }
    }
}
function applyDuplicateSliceIdentityAmbiguity(drafts) {
    const bySliceIdentity = new Map();
    for (const draft of drafts) {
        if (draft.sliceId === null)
            continue;
        const group = bySliceIdentity.get(draft.sliceId) ?? [];
        group.push(draft);
        bySliceIdentity.set(draft.sliceId, group);
    }
    for (const [sliceId, group] of bySliceIdentity) {
        const roots = new Set(group.map((draft) => draft.groupKey));
        if (roots.size < 2)
            continue;
        const paths = uniqueSorted(group.map((draft) => draft.pair.taskDefinitionPath));
        for (const draft of group) {
            draft.ambiguityReasons.push(gap("duplicate-slice-identity", "slice", paths, `Ordered slice identity '${sliceId}' appears in multiple physical planning roots.`));
            draft.proposedWorkflowLevel = null;
        }
    }
}
function policyFindingPreventsLevelProposal(finding) {
    return finding.code !== "missing-evidence-profile"
        && !finding.code.startsWith("missing-readiness")
        && finding.code !== "unmaterialized-readiness-review";
}
function policyFindingInvalidatesExplicitLevel(finding) {
    return finding.field === "workflow_level"
        || finding.field === "upstream"
        || finding.field.startsWith("upstream.");
}
function policyEvidence(artifactPath, evidence) {
    return evidence.map((entry) => ({ ...entry, path: artifactPath }));
}
function lifecycleEvidence(taskDefinitionPath, summaryPath, evidence) {
    return evidence.map((entry) => ({
        code: entry.code,
        field: entry.field,
        path: entry.artifact === "implementation_summary" ? summaryPath : taskDefinitionPath,
        sourceValue: entry.sourceValue,
        projectedValue: entry.projectedValue
    }));
}
function policyGaps(artifactPath, findings) {
    return findings.map((finding) => gap(`policy-${finding.code}`, finding.field, [artifactPath], finding.message));
}
function lifecycleProjection(pair) {
    return {
        readiness: pair.task.readiness,
        status: pair.task.status,
        implementationStatus: pair.implementationStatus,
        reviewDisposition: pair.reviewDisposition,
        terminal: pair.terminal
    };
}
function deriveExplicitGroup(relativeTaskPath, level) {
    if (level === "basic")
        return node_path_1.default.posix.dirname(relativeTaskPath);
    if (level === "structured") {
        const match = relativeTaskPath.match(/^(.+)\/tasks\/[^/]+\/task-definition\.md$/);
        return match?.[1] ?? node_path_1.default.posix.dirname(relativeTaskPath);
    }
    const match = relativeTaskPath.match(/^(.+)\/phases\/[^/]+\/milestones\/[^/]+\/tasks\//);
    return match?.[1] ?? node_path_1.default.posix.dirname(relativeTaskPath);
}
function readRepositoryFile(repositoryRoot, repositoryPathValue) {
    return node_fs_1.default.readFileSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(repositoryPathValue)), "utf8");
}
function readRepositoryFileIfExists(repositoryRoot, repositoryPathValue) {
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(repositoryPathValue));
    return node_fs_1.default.existsSync(absolutePath) && node_fs_1.default.statSync(absolutePath).isFile()
        ? node_fs_1.default.readFileSync(absolutePath, "utf8")
        : null;
}
function repositoryFileExists(repositoryRoot, repositoryPathValue) {
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, (0, paths_1.parseRepositoryRelativePath)(repositoryPathValue));
    return node_fs_1.default.existsSync(absolutePath) && node_fs_1.default.statSync(absolutePath).isFile();
}
function repositoryPath(input) {
    return input.split(node_path_1.default.sep).join("/");
}
function gap(code, field, paths, message) {
    return { code, field, paths: uniqueSorted(paths), message };
}
function sortEvidence(evidence) {
    return uniqueBy(evidence, (entry) => [entry.path, entry.code, entry.field, entry.sourceValue, entry.projectedValue].join("\0")).sort((left, right) => [
        left.path, left.code, left.field, left.sourceValue ?? "", left.projectedValue ?? ""
    ].join("\0").localeCompare([
        right.path, right.code, right.field, right.sourceValue ?? "", right.projectedValue ?? ""
    ].join("\0")));
}
function sortGaps(gaps) {
    return uniqueBy(gaps.map((entry) => ({ ...entry, paths: uniqueSorted(entry.paths) })), (entry) => [entry.code, entry.field, entry.paths.join(","), entry.message].join("\0")).sort((left, right) => [
        left.paths[0] ?? "", left.code, left.field, left.message
    ].join("\0").localeCompare([
        right.paths[0] ?? "", right.code, right.field, right.message
    ].join("\0")));
}
function sortFindings(findings) {
    return uniqueBy(findings.map((entry) => ({ ...entry, paths: uniqueSorted(entry.paths) })), (entry) => [entry.code, entry.paths.join(","), entry.message].join("\0")).sort((left, right) => [
        left.paths[0] ?? "", left.code, left.message
    ].join("\0").localeCompare([
        right.paths[0] ?? "", right.code, right.message
    ].join("\0")));
}
function uniqueSorted(values) {
    return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function uniqueBy(values, key) {
    const seen = new Set();
    return values.filter((value) => {
        const candidate = key(value);
        if (seen.has(candidate))
            return false;
        seen.add(candidate);
        return true;
    });
}
