#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * WU Prep Helper (WU-1223)
 *
 * Prepares a WU for completion by running gates and generating docs in the worktree.
 * After successful prep, prints copy-paste instruction to run wu:done from main.
 *
 * WU-1344: When gates fail on spec:linter due to pre-existing WU validation errors
 * (not caused by the current WU), prints a ready-to-copy wu:done --skip-gates
 * command with reason and fix-wu placeholders.
 *
 * WU-1493: Adds branch-pr mode support. When a WU has claimed_mode: branch-pr,
 * wu:prep reads the mode before rejecting non-worktree locations, validates that
 * the current branch matches the expected lane branch, and outputs PR-based
 * completion next steps on success.
 *
 * Workflow:
 * 1. Read WU YAML to check claimed_mode
 * 2. For worktree mode: verify we're in a worktree (error if in main checkout)
 *    For branch-pr mode: verify we're on the correct lane branch
 * 3. Run gates
 * 4. If gates fail, check if failures are pre-existing on main
 * 5. If pre-existing, print skip-gates command; otherwise, print fix guidance
 * 6. On success, print mode-appropriate next steps
 *
 * Usage:
 *   pnpm wu:prep --id WU-XXX [--docs-only] [--full-tests]
 *
 * @module
 */
import { spawnSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { minimatch } from 'minimatch';
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/arg-parser';
import { die } from '@lumenflow/core/error-handler';
import { resolveLocation } from '@lumenflow/core/context/location-resolver';
import { readWU } from '@lumenflow/core/wu-yaml';
import { WU_PATHS } from '@lumenflow/core/wu-paths';
import { createGitForPath } from '@lumenflow/core/git-adapter';
import { TEST_FILE_PATTERNS } from '@lumenflow/core';
import { resolveChangedFiles, isCodePathCoveredByChangedFiles, resolveBaseRef, } from '@lumenflow/core/wu-rules-engine';
import { validatePreflight, formatPreflightResult, formatPreflightWarnings, } from '@lumenflow/core/wu-preflight-validators';
import { CONTEXT_VALIDATION, PATTERNS, EXIT_CODES, WU_STATUS, EMOJI, CLAIMED_MODES, } from '@lumenflow/core/wu-constants';
import { defaultBranchFrom } from '@lumenflow/core/wu-done-paths';
import { getCurrentBranch } from '@lumenflow/core/wu-helpers';
import { createPreGatesCheckpoint } from '@lumenflow/core/wu-checkpoint';
import { resolveTddDiffEvidencePolicy, resolveTddOrderingPolicy, resolveScopedUnitTestExecutionPlan, formatScopedUnitTestFallbackMessage, } from '@lumenflow/packs-software-delivery/policy/gates-config';
import { runGates } from './gates.js';
import { evaluateMainDirtyMutationGuard } from './hooks/dirty-guard.js';
import { enforceWuBriefEvidenceForPrep, resolveWuBriefFreshnessMinutes, resolveWuBriefPolicyMode, } from './wu-done-policies.js';
import { resolveScopedUnitTestsForLifecycle } from './wu-lifecycle-sync/gate-scope-resolver.js';
export { isCodePathCoveredByChanges, findMissingCodePathCoverage, checkCodePathCoverageBeforeGates, formatCodePathCoverageFailure, } from './wu-code-path-coverage.js';
const { LOCATION_TYPES } = CONTEXT_VALIDATION;
const TDD_EXCEPTION_MARKER = 'tdd-exception:';
const CHANGED_FILES_EMPTY = '  - (none)';
const AUTOMATED_TEST_DIFF_EVIDENCE_LABEL = 'automated test diff evidence';
const GLOB_MATCH_OPTIONS = { dot: true };
const BOOTSTRAP_TRAP_FIX_WU_PLACEHOLDER = 'WU-XXXX';
const BOOTSTRAP_TRAP_PACKAGE_EXPORT_SKIP_REASON = 'bootstrap-trap: pack file moves cause wu:prep cross-module resolution; worktree build green';
const BOOTSTRAP_TRAP_PACKAGE_EXPORT_PATTERNS = [
    /ERR_PACKAGE_PATH_NOT_EXPORTED/i,
    /Package subpath ['"`][^'"`]+['"`] is not defined by "exports"/i,
];
const BOOTSTRAP_TRAP_DEPENDENCY_CYCLE_PATTERNS = [
    /Invalid package dependency graph/i,
    /\bcyclic dependency\b/i,
    /\bdependency cycle\b/i,
    /\bturbo cycle\b/i,
];
const SCOPED_UNIT_TEST_COMPARISON_TIMEOUT_MS = 90_000;
/**
 * Log prefix for wu:prep command output.
 */
const PREP_PREFIX = '[wu-prep]';
/**
 * GATES_OPTIONS for wu:prep command.
 * Subset of gates options relevant for prep.
 */
const PREP_OPTIONS = {
    docsOnly: {
        name: 'docsOnly',
        flags: '--docs-only',
        description: 'Run docs-only gates (format, spec-linter)',
    },
    fullTests: {
        name: 'fullTests',
        flags: '--full-tests',
        description: 'Run full incremental test suite (disable tests.unit scoped execution)',
    },
    force: WU_OPTIONS.force,
    reason: WU_OPTIONS.reason,
};
export const resolveScopedUnitTestsForPrep = resolveScopedUnitTestsForLifecycle;
export function buildRunGatesOptionsForPrep(options) {
    return {
        cwd: options.cwd,
        docsOnly: options.docsOnly,
        fullTests: options.fullTests,
        scopedTestPaths: options.scopedTestPaths,
        commandContext: 'wu:prep',
    };
}
/**
 * WU-2587: Automated test diff evidence is enforced from the resolved
 * tdd_diff_evidence policy instead of a hardcoded feature/bug check.
 */
export function shouldEnforceTddDiffEvidence(doc, cwd) {
    return shouldEnforceTddDiffEvidenceForPolicy(doc, resolveTddDiffEvidencePolicy(cwd || process.cwd()));
}
export function hasDocumentedTddException(notes) {
    if (typeof notes === 'string') {
        return notes.toLowerCase().includes(TDD_EXCEPTION_MARKER);
    }
    if (Array.isArray(notes)) {
        return notes.some((entry) => typeof entry === 'string' && hasDocumentedTddException(entry));
    }
    return false;
}
function normalizeDeclaredAutomatedTestPaths(tests) {
    const buckets = ['unit', 'e2e', 'integration'];
    const paths = [];
    for (const bucket of buckets) {
        const value = tests?.[bucket];
        if (!Array.isArray(value)) {
            continue;
        }
        for (const entry of value) {
            if (typeof entry !== 'string') {
                continue;
            }
            const normalized = entry.trim();
            if (normalized.length === 0) {
                continue;
            }
            paths.push(normalized);
        }
    }
    return paths;
}
function isTestFileFromDiff(filePath) {
    const normalized = filePath.replace(/\\/g, '/');
    return TEST_FILE_PATTERNS.some((pattern) => pattern.test(normalized));
}
function shouldEnforceTddDiffEvidenceForPolicy(doc, policy) {
    return (policy.mode === 'block' &&
        typeof doc.type === 'string' &&
        policy.applies_to_types.some((wuType) => wuType === doc.type));
}
export function areChangedFilesExemptFromTddDiffEvidence(options) {
    const { changedFiles, exemptPaths } = options;
    if (changedFiles.length === 0 || exemptPaths.length === 0) {
        return false;
    }
    return changedFiles.every((filePath) => {
        const normalizedPath = filePath.replace(/\\/g, '/');
        return exemptPaths.some((pattern) => minimatch(normalizedPath, pattern, GLOB_MATCH_OPTIONS));
    });
}
export function hasTddEvidenceInWorkingDiff(options) {
    const { changedFiles, declaredTestPaths } = options;
    if (changedFiles.some((filePath) => isTestFileFromDiff(filePath))) {
        return true;
    }
    for (const testPath of declaredTestPaths) {
        if (isCodePathCoveredByChangedFiles({ codePath: testPath, changedFiles })) {
            return true;
        }
    }
    return false;
}
export async function evaluateTddDiffEvidenceForPrep(options) {
    const { doc } = options;
    const policy = resolveTddDiffEvidencePolicy(options.cwd);
    if (!shouldEnforceTddDiffEvidenceForPolicy(doc, policy)) {
        return { valid: true, reason: 'not-applicable', changedFiles: [] };
    }
    if (hasDocumentedTddException(doc.notes)) {
        return { valid: true, reason: 'documented-exception', changedFiles: [] };
    }
    const resolveChangedFilesFn = options.resolveChangedFilesFn ?? resolveChangedFiles;
    const changedFilesResult = await resolveChangedFilesFn({ cwd: options.cwd });
    if (!changedFilesResult.ok) {
        return {
            valid: false,
            reason: 'diff-unavailable',
            changedFiles: [],
            diffReason: changedFilesResult.reason,
        };
    }
    if (areChangedFilesExemptFromTddDiffEvidence({
        changedFiles: changedFilesResult.files,
        exemptPaths: policy.exempt_paths,
    })) {
        return {
            valid: true,
            reason: 'policy-exempt-paths',
            changedFiles: changedFilesResult.files,
            baseRef: changedFilesResult.baseRef,
            headRef: changedFilesResult.headRef,
        };
    }
    const declaredTestPaths = normalizeDeclaredAutomatedTestPaths(doc.tests ?? {});
    const hasEvidence = hasTddEvidenceInWorkingDiff({
        changedFiles: changedFilesResult.files,
        declaredTestPaths,
    });
    if (hasEvidence) {
        return {
            valid: true,
            reason: 'test-diff-evidence-found',
            changedFiles: changedFilesResult.files,
            baseRef: changedFilesResult.baseRef,
            headRef: changedFilesResult.headRef,
        };
    }
    return {
        valid: false,
        reason: 'missing-test-diff-evidence',
        changedFiles: changedFilesResult.files,
        baseRef: changedFilesResult.baseRef,
        headRef: changedFilesResult.headRef,
    };
}
function formatTddDiffEvidenceScopeLabel(wuType) {
    return typeof wuType === 'string' && wuType.trim().length > 0 ? `${wuType} WUs` : 'This WU';
}
export function formatTddDiffEvidenceFailure(options) {
    const { wuId, wuType, result } = options;
    if (result.reason === 'diff-unavailable') {
        return (`${EMOJI.FAILURE} ${AUTOMATED_TEST_DIFF_EVIDENCE_LABEL} check failed for ${wuId}.\n\n` +
            `Unable to evaluate the branch diff needed for ${AUTOMATED_TEST_DIFF_EVIDENCE_LABEL}: ${result.diffReason || 'unknown diff error'}\n\n` +
            `Fix options:\n` +
            `  1. Ensure base ref is available (origin/main or main)\n` +
            `  2. Retry: pnpm wu:prep --id ${wuId}`);
    }
    const changedSection = result.changedFiles.length > 0
        ? result.changedFiles.map((filePath) => `  - ${filePath}`).join('\n')
        : CHANGED_FILES_EMPTY;
    return (`${EMOJI.FAILURE} Missing ${AUTOMATED_TEST_DIFF_EVIDENCE_LABEL} for ${wuId}.\n\n` +
        `${formatTddDiffEvidenceScopeLabel(wuType)} require at least one automated test file change before wu:prep.\n\n` +
        `Changed files (${result.baseRef || 'main'}...${result.headRef || 'HEAD'}):\n` +
        `${changedSection}\n\n` +
        `Fix options:\n` +
        `  1. Add/update test files in this branch and retry wu:prep\n` +
        `  2. If this change is intentionally exempt, document it in WU notes with:\n` +
        `     ${TDD_EXCEPTION_MARKER} <reason>`);
}
/**
 * Default branch-commit resolver: reads `git log --numstat base..HEAD`.
 *
 * Returns commits in chronological order (oldest first) — the natural order
 * for enforcing RED-first ordering. Uses `--reverse` so the output matches
 * commit authorship order on the branch.
 */
async function defaultResolveBranchCommits(options) {
    const baseRef = await resolveBaseRef({ cwd: options.cwd });
    if (!baseRef) {
        return { ok: false, reason: 'base ref unavailable (origin/main or main)' };
    }
    const headRef = 'HEAD';
    const git = createGitForPath(options.cwd);
    try {
        const raw = await git.raw([
            'log',
            `${baseRef}..${headRef}`,
            '--reverse',
            '--numstat',
            '--format=__WU_COMMIT__%H%x09%s',
            '--no-merges',
        ]);
        return { ok: true, commits: parseBranchCommits(raw), baseRef, headRef };
    }
    catch (error) {
        return { ok: false, reason: error instanceof Error ? error.message : String(error) };
    }
}
/**
 * Parse the output of `git log --numstat --format=__WU_COMMIT__<hash>\t<subject>`.
 *
 * Numstat line format: `<added>\t<deleted>\t<path>` where added/deleted may be
 * `-` for binary files.
 */
export function parseBranchCommits(raw) {
    const commits = [];
    const lines = raw.split('\n');
    let current = null;
    for (const rawLine of lines) {
        const line = rawLine.trimEnd();
        if (line.startsWith('__WU_COMMIT__')) {
            if (current) {
                commits.push(current);
            }
            const header = line.slice('__WU_COMMIT__'.length);
            const tab = header.indexOf('\t');
            const hash = tab === -1 ? header : header.slice(0, tab);
            const subject = tab === -1 ? '' : header.slice(tab + 1);
            current = { hash, subject, files: [] };
            continue;
        }
        if (!current || line.length === 0) {
            continue;
        }
        const parts = line.split('\t');
        if (parts.length < 3) {
            continue;
        }
        const [addedRaw, deletedRaw, ...pathParts] = parts;
        const filePath = pathParts.join('\t');
        const added = addedRaw === '-' ? 0 : parseInt(addedRaw, 10);
        const deleted = deletedRaw === '-' ? 0 : parseInt(deletedRaw, 10);
        if (Number.isNaN(added) || Number.isNaN(deleted) || !filePath) {
            continue;
        }
        current.files.push({ path: filePath, added, deleted });
    }
    if (current) {
        commits.push(current);
    }
    return commits;
}
/**
 * True when a commit subject introduces runtime behaviour (feat or fix).
 * Uses the standard Conventional Commits prefixes; scope is optional.
 */
function isRuntimeIntroducingCommit(subject) {
    const trimmed = subject.trim().toLowerCase();
    const prefix = trimmed.startsWith('feat') ? 'feat' : trimmed.startsWith('fix') ? 'fix' : null;
    if (!prefix) {
        return false;
    }
    const remainder = trimmed.slice(prefix.length);
    if (remainder.startsWith(':') || remainder.startsWith('!:')) {
        return true;
    }
    if (!remainder.startsWith('(')) {
        return false;
    }
    const scopeEndIndex = remainder.indexOf(')');
    if (scopeEndIndex <= 1) {
        return false;
    }
    const afterScope = remainder.slice(scopeEndIndex + 1);
    return afterScope.startsWith(':') || afterScope.startsWith('!:');
}
function isCommitFileExempt(filePath, exemptPaths) {
    if (exemptPaths.length === 0) {
        return false;
    }
    const normalized = filePath.replace(/\\/g, '/');
    return exemptPaths.some((pattern) => minimatch(normalized, pattern, GLOB_MATCH_OPTIONS));
}
/**
 * WU-2650: Evaluate whether the branch's commit order satisfies the RED-first
 * policy. Mirrors `evaluateTddDiffEvidenceForPrep`:
 *
 *   1. Skip if WU type is not covered by the resolved policy (applies_to_types)
 *      or mode !== 'block'.
 *   2. Honour a `tdd-exception:` marker in notes.
 *   3. Walk commits in chronological order. If a feat/fix commit adds more than
 *      min_runtime_loc_per_commit_without_tests of non-exempt, non-test LOC and
 *      no earlier (or same) commit within the grace window touched a test file,
 *      flag that commit.
 */
export async function evaluateCommitOrderForPrep(options) {
    const { doc } = options;
    const policy = resolveTddOrderingPolicy(options.cwd);
    if (policy.mode !== 'block') {
        return { valid: true, reason: 'not-applicable' };
    }
    if (typeof doc.type !== 'string' ||
        !policy.applies_to_types.some((wuType) => wuType === doc.type)) {
        return { valid: true, reason: 'not-applicable' };
    }
    if (hasDocumentedTddException(doc.notes)) {
        return { valid: true, reason: 'documented-exception' };
    }
    const resolveCommitsFn = options.resolveBranchCommitsFn ?? defaultResolveBranchCommits;
    const commitsResult = await resolveCommitsFn({ cwd: options.cwd });
    if (!commitsResult.ok) {
        return {
            valid: false,
            reason: 'branch-commits-unavailable',
            diffReason: commitsResult.reason,
        };
    }
    // Commits arrive chronological (oldest first) — the default resolver uses
    // `git log --reverse` so earlier commits are inspected before later ones.
    const commitsChronological = commitsResult.commits;
    let testsSeenBeforeIndex = -1;
    for (let i = 0; i < commitsChronological.length; i++) {
        const commit = commitsChronological[i];
        const hasTestInThisCommit = commit.files.some((file) => isTestFileFromDiff(file.path));
        if (hasTestInThisCommit && testsSeenBeforeIndex === -1) {
            testsSeenBeforeIndex = i;
        }
        if (!isRuntimeIntroducingCommit(commit.subject)) {
            continue;
        }
        // Count non-test, non-exempt runtime LOC added in this commit.
        const runtimeAdded = commit.files.reduce((sum, file) => {
            if (isTestFileFromDiff(file.path))
                return sum;
            if (isCommitFileExempt(file.path, policy.exempt_paths))
                return sum;
            return sum + file.added;
        }, 0);
        if (runtimeAdded < policy.min_runtime_loc_per_commit_without_tests) {
            continue;
        }
        // Ordering is OK when:
        //   - this commit itself contains test files (interleaved), OR
        //   - tests were first seen in an earlier commit, OR
        //   - this commit is within the configured grace window from branch start.
        const testsSeenEarlier = testsSeenBeforeIndex !== -1 && testsSeenBeforeIndex <= i;
        const inGraceWindow = i < policy.grace_commits;
        if (hasTestInThisCommit || testsSeenEarlier || inGraceWindow) {
            continue;
        }
        const testFilesInCommit = commit.files.filter((file) => isTestFileFromDiff(file.path)).length;
        return {
            valid: false,
            reason: 'tests-after-detected',
            baseRef: commitsResult.baseRef,
            headRef: commitsResult.headRef,
            offendingCommit: {
                hash: commit.hash,
                subject: commit.subject,
                runtimeAdded,
                testFilesInCommit,
            },
        };
    }
    return {
        valid: true,
        reason: 'ordering-ok',
        baseRef: commitsResult.baseRef,
        headRef: commitsResult.headRef,
    };
}
export function formatTddOrderingFailure(options) {
    const { wuId, wuType, result } = options;
    if (result.reason === 'branch-commits-unavailable') {
        return (`${EMOJI.FAILURE} commit-order check failed for ${wuId}.\n\n` +
            `Unable to enumerate branch commits: ${result.diffReason || 'unknown git error'}\n\n` +
            `Fix options:\n` +
            `  1. Ensure base ref is available (origin/main or main)\n` +
            `  2. Retry: pnpm wu:prep --id ${wuId}`);
    }
    const typeLabel = typeof wuType === 'string' && wuType.trim().length > 0 ? `${wuType} WUs` : 'This WU';
    const offending = result.offendingCommit;
    const commitLine = offending
        ? `  - ${offending.hash.slice(0, 10)} ${offending.subject}\n` +
            `    runtime LOC added: ${offending.runtimeAdded} (threshold triggered)\n` +
            `    test files in same commit: ${offending.testFilesInCommit}`
        : '  - (unknown commit)';
    return (`${EMOJI.FAILURE} commit-order (RED-first) check failed for ${wuId}.\n\n` +
        `${typeLabel} must introduce tests in a commit preceding or alongside any feat/fix\n` +
        `commit that adds non-trivial runtime code. The offending commit:\n` +
        `${commitLine}\n\n` +
        `Range: ${result.baseRef || 'main'}..${result.headRef || 'HEAD'}\n\n` +
        `Fix options:\n` +
        `  1. Restructure the branch so a test(${wuId.toLowerCase()}): … commit lands first (RED).\n` +
        `  2. Amend the offending commit to include the matching test files.\n` +
        `  3. If intentionally exempt, document it in WU notes with:\n` +
        `     ${TDD_EXCEPTION_MARKER} <reason>`);
}
/**
 * WU-1344: Check if a gate name is the spec:linter gate.
 * Used to identify when spec:linter fails so we can check for pre-existing failures.
 *
 * @param gateName - Name of the gate that failed
 * @returns true if this is the spec:linter gate
 */
export function isPreExistingSpecLinterFailure(gateName) {
    const normalizedName = gateName.toLowerCase().replace(/[:-]/g, '');
    return normalizedName === 'speclinter';
}
/**
 * WU-1344: Format a skip-gates command for wu:done.
 * Includes --reason and --fix-wu placeholders.
 *
 * @param options - Configuration options
 * @param options.wuId - The WU ID being completed
 * @param options.mainCheckout - Path to main checkout
 * @returns Formatted command string ready to copy-paste
 */
export function formatSkipGatesCommand(options) {
    const { wuId, mainCheckout } = options;
    return `cd ${mainCheckout} && pnpm wu:done --id ${wuId} --skip-gates --reason "pre-existing on main" --fix-wu WU-XXXX`;
}
function formatBootstrapTrapSkipGatesCommand(options) {
    const { wuId, mainCheckout } = options;
    return (`cd ${mainCheckout} && pnpm wu:done --id ${wuId} --skip-gates ` +
        `--reason "${BOOTSTRAP_TRAP_PACKAGE_EXPORT_SKIP_REASON}" ` +
        `--fix-wu ${BOOTSTRAP_TRAP_FIX_WU_PLACEHOLDER}`);
}
export async function diagnoseBootstrapTrapBuild(options) {
    const execOnCwd = options.execOnCwd ?? defaultExecFileOnCwd(options.cwd);
    const result = await execOnCwd('pnpm', ['build']);
    const combinedOutput = [result.stdout, result.stderr].filter(Boolean).join('\n');
    return {
        buildPassed: result.exitCode === EXIT_CODES.SUCCESS,
        trap: detectBootstrapTrapSignature(combinedOutput),
    };
}
export function formatBootstrapTrapFailure(options) {
    const { wuId, mainCheckout, buildPassed, diagnosis } = options;
    const header = `${EMOJI.WARNING} Known bootstrap-trap detected for ${wuId}.\n\n` +
        `${diagnosis.summary}\n` +
        `Matched signature: ${diagnosis.matchedSignature}\n\n`;
    if (diagnosis.kind === 'package-export' && buildPassed) {
        return (header +
            `Safe exit when the worktree build is already green and the move is otherwise complete:\n` +
            `  1. Use the green worktree build as the authoritative verification.\n` +
            `  2. Finish from main with:\n` +
            `     ${formatBootstrapTrapSkipGatesCommand({ wuId, mainCheckout })}\n` +
            `  3. Replace ${BOOTSTRAP_TRAP_FIX_WU_PLACEHOLDER} with the WU that lands the export-surface follow-up.\n\n` +
            `If the worktree build is not green, fix the package exports and rerun:\n` +
            `  pnpm wu:prep --id ${wuId}`);
    }
    if (diagnosis.kind === 'package-export') {
        return (header +
            `Fix the package exports / cross-module surface first, then rerun:\n` +
            `  1. Repair the moved-file exports or consumer imports.\n` +
            `  2. Verify with: pnpm build\n` +
            `  3. Retry: pnpm wu:prep --id ${wuId}`);
    }
    return (header +
        `Fix the dependency cycle first, then rerun:\n` +
        `  1. Remove or relocate the dependency edge that closes the cycle.\n` +
        `  2. Verify with: pnpm build\n` +
        `  3. Retry: pnpm wu:prep --id ${wuId}`);
}
function findBootstrapTrapMatch(output, patterns) {
    for (const pattern of patterns) {
        const match = output.match(pattern);
        if (match?.[0]) {
            return match[0];
        }
    }
    return null;
}
export function detectBootstrapTrapSignature(output) {
    const normalizedOutput = output.trim();
    if (normalizedOutput.length === 0) {
        return null;
    }
    const packageExportMatch = findBootstrapTrapMatch(normalizedOutput, BOOTSTRAP_TRAP_PACKAGE_EXPORT_PATTERNS);
    if (packageExportMatch) {
        return {
            kind: 'package-export',
            matchedSignature: packageExportMatch,
            summary: 'Package export / cross-module resolution failed against the main checkout.',
        };
    }
    const dependencyCycleMatch = findBootstrapTrapMatch(normalizedOutput, BOOTSTRAP_TRAP_DEPENDENCY_CYCLE_PATTERNS);
    if (dependencyCycleMatch) {
        return {
            kind: 'dependency-cycle',
            matchedSignature: dependencyCycleMatch,
            summary: 'A dependency cycle is blocking package bootstrap/build.',
        };
    }
    return null;
}
/**
 * WU-1344: Default implementation of execOnMain using spawnSync.
 * Uses spawnSync with pnpm executable for safety (no shell injection risk).
 */
function defaultExecOnMain(mainCheckout) {
    return async (cmd) => {
        // WU-1441: Compare main vs worktree using the *current* CLI build, even if
        // the main checkout doesn't yet support `wu:validate --json`.
        //
        // When wu:prep is running from a worktree, running `pnpm wu:validate --json`
        // inside the main checkout will execute the older CLI (and fail to parse).
        // Instead, execute the sibling dist entrypoint directly and vary only `cwd`.
        const distDir = path.dirname(fileURLToPath(import.meta.url));
        const wuValidateDist = path.join(distDir, 'wu-validate.js');
        const shouldUseDistWuValidate = cmd.includes('wu:validate') && cmd.includes('--json') && existsSync(wuValidateDist);
        if (shouldUseDistWuValidate) {
            // eslint-disable-next-line sonarjs/no-os-command-from-path -- node resolved from PATH; running dist script
            const result = spawnSync('node', [wuValidateDist, '--all', '--json'], {
                cwd: mainCheckout,
                encoding: 'utf-8',
                stdio: ['pipe', 'pipe', 'pipe'],
            });
            return {
                exitCode: result.status ?? 1,
                stdout: result.stdout ?? '',
                stderr: result.stderr ?? '',
            };
        }
        // Parse command to extract pnpm script name and args
        // Expected format: "pnpm spec:linter" or similar
        const parts = cmd.split(/\s+/);
        const executable = parts[0];
        const args = parts.slice(1);
        const result = spawnSync(executable, args, {
            cwd: mainCheckout,
            encoding: 'utf-8',
            stdio: ['pipe', 'pipe', 'pipe'],
        });
        return {
            exitCode: result.status ?? 1,
            stdout: result.stdout ?? '',
            stderr: result.stderr ?? '',
        };
    };
}
function defaultExecFileOnCwd(cwd, timeoutMs) {
    return async (command, args) => {
        const result = spawnSync(command, args, {
            cwd,
            encoding: 'utf-8',
            stdio: ['pipe', 'pipe', 'pipe'],
            ...(timeoutMs ? { timeout: timeoutMs } : {}),
        });
        const timeoutMessage = result.error && 'code' in result.error && result.error.code === 'ETIMEDOUT' && timeoutMs
            ? `Command timed out after ${timeoutMs}ms: ${command} ${args.join(' ')}`
            : '';
        return {
            exitCode: result.status ?? 1,
            stdout: result.stdout ?? '',
            stderr: [result.stderr ?? '', timeoutMessage].filter(Boolean).join('\n'),
        };
    };
}
function runScopedValidation({ wuId, cwd }) {
    // eslint-disable-next-line sonarjs/no-os-command-from-path -- pnpm resolved from PATH; CLI orchestration
    const result = spawnSync('pnpm', ['wu:validate', '--id', wuId], {
        cwd,
        encoding: 'utf-8',
        stdio: 'inherit',
    });
    return (result.status ?? 1) === EXIT_CODES.SUCCESS;
}
function parseSpecLinterReport(output) {
    try {
        const parsed = JSON.parse(output.trim());
        return parsed;
    }
    catch {
        return null;
    }
}
export function classifySpecLinterFailures(options) {
    const mainSet = new Set(options.mainInvalid);
    const worktreeSet = new Set(options.worktreeInvalid);
    const preExistingFailures = [...worktreeSet].filter((id) => mainSet.has(id));
    const newFailures = [...worktreeSet].filter((id) => !mainSet.has(id));
    return {
        hasPreExisting: preExistingFailures.length > 0,
        hasNewFailures: newFailures.length > 0,
        newFailures,
        preExistingFailures,
    };
}
export function buildScopedUnitTestArgs(scopedUnitTests) {
    return ['vitest', 'run', ...scopedUnitTests, '--passWithNoTests', '--maxWorkers=1'];
}
export async function checkPreExistingScopedUnitTestFailures(options) {
    const { mainCheckout, worktreeCwd = process.cwd(), scopedUnitTests, execOnMain = defaultExecFileOnCwd(mainCheckout, SCOPED_UNIT_TEST_COMPARISON_TIMEOUT_MS), execOnWorktree = defaultExecFileOnCwd(worktreeCwd, SCOPED_UNIT_TEST_COMPARISON_TIMEOUT_MS), } = options;
    if (scopedUnitTests.length === 0) {
        return {
            hasPreExisting: false,
            hasNewFailures: false,
            checkedPaths: [],
        };
    }
    const worktreePlan = resolveScopedUnitTestExecutionPlan({
        projectRoot: worktreeCwd,
        scopedUnitTests,
    });
    const mainPlan = resolveScopedUnitTestExecutionPlan({
        projectRoot: mainCheckout,
        scopedUnitTests,
    });
    const fallbackPlan = worktreePlan?.mode === 'default-flow'
        ? worktreePlan
        : mainPlan?.mode === 'default-flow'
            ? mainPlan
            : null;
    if (fallbackPlan) {
        return {
            hasPreExisting: false,
            hasNewFailures: false,
            checkedPaths: scopedUnitTests,
            skippedReason: formatScopedUnitTestFallbackMessage(fallbackPlan),
        };
    }
    const testArgs = worktreePlan?.mode === 'direct' ? worktreePlan.args : buildScopedUnitTestArgs(scopedUnitTests);
    try {
        const worktreeResult = await execOnWorktree('pnpm', testArgs);
        const mainArgs = mainPlan?.mode === 'direct' ? mainPlan.args : testArgs;
        const mainResult = await execOnMain('pnpm', mainArgs);
        const bootstrapTrap = detectBootstrapTrapSignature([worktreeResult.stdout, worktreeResult.stderr, mainResult.stdout, mainResult.stderr]
            .filter(Boolean)
            .join('\n'));
        if (bootstrapTrap) {
            return {
                hasPreExisting: false,
                hasNewFailures: false,
                checkedPaths: scopedUnitTests,
                error: `${bootstrapTrap.summary} Matched signature: ${bootstrapTrap.matchedSignature}`,
                bootstrapTrap,
            };
        }
        const worktreeFailed = worktreeResult.exitCode !== EXIT_CODES.SUCCESS;
        const mainFailed = mainResult.exitCode !== EXIT_CODES.SUCCESS;
        return {
            hasPreExisting: worktreeFailed && mainFailed,
            hasNewFailures: worktreeFailed && !mainFailed,
            checkedPaths: scopedUnitTests,
        };
    }
    catch (error) {
        return {
            hasPreExisting: false,
            hasNewFailures: false,
            checkedPaths: scopedUnitTests,
            error: error instanceof Error ? error.message : String(error),
        };
    }
}
export function shouldOfferSkipGatesGuidance(checks) {
    const relevantChecks = checks.filter((check) => check.hasPreExisting || check.hasNewFailures);
    return (relevantChecks.length > 0 &&
        relevantChecks.every((check) => check.hasPreExisting && !check.hasNewFailures));
}
async function runSpecLinterJson(execOnCwd) {
    const result = await execOnCwd('pnpm --silent wu:validate --all --json');
    const report = parseSpecLinterReport(result.stdout);
    const bootstrapTrap = detectBootstrapTrapSignature([result.stdout, result.stderr].filter(Boolean).join('\n'));
    if (!report || !Array.isArray(report.invalid)) {
        return {
            invalidIds: [],
            error: bootstrapTrap
                ? `${bootstrapTrap.summary} Matched signature: ${bootstrapTrap.matchedSignature}`
                : 'Failed to parse wu:validate JSON output.',
            bootstrapTrap: bootstrapTrap ?? undefined,
        };
    }
    const invalidIds = report.invalid
        .map((item) => item?.wuId)
        .filter((id) => typeof id === 'string');
    return { invalidIds };
}
/**
 * WU-1344: Check if spec:linter failures are pre-existing on main branch.
 *
 * Runs spec:linter on the main checkout to determine if the failures
 * already exist there (i.e., not caused by the current WU).
 *
 * @param options - Configuration options
 * @param options.mainCheckout - Path to main checkout
 * @param options.execOnMain - Optional function to execute commands on main (for testing)
 * @returns Result indicating whether failures are pre-existing
 */
export async function checkPreExistingFailures(options) {
    const { mainCheckout, execOnMain = defaultExecOnMain(mainCheckout), execOnWorktree = defaultExecOnMain(process.cwd()), } = options;
    try {
        const worktreeResult = await runSpecLinterJson(execOnWorktree);
        const mainResult = await runSpecLinterJson(execOnMain);
        if (worktreeResult.error || mainResult.error) {
            return {
                hasPreExisting: false,
                hasNewFailures: false,
                newFailures: [],
                preExistingFailures: [],
                error: worktreeResult.error || mainResult.error,
                bootstrapTrap: worktreeResult.bootstrapTrap || mainResult.bootstrapTrap,
            };
        }
        const classification = classifySpecLinterFailures({
            mainInvalid: mainResult.invalidIds,
            worktreeInvalid: worktreeResult.invalidIds,
        });
        return {
            ...classification,
            error: undefined,
        };
    }
    catch (error) {
        // If we can't check main, assume failures are NOT pre-existing
        // (safer to require fixing rather than skipping)
        return {
            hasPreExisting: false,
            hasNewFailures: false,
            newFailures: [],
            preExistingFailures: [],
            error: error.message,
        };
    }
}
/**
 * WU-1493: Check if a WU doc uses branch-pr claimed mode.
 *
 * @param doc - Partial WU YAML document (needs claimed_mode field)
 * @returns true if claimed_mode is 'branch-pr'
 */
export function isBranchPrMode(doc) {
    return doc.claimed_mode === CLAIMED_MODES.BRANCH_PR;
}
/**
 * WU-1493: Validate that the current git branch matches the expected lane branch
 * for branch-pr mode.
 *
 * @param options - Branch comparison options
 * @param options.currentBranch - The currently checked-out branch
 * @param options.expectedBranch - The expected lane branch (e.g., lane/framework-cli/wu-1493)
 * @returns Validation result with valid flag and optional error message
 */
export function validateBranchPrBranch(options) {
    const { currentBranch, expectedBranch } = options;
    if (currentBranch === expectedBranch) {
        return { valid: true };
    }
    return {
        valid: false,
        error: `Current branch '${currentBranch}' does not match expected lane branch '${expectedBranch}'.\n\n` +
            `Switch to the lane branch first:\n` +
            `  git checkout ${expectedBranch}`,
    };
}
/**
 * WU-1493: Format success message for branch-pr mode.
 * Shows the wu:done handoff for branch-pr completion.
 *
 * @param options - Message options
 * @param options.wuId - The WU ID
 * @param options.laneBranch - The lane branch name
 * @returns Formatted success message string
 */
export function formatBranchPrSuccessMessage(options) {
    const { wuId, laneBranch } = options;
    return (`${PREP_PREFIX} ${EMOJI.SUCCESS} ${wuId}: Prep completed successfully!\n` +
        `\n` +
        `${PREP_PREFIX} Gates passed on branch ${laneBranch}.\n` +
        `\n` +
        `${PREP_PREFIX} Next steps for branch-pr mode:\n` +
        `\n` +
        `  1. Run completion:   pnpm wu:done --id ${wuId}\n` +
        `     wu:done will push ${laneBranch} and create the PR.\n` +
        `  2. After PR merge:   pnpm wu:cleanup --id ${wuId}\n`);
}
export function evaluatePrepMainMutationGuard(options) {
    return evaluateMainDirtyMutationGuard({
        commandName: 'wu:prep',
        mainCheckout: options.mainCheckout,
        mainStatus: options.mainStatus,
        hasActiveWorktreeContext: !options.isBranchPr,
        isBranchPrMode: options.isBranchPr,
    });
}
/**
 * Print success message with copy-paste instruction.
 */
function printSuccessMessage(wuId, mainCheckout) {
    console.log('');
    console.log(`${PREP_PREFIX} ${EMOJI.SUCCESS} ${wuId}: Prep completed successfully!`);
    console.log('');
    console.log(`${PREP_PREFIX} Gates passed in worktree.`);
    console.log('');
    console.log(`${PREP_PREFIX} Next step - copy and paste this command:`);
    console.log('');
    console.log(`    cd ${mainCheckout} && pnpm wu:done --id ${wuId}`);
    console.log('');
}
/**
 * Main entry point.
 */
/**
 * WU-2102: Create a gates-passed checkpoint so wu:done can skip gates.
 *
 * Called after wu:prep gates succeed. wu:done's canSkipGates() reads this
 * checkpoint and skips its own gate run when valid.
 */
export async function createPrepCheckpoint(params) {
    await createPreGatesCheckpoint({
        wuId: params.wuId,
        worktreePath: params.worktreePath,
        branchName: params.branchName,
        gatesPassed: true,
    }, {});
}
export async function main() {
    // Parse arguments
    const args = createWUParser({
        name: 'wu-prep',
        description: 'Prepare WU for completion (run gates in worktree; wu:done reuses the same tests.unit scope)',
        options: [
            WU_OPTIONS.id,
            PREP_OPTIONS.docsOnly,
            PREP_OPTIONS.fullTests,
            PREP_OPTIONS.force,
            PREP_OPTIONS.reason,
        ],
        required: ['id'],
        allowPositionalId: true,
    });
    const id = args.id.toUpperCase();
    if (!PATTERNS.WU_ID.test(id)) {
        die(`Invalid WU id '${args.id}'. Expected format WU-123`);
    }
    // Detect location
    const location = await resolveLocation();
    // WU-1493: Read WU YAML early to check claimed_mode BEFORE location rejection.
    // This allows branch-pr WUs to run from main checkout on the lane branch.
    const wuPath = WU_PATHS.WU(id);
    let doc;
    try {
        doc = readWU(wuPath, id);
    }
    catch (error) {
        die(`Failed to read WU ${id}: ${error.message}\n\n` +
            `Options:\n` +
            `  1. Check if WU file exists: ls -la ${wuPath}\n` +
            `  2. Validate YAML syntax: pnpm wu:validate --id ${id}`);
    }
    const branchPr = isBranchPrMode(doc);
    // WU-1223 + WU-1493: Location validation
    // For branch-pr mode: allow running from main checkout on the correct lane branch
    // For worktree mode: must be in a worktree (original behavior)
    if (branchPr) {
        // WU-1493: branch-pr mode - validate we're on the correct lane branch
        const expectedBranch = defaultBranchFrom(doc);
        const currentBranch = getCurrentBranch();
        if (!expectedBranch) {
            die(`${EMOJI.FAILURE} Cannot determine lane branch for ${id}.\n\n` +
                `Check that the WU has a valid lane and id in its YAML spec.`);
        }
        if (!currentBranch) {
            die(`${EMOJI.FAILURE} Cannot determine current git branch.\n\n` +
                `Ensure you are in a git repository.`);
        }
        const branchCheck = validateBranchPrBranch({
            currentBranch,
            expectedBranch,
        });
        if (!branchCheck.valid) {
            die(`${EMOJI.FAILURE} ${branchCheck.error}`);
        }
        console.log(`${PREP_PREFIX} branch-pr mode detected for ${id}`);
    }
    else {
        // Original worktree-only behavior
        if (location.type !== LOCATION_TYPES.WORKTREE) {
            die(`${EMOJI.FAILURE} wu:prep must be run from a worktree, not ${location.type}.\n\n` +
                `Current location: ${location.cwd}\n\n` +
                `If you have a worktree for ${id}, navigate to it first:\n` +
                `  cd worktrees/<lane>-${id.toLowerCase()}\n\n` +
                `If you don't have a worktree yet, claim the WU first:\n` +
                `  pnpm wu:claim --id ${id} --lane "<lane>"`);
        }
        // Verify the worktree is for the correct WU
        if (location.worktreeWuId && location.worktreeWuId !== id) {
            console.warn(`${PREP_PREFIX} ${EMOJI.WARNING} Worktree is for ${location.worktreeWuId}, but you specified ${id}.`);
            console.warn(`${PREP_PREFIX} Proceeding with ${id} as specified.`);
        }
    }
    // Validate WU status is in_progress
    if (doc.status !== WU_STATUS.IN_PROGRESS) {
        die(`${EMOJI.FAILURE} WU ${id} status is '${doc.status}', expected '${WU_STATUS.IN_PROGRESS}'.\n\n` +
            `wu:prep can only be run on WUs that are in progress.`);
    }
    await enforceWuBriefEvidenceForPrep(id, doc, {
        baseDir: location.cwd,
        mode: resolveWuBriefPolicyMode(),
        freshnessMinutes: resolveWuBriefFreshnessMinutes(),
        force: Boolean(args.force),
        reason: typeof args.reason === 'string' ? args.reason : undefined,
    });
    if (!branchPr) {
        const mainStatus = await createGitForPath(location.mainCheckout).getStatus();
        const mainMutationGuard = evaluatePrepMainMutationGuard({
            mainCheckout: location.mainCheckout,
            isBranchPr: branchPr,
            mainStatus,
        });
        if (mainMutationGuard.blocked) {
            die(mainMutationGuard.message ?? `${EMOJI.FAILURE} wu:prep blocked by dirty-main guard.`);
        }
    }
    console.log(`${PREP_PREFIX} Preparing ${id} for completion...`);
    console.log(`${PREP_PREFIX} Location: ${location.cwd}`);
    console.log(`${PREP_PREFIX} Main checkout: ${location.mainCheckout}`);
    console.log('');
    console.log(`${PREP_PREFIX} Running scoped validation for ${id}...`);
    const scopedOk = runScopedValidation({ wuId: id, cwd: location.cwd });
    if (!scopedOk) {
        die(`${EMOJI.FAILURE} Scoped validation failed for ${id}.\n\n` +
            `Fix the WU spec issues and rerun wu:prep.`);
    }
    console.log(`${PREP_PREFIX} Running reality preflight checks...`);
    const realityPreflight = await validatePreflight(id, {
        rootDir: location.cwd,
        worktreePath: location.cwd,
        phase: 'reality',
    });
    if (!realityPreflight.valid) {
        die(formatPreflightResult(id, realityPreflight));
    }
    if (Array.isArray(realityPreflight.warnings) && realityPreflight.warnings.length > 0) {
        const warningLines = formatPreflightWarnings(realityPreflight.warnings, `${PREP_PREFIX} ${EMOJI.WARNING} Reality preflight warnings:`);
        for (const line of warningLines) {
            console.log(line.startsWith('  - ') ? `${PREP_PREFIX} ${line}` : line);
        }
    }
    const tddEvidenceResult = await evaluateTddDiffEvidenceForPrep({
        wuId: id,
        doc,
        cwd: location.cwd,
    });
    if (!tddEvidenceResult.valid) {
        die(formatTddDiffEvidenceFailure({ wuId: id, result: tddEvidenceResult }));
    }
    // WU-2650: Commit-order (RED-first) gate. Runs BEFORE gates so ordering
    // violations surface independently of lint/test failures.
    const commitOrderResult = await evaluateCommitOrderForPrep({
        wuId: id,
        doc,
        cwd: location.cwd,
    });
    if (!commitOrderResult.valid) {
        die(formatTddOrderingFailure({ wuId: id, wuType: doc.type, result: commitOrderResult }));
    }
    const scopedUnitTests = resolveScopedUnitTestsForPrep({
        fullTests: args.fullTests,
        tests: doc.tests,
    });
    // WU-1344: Check for pre-existing spec:linter failures on main BEFORE running gates.
    // We do this first because runGates() calls die() on failure, which exits the process
    // before we can check. By checking first, we can set up an exit handler to show
    // the skip-gates command if gates fail.
    console.log(`${PREP_PREFIX} Checking for pre-existing spec:linter failures on main...`);
    const preExistingCheck = await checkPreExistingFailures({
        mainCheckout: location.mainCheckout,
    });
    if (preExistingCheck.error) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Unable to compare spec:linter results: ${preExistingCheck.error}`);
    }
    else if (preExistingCheck.hasPreExisting && !preExistingCheck.hasNewFailures) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Pre-existing failures detected on main.`);
    }
    else if (preExistingCheck.hasNewFailures) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} New spec:linter failures detected in worktree: ${preExistingCheck.newFailures.join(', ')}`);
    }
    else {
        console.log(`${PREP_PREFIX} No pre-existing failures on main.`);
    }
    let scopedUnitTestCheck = {
        hasPreExisting: false,
        hasNewFailures: false,
        checkedPaths: [],
    };
    if (!args.fullTests && scopedUnitTests.length > 0) {
        console.log(`${PREP_PREFIX} Checking for pre-existing scoped test failures on main...`);
        scopedUnitTestCheck = await checkPreExistingScopedUnitTestFailures({
            mainCheckout: location.mainCheckout,
            worktreeCwd: location.cwd,
            scopedUnitTests,
        });
    }
    if (scopedUnitTestCheck.skippedReason) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} ${scopedUnitTestCheck.skippedReason}`);
    }
    else if (scopedUnitTestCheck.error) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Unable to compare scoped test results: ${scopedUnitTestCheck.error}`);
    }
    else if (scopedUnitTestCheck.hasPreExisting && !scopedUnitTestCheck.hasNewFailures) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Declared scoped test failures are also present on main.`);
    }
    else if (scopedUnitTestCheck.hasNewFailures) {
        console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Scoped tests fail in this worktree but pass on main.`);
    }
    const hasPreExistingOnly = shouldOfferSkipGatesGuidance([preExistingCheck, scopedUnitTestCheck]);
    const bootstrapTrapHint = preExistingCheck.bootstrapTrap ?? scopedUnitTestCheck.bootstrapTrap ?? null;
    if (hasPreExistingOnly && !bootstrapTrapHint) {
        // Set up an exit handler to print the skip-gates command when gates fail
        // This runs before process.exit() fully terminates the process
        process.on('exit', (code) => {
            if (code !== EXIT_CODES.SUCCESS) {
                console.log('');
                console.log(`${PREP_PREFIX} ${EMOJI.WARNING} Since failures are pre-existing on main, you can skip gates:`);
                console.log('');
                console.log(`    ${formatSkipGatesCommand({ wuId: id, mainCheckout: location.mainCheckout })}`);
                console.log('');
                console.log(`${PREP_PREFIX} Replace WU-XXXX with the WU that will fix these pre-existing failures.`);
                console.log('');
            }
        });
    }
    console.log('');
    if (args.fullTests) {
        console.log(`${PREP_PREFIX} --full-tests enabled; using default incremental/full test gate flow.`);
    }
    else if (scopedUnitTests.length > 0) {
        console.log(`${PREP_PREFIX} Using scoped tests from tests.unit (${scopedUnitTests.length} path${scopedUnitTests.length === 1 ? '' : 's'}).`);
    }
    // Run gates
    console.log(`${PREP_PREFIX} Running gates...`);
    const gatesResult = await runGates(buildRunGatesOptionsForPrep({
        cwd: location.cwd,
        docsOnly: args.docsOnly,
        fullTests: args.fullTests,
        scopedTestPaths: scopedUnitTests,
    }));
    if (!gatesResult) {
        const buildDiagnostic = await diagnoseBootstrapTrapBuild({ cwd: location.cwd });
        const bootstrapTrapDiagnosis = buildDiagnostic.trap ||
            (bootstrapTrapHint?.kind === 'package-export' && buildDiagnostic.buildPassed
                ? bootstrapTrapHint
                : null);
        if (bootstrapTrapDiagnosis) {
            die(formatBootstrapTrapFailure({
                wuId: id,
                mainCheckout: location.mainCheckout,
                buildPassed: buildDiagnostic.buildPassed,
                diagnosis: bootstrapTrapDiagnosis,
            }));
        }
        // Gates failed - if pre-existing check was already done and showed failures,
        // the exit handler above will print the skip-gates command.
        // Otherwise, tell the user to fix the failures.
        if (!hasPreExistingOnly) {
            die(`${EMOJI.FAILURE} Gates failed.\n\n` + `Fix the gate failures and run wu:prep again.`);
        }
        // Pre-existing failures - exit with error, handler will print skip-gates command
        process.exit(EXIT_CODES.ERROR);
    }
    // WU-2102: Write gates-passed checkpoint so wu:done can skip gates
    const branchName = defaultBranchFrom(doc) ?? getCurrentBranch() ?? '';
    await createPrepCheckpoint({ wuId: id, worktreePath: location.cwd, branchName });
    // WU-1493: Success - print mode-appropriate message
    if (branchPr) {
        const laneBranch = defaultBranchFrom(doc) ?? '';
        const message = formatBranchPrSuccessMessage({ wuId: id, laneBranch });
        console.log('');
        console.log(message);
    }
    else {
        printSuccessMessage(id, location.mainCheckout);
    }
}
// WU-1181: Use import.meta.main instead of process.argv[1] comparison
// The old pattern fails with pnpm symlinks because process.argv[1] is the symlink
// path but import.meta.url resolves to the real path - they never match
import { runCLI } from './cli-entry-point.js';
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=wu-prep.js.map