#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * WU Claim Helper
 *
 * Canonical sequence:
 * 1) Auto-update backlog/status/WU YAML (safe parsing) unless `--no-auto`
 * 2) Commit and push to `main`
 * 3) Create a dedicated worktree+branch for the WU
 *
 * Usage:
 *   node tools/wu-claim.ts --id WU-334 --lane Intelligence \
 *     [--worktree worktrees/intelligence-wu-334] [--branch lane/intelligence/wu-334]
 *
 * WU-2542: This script imports utilities from @lumenflow/core package.
 * Full migration to thin shim pending @lumenflow/core CLI export implementation.
 *
 * WU-1649: Decomposed into focused modules:
 *   - wu-claim-validation.ts: Pre-flight validation, schema, lane/spec checks
 *   - wu-claim-state.ts: State update helpers (WU YAML, backlog, status)
 *   - wu-claim-worktree.ts: Worktree mode claim workflow
 *   - wu-claim-branch.ts: Branch-only mode claim workflow
 *   - wu-claim-output.ts: Output formatting and display helpers
 *   - wu-claim-resume-handler.ts: Resume/handoff mode handler
 *   - wu-claim-mode.ts: Mode resolution (pre-existing)
 *   - wu-claim-cloud.ts: Cloud claim helpers (pre-existing)
 */
// WU-2542: Import from @lumenflow/core to establish shim layer dependency
// eslint-disable-next-line sonarjs/unused-import -- Validates @lumenflow/core package link
import { VERSION as _LUMENFLOW_VERSION } from '@lumenflow/core';
import { resolveIntegrationTarget } from '@lumenflow/core/integration-target';
import { rmSync } from 'node:fs';
import path from 'node:path';
import { isOrphanWorktree } from '@lumenflow/core/orphan-detector';
import { getGitForCwd } from '@lumenflow/core/git-adapter';
import { die, getErrorMessage } from '@lumenflow/core/error-handler';
import { createWUParser, WU_OPTIONS } from '@lumenflow/core/arg-parser';
// WU-1491: Mode resolution for --cloud and flag combinations
import { resolveClaimMode } from './wu-claim-mode.js';
// WU-1590: Cloud claim helpers for branch-pr/cloud execution behavior
// WU-1766: shouldSkipEnsureOnMainForClaim added to bypass ensureOnMain in cloud mode
import { shouldSkipBranchExistsCheck, resolveBranchClaimExecution, shouldSkipEnsureOnMainForClaim, } from './wu-claim-cloud.js';
// WU-1495: Cloud auto-detection from config-driven env signals
import { detectCloudMode, resolveEffectiveCloudActivation, CLOUD_ACTIVATION_SOURCE, } from '@lumenflow/core/cloud-detect';
import { WU_PATHS } from '@lumenflow/core/wu-paths';
import { CLAIMED_MODES, PATTERNS, toKebab, LOG_PREFIX, EMOJI, ENV_VARS, EXIT_CODES, } from '@lumenflow/core/wu-constants';
import { isInGitWorktree, shouldSkipRemoteOperations, withMicroWorktree, } from '@lumenflow/core/micro-worktree';
import { ensureOnMain } from '@lumenflow/core/wu-helpers';
import { emitWUFlowEvent } from '@lumenflow/core/telemetry';
import { startSessionForWU } from '@lumenflow/agent/auto-session';
import { resolveDefaultRoleContract } from './delegation-role-resolver.js';
import { getConfig } from '@lumenflow/core/config';
import { acquireLaneLock, releaseLaneLock, checkLaneLock, forceRemoveStaleLock, } from '@lumenflow/core/lane-lock';
import { getAssignedEmail } from '@lumenflow/core/wu-claim-helpers';
// WU-1649: Import from extracted modules
import { runPreflightValidations, handleCodePathOverlap, validateBranchOnlyMode, } from './wu-claim-validation.js';
import { readWUTitle, getStagedChanges, ensureCleanOrClaimOnlyWhenNoAuto, applyCanonicalClaimUpdate, rollbackCanonicalClaim, recordClaimPickupEvidence, shouldApplyCanonicalClaimUpdate as shouldApplyCanonicalClaimUpdateFn, } from './wu-claim-state.js';
import { claimWorktreeMode } from './wu-claim-worktree.js';
import { claimBranchOnlyMode } from './wu-claim-branch.js';
import { handleResumeMode } from './wu-claim-resume-handler.js';
import { extractSandboxCommandFromArgv, runWuSandbox } from './wu-sandbox.js';
import { recordWuBriefEvidence } from './wu-spawn-strategy-resolver.js';
// WU-2654: narrow conductor emissions for lane-lock + delegation-pickup.
import { buildDelegationPickedUpEvent, buildLockAcquiredEvent, readOrgIdFromConfig, resolveConductorEventSink, } from './kernel-event-sync/lifecycle-emitters.js';
// WU-2668: Queue wu:claim attempts that auto-fire when blockers merge.
import { appendQueueEvent, cancelActiveEntry, findActiveEntry, waitForBlockersResolved, } from './claim-queue.js';
import { detectConflicts } from '@lumenflow/core/code-paths-overlap';
import { readFileSync, existsSync } from 'node:fs';
import { parseYAML } from '@lumenflow/core/wu-yaml';
import { resolveClaimQueueRelativePath } from './state-path-resolvers.js';
// ============================================================================
// RE-EXPORTS: Preserve public API for existing test consumers
// ============================================================================
// From wu-claim-validation.ts
export { resolveClaimStatus, validateManualTestsForClaim } from './wu-claim-validation.js';
// From wu-claim-state.ts
export { shouldApplyCanonicalClaimUpdate, shouldPersistClaimMetadataOnBranch, resolveClaimBaselineRef, buildRollbackYamlDoc, hasClaimPickupEvidence, recordClaimPickupEvidence, getWorktreeCommitFiles, } from './wu-claim-state.js';
// From wu-claim-output.ts
export { formatProjectDefaults, printProjectDefaults, printLifecycleNudge, } from './wu-claim-output.js';
// From wu-claim-worktree.ts
export { applyFallbackSymlinks } from './wu-claim-worktree.js';
// ============================================================================
// Cloud activation (kept in orchestrator since it's only used in main())
// ============================================================================
const PREFIX = LOG_PREFIX.CLAIM;
const DEFAULT_WU_BRIEF_POLICY_MODE = 'auto';
const WU_BRIEF_POLICY_MODES = ['off', 'manual', 'auto', 'required'];
const AUTO_BRIEF_CLIENT_NAME = 'wu:claim:auto';
const WU_CLAIM_SANDBOX_OPTION = {
    name: 'sandbox',
    flags: '--sandbox',
    description: 'Launch a post-claim session via wu:sandbox (use -- <command> to override the default shell)',
    type: 'boolean',
};
function isWuClaimBriefPolicyMode(value) {
    return (typeof value === 'string' &&
        WU_BRIEF_POLICY_MODES.includes(value));
}
export function resolveWuClaimBriefPolicyMode(config = getConfig()) {
    const configuredMode = config.wu?.brief?.policyMode;
    if (isWuClaimBriefPolicyMode(configuredMode)) {
        return configuredMode;
    }
    return DEFAULT_WU_BRIEF_POLICY_MODE;
}
export async function maybeRunAutoBriefForClaim(options, deps = {}) {
    const mode = options.policyMode ?? DEFAULT_WU_BRIEF_POLICY_MODE;
    const logger = deps.logger || console;
    if (mode === 'off' || mode === 'manual') {
        logger.log(`${PREFIX} wu:brief auto-run skipped (policy=${mode}).`);
        return { attempted: false, mode };
    }
    const recordEvidence = deps.recordEvidence || recordWuBriefEvidence;
    try {
        await recordEvidence({
            wuId: options.wuId,
            workspaceRoot: options.workspaceRoot,
            clientName: options.clientName || AUTO_BRIEF_CLIENT_NAME,
            evidenceMode: 'claim-auto',
            claimedMode: options.claimedMode,
            claimedBranch: options.claimedBranch,
        });
        logger.log(`${PREFIX} ${EMOJI.WARNING} claim-auto stub recorded (policy=${mode}). ` +
            `You MUST still run \`pnpm wu:brief --client <client>\` — ` +
            `claim-auto does not satisfy wu:done and does not generate the handoff prompt.`);
        return { attempted: true, mode };
    }
    catch (error) {
        const message = getErrorMessage(error);
        if (mode === 'required') {
            throw new Error(`wu:brief auto-run failed (policy=required): ${message}`, {
                cause: error,
            });
        }
        logger.warn(`${PREFIX} Warning: wu:brief auto-run failed (policy=auto): ${message}`);
        return { attempted: true, mode };
    }
}
async function maybeStartControlPlaneSidecar() {
    try {
        const { spawnControlPlaneSidecar } = await import('./sidecar-manager.js');
        const result = await spawnControlPlaneSidecar({
            workspaceRoot: process.cwd(),
            logger: {
                warn: (message) => console.warn(`${PREFIX} ${message}`),
            },
        });
        if (result.spawned) {
            console.log(`${PREFIX} ${EMOJI.SUCCESS} Control-plane sync sidecar started (pid=${result.pid})`);
        }
    }
    catch (error) {
        console.warn(`${PREFIX} Warning: could not start control-plane sync sidecar: ${getErrorMessage(error)}`);
    }
}
async function flushClaimLifecycleSyncEvent(input) {
    const [{ flushWuLifecycleSync }, { WU_LIFECYCLE_COMMANDS }] = await Promise.all([
        import('./wu-lifecycle-sync/service.js'),
        import('./wu-lifecycle-sync/constants.js'),
    ]);
    await flushWuLifecycleSync({
        command: WU_LIFECYCLE_COMMANDS.CLAIM,
        wuId: input.wuId,
        by: input.by,
        sessionId: input.sessionId,
    }, {
        workspaceRoot: process.cwd(),
        logger: {
            warn: (message) => console.warn(`${PREFIX} ${message}`),
        },
    });
}
function normalizePathForYamlStorage(rawPath) {
    return rawPath.replaceAll('\\', '/').replace(/^\.\/+/, '');
}
/**
 * WU-2247: Store worktree_path as repo-relative path in version-controlled YAML.
 *
 * Converts absolute user-home paths (e.g. <home>/.../worktrees/lane-wu-1234) to
 * repo-relative paths (e.g. worktrees/lane-wu-1234) for portability and privacy.
 */
export function toRelativeWorktreePathForStorage(worktreePath, repoRoot = process.cwd()) {
    const trimmed = worktreePath.trim();
    if (!trimmed)
        return trimmed;
    const candidate = path.isAbsolute(trimmed) ? path.relative(repoRoot, trimmed) : trimmed;
    const normalized = normalizePathForYamlStorage(candidate);
    return normalized.length > 0 ? normalized : '.';
}
/**
 * Resolve branch-aware cloud activation for wu:claim.
 *
 * This preserves source attribution from detectCloudMode while enforcing
 * protected-branch behavior for explicit vs env-signal activation.
 */
export function resolveCloudActivationForClaim(input) {
    const detection = detectCloudMode({
        cloudFlag: input.cloudFlag,
        env: input.env,
        config: input.config,
    });
    return resolveEffectiveCloudActivation({
        detection,
        currentBranch: input.currentBranch,
    });
}
export function resolveDefaultClaimSandboxCommand(env = process.env, platform = process.platform) {
    if (platform === 'win32') {
        return ['powershell.exe', '-NoLogo'];
    }
    const shell = env.SHELL?.trim();
    return shell && shell.length > 0 ? [shell] : ['/bin/sh'];
}
export function resolveClaimSandboxCommand(argv = process.argv, env = process.env, platform = process.platform) {
    const explicit = extractSandboxCommandFromArgv(argv);
    if (explicit.length > 0) {
        return explicit;
    }
    return resolveDefaultClaimSandboxCommand(env, platform);
}
// ============================================================================
// WU-2668: Claim queue helpers (--wait-for-lane / --cancel-queue)
// ============================================================================
const DEFAULT_WAIT_TIMEOUT_SECONDS = 3600;
const WAIT_POLL_INTERVAL_MS = 5_000;
export async function appendClaimQueueEventWithIsolation(input, deps = {}) {
    const appendQueueEventImpl = deps.appendQueueEventImpl ?? appendQueueEvent;
    const checkIsInGitWorktree = deps.isInGitWorktreeImpl ?? isInGitWorktree;
    const runMicroWorktree = deps.withMicroWorktreeImpl ?? withMicroWorktree;
    if (checkIsInGitWorktree()) {
        appendQueueEventImpl(input.baseDir, input.event);
        return;
    }
    await runMicroWorktree({
        operation: input.operation,
        id: input.wuId.toLowerCase(),
        logPrefix: PREFIX,
        pushOnly: true,
        execute: async ({ worktreePath }) => {
            appendQueueEventImpl(worktreePath, input.event);
            return {
                commitMessage: input.commitMessage,
                files: [resolveClaimQueueRelativePath(worktreePath)],
            };
        },
    });
}
export async function cancelClaimQueueEntryWithIsolation(input, deps = {}) {
    const cancelActiveEntryImpl = deps.cancelActiveEntryImpl ?? cancelActiveEntry;
    const findActiveEntryImpl = deps.findActiveEntryImpl ?? findActiveEntry;
    const checkIsInGitWorktree = deps.isInGitWorktreeImpl ?? isInGitWorktree;
    const runMicroWorktree = deps.withMicroWorktreeImpl ?? withMicroWorktree;
    if (checkIsInGitWorktree()) {
        return cancelActiveEntryImpl(input.baseDir, input.wuId, input.timestamp);
    }
    if (!findActiveEntryImpl(input.baseDir, input.wuId)) {
        return { cancelled: false };
    }
    let result = { cancelled: false };
    await runMicroWorktree({
        operation: 'wu-claim-cancel-queue',
        id: input.wuId.toLowerCase(),
        logPrefix: PREFIX,
        pushOnly: true,
        execute: async ({ worktreePath }) => {
            result = cancelActiveEntryImpl(worktreePath, input.wuId, input.timestamp);
            return {
                commitMessage: `chore(${input.wuId.toLowerCase()}): cancel queued claim`,
                files: [resolveClaimQueueRelativePath(worktreePath)],
            };
        },
    });
    return result;
}
/**
 * WU-2668: Read the claiming WU's code_paths and compute current overlap
 * blockers. Returns an empty list when the YAML is missing or has no paths.
 */
export function computeOverlapBlockers(wuPath, statusPath, id) {
    if (!existsSync(wuPath))
        return [];
    const wuContent = readFileSync(wuPath, 'utf-8');
    const wuDoc = parseYAML(wuContent);
    const codePaths = Array.isArray(wuDoc?.code_paths) ? wuDoc.code_paths : [];
    if (codePaths.length === 0)
        return [];
    const result = detectConflicts(statusPath, codePaths, id);
    return result.conflicts.map((c) => c.wuid);
}
/**
 * WU-2668: Parse the --wait-timeout-seconds flag value. Falls back to the
 * default (3600s = 1 hour) when absent or invalid.
 */
export function resolveWaitTimeoutSeconds(raw) {
    if (typeof raw === 'number' && Number.isFinite(raw) && raw > 0)
        return Math.floor(raw);
    if (typeof raw === 'string' && raw.trim().length > 0) {
        const parsed = Number.parseInt(raw, 10);
        if (Number.isFinite(parsed) && parsed > 0)
            return parsed;
    }
    return DEFAULT_WAIT_TIMEOUT_SECONDS;
}
export async function maybeLaunchClaimSandboxSession(input, deps = {}) {
    if (!input.enabled) {
        return null;
    }
    const launchSandbox = deps.launchSandbox || runWuSandbox;
    const command = resolveClaimSandboxCommand(input.argv || process.argv, input.env || process.env, input.platform || process.platform);
    console.log(`${PREFIX} Launching post-claim session via wu:sandbox...`);
    return launchSandbox({
        id: input.id,
        worktree: input.worktreePath,
        command,
    });
}
// ============================================================================
// Main orchestrator
// ============================================================================
// eslint-disable-next-line sonarjs/cognitive-complexity -- main() orchestrates multi-step claim workflow
export async function main() {
    const args = createWUParser({
        name: 'wu-claim',
        description: 'Claim a work unit by creating a worktree/branch and updating status',
        options: [
            WU_OPTIONS.id,
            WU_OPTIONS.lane,
            WU_OPTIONS.worktree,
            WU_OPTIONS.branch,
            WU_OPTIONS.branchOnly,
            WU_OPTIONS.prMode,
            WU_OPTIONS.noAuto,
            WU_OPTIONS.force,
            WU_OPTIONS.forceOverlap,
            WU_OPTIONS.fix,
            WU_OPTIONS.reason,
            WU_OPTIONS.allowIncomplete,
            WU_OPTIONS.cloud, // WU-1491: Cloud/branch-pr mode for cloud agents
            WU_OPTIONS.resume, // WU-2411: Agent handoff flag
            WU_OPTIONS.skipSetup, // WU-1023: Skip auto-setup for fast claims
            WU_OPTIONS.noPush, // Skip pushing claim state/branch (air-gapped)
            // WU-2668: --wait-for-lane / --wait-timeout-seconds / --cancel-queue
            WU_OPTIONS.waitForLane,
            WU_OPTIONS.waitTimeoutSeconds,
            WU_OPTIONS.cancelQueue,
            WU_CLAIM_SANDBOX_OPTION,
        ],
        required: ['id', 'lane'],
        allowPositionalId: true,
    });
    const id = args.id.toUpperCase();
    if (!PATTERNS.WU_ID.test(id))
        die(`Invalid WU id '${args.id}'. Expected format WU-123`);
    // WU-2668: --cancel-queue short-circuits the claim flow. It drops the
    // queued entry (if any) without touching git/lane state so cancellation is
    // safe to invoke from anywhere.
    if (args.cancelQueue) {
        const result = await cancelClaimQueueEntryWithIsolation({
            baseDir: process.cwd(),
            wuId: id,
        });
        if (result.cancelled) {
            console.log(`${PREFIX} ${EMOJI.SUCCESS} Cancelled queued claim for ${id}.`);
        }
        else {
            console.log(`${PREFIX} No active queued claim for ${id} to cancel (no-op).`);
        }
        return;
    }
    // WU-1609: Resolve branch-aware cloud activation at preflight so explicit
    // protected-branch cloud requests fail before lane locking/state mutation.
    // WU-1766: Cloud detection MUST happen before ensureOnMain so cloud agents
    // on claude/*/codex/* branches can bypass the main-branch requirement.
    const preflightBranch = await getGitForCwd().getCurrentBranch();
    const preflightCloudEffective = resolveCloudActivationForClaim({
        cloudFlag: Boolean(args.cloud),
        env: process.env,
        config: getConfig().cloud,
        currentBranch: preflightBranch,
    });
    if (preflightCloudEffective.blocked) {
        const sourceHint = preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.FLAG
            ? '--cloud'
            : `${ENV_VARS.CLOUD}=1`;
        die(`Cloud mode blocked on protected branch "${preflightBranch}".\n\n` +
            `Explicit cloud activation (${sourceHint}) is not allowed on main/master.\n` +
            `Switch to a non-main branch for cloud mode, or run wu:claim without cloud activation on main/master.`);
    }
    if (preflightCloudEffective.suppressed) {
        const signalSuffix = preflightCloudEffective.matchedSignal
            ? ` (signal: ${preflightCloudEffective.matchedSignal})`
            : '';
        console.log(`${PREFIX} Cloud auto-detection suppressed on protected branch "${preflightBranch}"${signalSuffix}; continuing with standard claim flow.`);
    }
    else if (preflightCloudEffective.isCloud &&
        preflightCloudEffective.source === CLOUD_ACTIVATION_SOURCE.ENV_SIGNAL) {
        console.log(`${PREFIX} Cloud mode auto-detected (source: ${preflightCloudEffective.source}${preflightCloudEffective.matchedSignal ? `, signal: ${preflightCloudEffective.matchedSignal}` : ''})`);
    }
    // WU-1766: Skip ensureOnMain in cloud mode — cloud agents operate from
    // agent branches (claude/*, codex/*) and cannot switch to main.
    if (!shouldSkipEnsureOnMainForClaim({ isCloud: preflightCloudEffective.isCloud })) {
        await ensureOnMain(getGitForCwd());
    }
    else {
        console.log(`${PREFIX} Cloud mode: skipping ensureOnMain (agent branch: ${preflightBranch})`);
    }
    // WU-2411: Handle --resume flag for agent handoff
    if (args.resume) {
        await handleResumeMode(args, id);
        return; // Resume mode handles its own flow
    }
    // Preflight: ensure working tree is clean (unless --no-auto, which expects staged changes)
    if (!args.noAuto) {
        const status = await getGitForCwd().getStatus();
        if (status.trim()) {
            die(`Working tree is not clean. Commit or clean these changes before claiming.\n\n` +
                `Uncommitted changes:\n${status}\n\n` +
                `Options:\n` +
                `  1. git add . && git commit -m "..."\n` +
                `  2. If they are disposable, clean them up manually (do not git stash on main)\n` +
                `  3. Use --no-auto if you already staged claim edits manually`);
        }
    }
    let stagedChanges = [];
    if (args.noAuto) {
        await ensureCleanOrClaimOnlyWhenNoAuto();
        stagedChanges = await getStagedChanges();
    }
    // WU-1361: Fetch latest remote before validation (no local main mutation)
    // WU-1653: Also skip when git.requireRemote=false (local-only mode)
    // WU-2194: Removed ensureMainUpToDate — pushOnly mode bases from origin/main,
    // not local main, so local-main staleness is irrelevant.
    const skipRemote = shouldSkipRemoteOperations();
    const integrationTarget = resolveIntegrationTarget(getConfig());
    if (!args.noPush && !skipRemote) {
        await getGitForCwd().fetch(integrationTarget.remote, integrationTarget.mainBranch);
    }
    else if (skipRemote) {
        console.log(`${PREFIX} Local-only mode (git.requireRemote=false): skipping ${integrationTarget.remote} sync`);
    }
    else {
        console.warn(`${PREFIX} Warning: --no-push enabled. Skipping ${integrationTarget.trackingRef} sync; local state may be stale.`);
    }
    const WU_PATH = WU_PATHS.WU(id);
    const STATUS_PATH = WU_PATHS.STATUS();
    const BACKLOG_PATH = WU_PATHS.BACKLOG();
    // WU-1649: Delegated to wu-claim-validation.ts
    const { fixableIssues } = await runPreflightValidations(args, id, WU_PATH, STATUS_PATH);
    // WU-2668: --wait-for-lane queues the claim when overlaps exist and polls
    // wu-events.jsonl for blocker merges before continuing. Runs BEFORE the
    // lane lock is acquired so the lane stays free for whichever agent is
    // actually working on the blocking WUs.
    if (args.waitForLane) {
        const blockers = computeOverlapBlockers(WU_PATH, STATUS_PATH, id);
        if (blockers.length === 0) {
            console.log(`${PREFIX} --wait-for-lane: no overlapping in-progress WUs detected; proceeding immediately.`);
        }
        else {
            const timeoutSeconds = resolveWaitTimeoutSeconds(args.waitTimeoutSeconds);
            console.log(`${PREFIX} --wait-for-lane: queueing ${id} behind ${blockers.join(', ')} (timeout ${timeoutSeconds}s).`);
            await appendClaimQueueEventWithIsolation({
                baseDir: process.cwd(),
                wuId: id,
                operation: 'wu-claim-queue',
                commitMessage: `chore(${id.toLowerCase()}): queue claim wait-for-lane`,
                event: {
                    type: 'queued',
                    wuId: id,
                    lane: args.lane,
                    blockers,
                    timeoutSeconds,
                    timestamp: new Date().toISOString(),
                    ...(args.worktree ? { worktree: args.worktree } : {}),
                    ...(args.branch ? { branch: args.branch } : {}),
                },
            });
            const waitResult = await waitForBlockersResolved({
                baseDir: process.cwd(),
                blockers,
                timeoutMs: timeoutSeconds * 1000,
                pollIntervalMs: WAIT_POLL_INTERVAL_MS,
                isCancelled: () => findActiveEntry(process.cwd(), id) === null,
            });
            if (waitResult.cancelled) {
                console.log(`${PREFIX} --wait-for-lane: queued claim for ${id} was cancelled externally; aborting.`);
                return;
            }
            if (!waitResult.cleared) {
                await appendClaimQueueEventWithIsolation({
                    baseDir: process.cwd(),
                    wuId: id,
                    operation: 'wu-claim-queue-timeout',
                    commitMessage: `chore(${id.toLowerCase()}): time out queued claim`,
                    event: {
                        type: 'timed_out',
                        wuId: id,
                        timestamp: new Date().toISOString(),
                    },
                });
                die(`--wait-for-lane timed out after ${timeoutSeconds}s waiting for blockers to merge.\n\n` +
                    `Still blocking: ${waitResult.remainingBlockers.join(', ')}\n\n` +
                    `Options:\n` +
                    `  1. Rerun wu:claim --wait-for-lane with a larger --wait-timeout-seconds\n` +
                    `  2. Investigate why blocker WUs have not merged (pnpm mem:inbox / pnpm wu:status)\n` +
                    `  3. Cancel the queued claim: pnpm wu:claim --cancel-queue --id ${id}`);
            }
            console.log(`${PREFIX} ${EMOJI.SUCCESS} --wait-for-lane: all blockers merged; auto-firing wu:claim for ${id}.`);
            await appendClaimQueueEventWithIsolation({
                baseDir: process.cwd(),
                wuId: id,
                operation: 'wu-claim-queue-fire',
                commitMessage: `chore(${id.toLowerCase()}): fire queued claim`,
                event: {
                    type: 'fired',
                    wuId: id,
                    timestamp: new Date().toISOString(),
                },
            });
        }
    }
    // WU-1603: Atomic lane lock to prevent TOCTOU race conditions
    // This is Layer 2 defense after status.md check - prevents parallel agents from
    // both reading a free status.md before either updates it
    const existingLock = checkLaneLock(args.lane);
    if (existingLock.locked && existingLock.isStale) {
        const staleMetadata = existingLock.metadata;
        if (staleMetadata) {
            console.log(`${PREFIX} Detected stale lock for "${args.lane}" (${staleMetadata.wuId})`);
            console.log(`${PREFIX} Lock timestamp: ${staleMetadata.timestamp}`);
        }
        else {
            console.log(`${PREFIX} Detected stale lock for "${args.lane}"`);
        }
        forceRemoveStaleLock(args.lane);
    }
    const lockResult = acquireLaneLock(args.lane, id, {
        agentSession: null, // Will be set after session starts
    });
    if (!lockResult.acquired) {
        // Lock acquisition failed - another agent got there first
        const staleSuffix = lockResult.isStale
            ? '\n\nNote: This lock may be stale (>24h). Use --force to override if the owning WU is abandoned.'
            : '';
        die(`Cannot claim ${id}: ${lockResult.error}\n\n` +
            `Another agent is actively claiming or has claimed this lane.\n\n` +
            `Do not modify ${lockResult.existingLock?.wuId || 'the other WU'} with wu:block, wu:release, wu:recover, or wu:unblock just to free the lane.\n\n` +
            `Options:\n` +
            `  1. Wait for ${lockResult.existingLock?.wuId || 'the other WU'} to complete or block\n` +
            `  2. Choose a different lane\n` +
            `  3. Coordinate with the owner via mem:signal / wu:escalate\n` +
            `  4. Use --force to override (P0 emergencies only)${staleSuffix}`);
    }
    emitWUFlowEvent({
        script: 'wu-claim',
        wu_id: id,
        lane: args.lane,
        step: 'lane_lock_acquired',
    });
    // WU-2654: narrow conductor emission for lock:acquired. The lane identifier
    // is used as the lock_id since lane_locks (WU-1603) are the kernel's canonical
    // WIP-enforcement mechanism. Fail-silent when org_id unset.
    try {
        const orgId = readOrgIdFromConfig(getConfig());
        if (orgId) {
            const sink = await resolveConductorEventSink();
            sink(buildLockAcquiredEvent({
                lock_id: `lane:${args.lane}`,
                held_by: id,
            }));
        }
    }
    catch {
        // Emission is best-effort; lock is already held.
    }
    // WU-1808: Wrap claim execution in try/finally to ensure lock release on failure
    // If claim fails after lock acquisition, the lane would be blocked without this cleanup
    let claimSucceeded = false;
    // WU-1521: Track canonical claim push state for rollback in finally block
    let canonicalClaimPushed = false;
    let claimTitle = '';
    let claimActor;
    let postClaimSandboxWorktree;
    try {
        // Code paths overlap detection (WU-901)
        handleCodePathOverlap(WU_PATH, STATUS_PATH, id, args);
        // Prepare paths and branches
        const laneK = toKebab(args.lane);
        const idK = id.toLowerCase();
        const title = (await readWUTitle(id)) || '';
        const branch = args.branch || `lane/${laneK}/${idK}`;
        const configuredWorktreesDir = getConfig({ projectRoot: process.cwd() }).directories.worktrees;
        const requestedWorktree = args.worktree || path.join(configuredWorktreesDir, `${laneK}-${idK}`);
        const worktree = toRelativeWorktreePathForStorage(requestedWorktree, process.cwd());
        const currentBranch = preflightBranch;
        const cloudEffective = preflightCloudEffective;
        const effectiveCloud = cloudEffective.isCloud;
        // WU-1590: Capture current branch for cloud claim metadata (before UnsafeAny branch switching)
        const currentBranchForCloud = effectiveCloud ? currentBranch : undefined;
        // WU-1491: Resolve claimed mode from flag combination
        const modeResult = resolveClaimMode({
            branchOnly: args.branchOnly,
            prMode: args.prMode,
            cloud: effectiveCloud,
        });
        if (modeResult.error) {
            die(modeResult.error);
        }
        const claimedMode = modeResult.mode;
        if (!claimedMode) {
            die('Could not resolve claim mode from CLI flags.');
        }
        // Branch-Only singleton guard: only for pure branch-only mode (not branch-pr)
        // branch-pr skips this guard because it supports parallel agents via PR isolation
        if (!modeResult.skipBranchOnlySingletonGuard) {
            await validateBranchOnlyMode(STATUS_PATH, id);
        }
        // WU-1590: Skip branch-exists checks in cloud mode (branch already exists by definition)
        const branchExecution = resolveBranchClaimExecution({
            claimedMode,
            isCloud: effectiveCloud,
            currentBranch,
            requestedBranch: branch,
        });
        const effectiveBranch = branchExecution.executionBranch;
        const skipBranchChecks = shouldSkipBranchExistsCheck({
            isCloud: effectiveCloud,
            currentBranch,
            laneBranch: effectiveBranch,
        });
        // Check if remote branch already exists (prevents duplicate global claims)
        // WU-1653: Skip when requireRemote=false (no remote to check)
        if (!args.noPush && !skipBranchChecks && !shouldSkipRemoteOperations()) {
            const remoteExists = await getGitForCwd().remoteBranchExists(integrationTarget.remote, effectiveBranch);
            if (remoteExists) {
                die(`Remote branch ${integrationTarget.remote}/${effectiveBranch} already exists. WU may already be claimed.\n\n` +
                    `Options:\n` +
                    `  1. Coordinate with the owning agent or wait for completion\n` +
                    `  2. Choose a different WU\n` +
                    `  3. Use --no-push for local-only claims (offline)`);
            }
        }
        // Check if branch already exists locally (prevents duplicate claims)
        if (!skipBranchChecks) {
            const branchAlreadyExists = await getGitForCwd().branchExists(effectiveBranch);
            if (branchAlreadyExists) {
                die(`Branch ${effectiveBranch} already exists. WU may already be claimed.\n\n` +
                    `Git branch existence = WU claimed (natural locking).\n\n` +
                    `Options:\n` +
                    `  1. Check git worktree list to see if worktree exists\n` +
                    `  2. Coordinate with the owning agent or wait for them to complete\n` +
                    `  3. Choose a different WU`);
            }
        }
        // Layer 3 defense (WU-1476): Pre-flight orphan check
        // Clean up orphan directory if it exists at target worktree path
        const absoluteWorktreePath = path.resolve(worktree);
        if (await isOrphanWorktree(absoluteWorktreePath, process.cwd())) {
            console.log(`${PREFIX} Detected orphan directory at ${worktree}, cleaning up...`);
            try {
                rmSync(absoluteWorktreePath, { recursive: true, force: true });
                console.log(`${PREFIX} ${EMOJI.SUCCESS} Orphan directory removed`);
            }
            catch (err) {
                die(`Failed to clean up orphan directory at ${worktree}\n\n` +
                    `Error: ${getErrorMessage(err)}\n\n` +
                    `Manual cleanup: rm -rf ${absoluteWorktreePath}`);
            }
        }
        // WU-1438: Start agent session BEFORE metadata update to include session_id in YAML
        // WU-2754 (ADR-014 extension 2): stamp lifecycle_role + specialty_profile
        // on the auto-started session using the pack-layer role resolver. Kernel
        // stays vocabulary-agnostic; CLI owns the alias → descriptor mapping.
        const roleContract = resolveDefaultRoleContract({
            targetWuId: id,
            lane: laneK,
        });
        let sessionId = null;
        try {
            const sessionResult = await startSessionForWU({
                wuId: id,
                tier: 2,
                lifecycleRole: roleContract.actual_role.lifecycle_role,
                specialtyProfile: roleContract.actual_role.specialty_profile,
            });
            sessionId = sessionResult.sessionId;
            if (sessionResult.alreadyActive) {
                console.log(`${PREFIX} Agent session already active (${sessionId.slice(0, 8)}...)`);
            }
            else {
                console.log(`${PREFIX} ${EMOJI.SUCCESS} Agent session started (${sessionId.slice(0, 8)}...)`);
            }
        }
        catch (err) {
            // Non-blocking: session start failure should not block claim
            console.warn(`${PREFIX} Warning: Could not start agent session: ${getErrorMessage(err)}`);
        }
        // Execute claim workflow
        const baseCtx = {
            args,
            id,
            laneK,
            title,
            branch: effectiveBranch,
            worktree,
            WU_PATH,
            STATUS_PATH,
            BACKLOG_PATH,
            claimedMode,
            shouldCreateBranch: branchExecution.shouldCreateBranch,
            currentBranch,
            fixableIssues, // WU-1361: Pass fixable issues for worktree application
            stagedChanges,
            currentBranchForCloud, // WU-1590: For persisting claimed_branch in branch-pr mode
        };
        let updatedTitle = title;
        claimTitle = title;
        const shouldApplyCanonicalUpdate = shouldApplyCanonicalClaimUpdateFn({
            isCloud: effectiveCloud,
            claimedMode,
            noPush: Boolean(args.noPush),
        });
        if (shouldApplyCanonicalUpdate) {
            updatedTitle = (await applyCanonicalClaimUpdate(baseCtx, sessionId)) || updatedTitle;
            // WU-1521: Mark that canonical claim was pushed to origin/main
            // If claim fails after this point, the finally block will rollback
            canonicalClaimPushed = true;
            claimTitle = updatedTitle || title;
            // Refresh origin/main after push-only update so worktrees start from canonical state
            // WU-1653: Skip fetch when requireRemote=false (no remote)
            if (!shouldSkipRemoteOperations()) {
                await getGitForCwd().fetch(integrationTarget.remote, integrationTarget.mainBranch);
            }
        }
        else if (!args.noPush && claimedMode === CLAIMED_MODES.BRANCH_PR) {
            console.log(`${PREFIX} Skipping canonical claim update on ${integrationTarget.trackingRef} for cloud branch-pr claim.`);
        }
        const ctx = {
            ...baseCtx,
            args: baseCtx.args,
            sessionId,
            updatedTitle,
        };
        // WU-1491: Route to correct mode handler
        // branch-pr uses branch-only workflow (no worktree) but with branch-pr claimed_mode
        if (claimedMode === CLAIMED_MODES.BRANCH_ONLY || claimedMode === CLAIMED_MODES.BRANCH_PR) {
            await claimBranchOnlyMode(ctx);
        }
        else {
            await claimWorktreeMode(ctx);
        }
        const wuBriefPolicyMode = resolveWuClaimBriefPolicyMode(getConfig());
        const briefWorkspaceRoot = claimedMode === CLAIMED_MODES.BRANCH_ONLY || claimedMode === CLAIMED_MODES.BRANCH_PR
            ? process.cwd()
            : path.resolve(worktree);
        await maybeRunAutoBriefForClaim({
            wuId: id,
            workspaceRoot: briefWorkspaceRoot,
            policyMode: wuBriefPolicyMode,
            claimedMode,
            claimedBranch: effectiveBranch,
        });
        // WU-1605: Record claim-time pickup evidence for delegation provenance.
        // Non-blocking: this metadata should not block claim completion.
        try {
            let claimedBy = process.env[ENV_VARS.GIT_AUTHOR_EMAIL]?.trim();
            try {
                claimedBy = await getAssignedEmail(getGitForCwd());
            }
            catch {
                // Fall back to env/default when git email lookup fails in this context.
            }
            const pickupResult = await recordClaimPickupEvidence(id, {
                baseDir: process.cwd(),
                claimedBy,
            });
            claimActor = claimedBy;
            if (pickupResult.recorded) {
                console.log(`${PREFIX} ${EMOJI.SUCCESS} Recorded delegation pickup evidence (${pickupResult.spawnId})`);
            }
            else if (pickupResult.alreadyRecorded) {
                console.log(`${PREFIX} ${EMOJI.INFO} Delegation pickup evidence already recorded (${pickupResult.spawnId})`);
            }
            // WU-2654: narrow conductor emission for delegation:picked_up. Only fires
            // when a pickup record was just written (i.e. the WU was spawned via
            // wu:delegate and the current actor is the pickup owner). spawnId doubles
            // as delegation_id here — the wu-spawn-helpers code uses them
            // interchangeably (see WU-1605). Fail-silent when org_id unset.
            if (pickupResult.recorded && pickupResult.spawnId) {
                try {
                    const orgId = readOrgIdFromConfig(getConfig());
                    if (orgId) {
                        const sink = await resolveConductorEventSink();
                        sink(buildDelegationPickedUpEvent({
                            delegation_id: pickupResult.spawnId,
                            picked_up_by: claimedBy ?? 'unknown',
                        }));
                    }
                }
                catch {
                    // Best-effort; pickup evidence already recorded.
                }
            }
        }
        catch (err) {
            console.warn(`${PREFIX} Warning: Could not record delegation pickup evidence: ${getErrorMessage(err)}`);
        }
        await flushClaimLifecycleSyncEvent({
            wuId: id,
            by: claimActor,
            sessionId: sessionId ?? undefined,
        });
        // WU-2642 (INIT-057 Phase 4): Start sub-minute control-plane event sync
        // sidecar so state changes between wu:claim and wu:done reach the cloud
        // within sync_interval. Best-effort — never blocks claim completion.
        await maybeStartControlPlaneSidecar();
        // Mark claim as successful - lock should remain for wu:done to release
        claimSucceeded = true;
        postClaimSandboxWorktree =
            claimedMode === CLAIMED_MODES.BRANCH_ONLY || claimedMode === CLAIMED_MODES.BRANCH_PR
                ? process.cwd()
                : path.resolve(worktree);
    }
    finally {
        // WU-1808: Release lane lock if claim did not complete successfully
        // This prevents orphan locks from blocking the lane when claim crashes or fails
        if (!claimSucceeded) {
            // WU-1521: Rollback canonical claim state if it was pushed to origin/main
            // This ensures re-running wu:claim succeeds without needing wu:repair
            if (canonicalClaimPushed) {
                await rollbackCanonicalClaim(id, args.lane, claimTitle);
            }
            console.log(`${PREFIX} Claim did not complete - releasing lane lock...`);
            const releaseResult = releaseLaneLock(args.lane, { wuId: id });
            if (releaseResult.released && !releaseResult.notFound) {
                console.log(`${PREFIX} Lane lock released for "${args.lane}"`);
            }
        }
    }
    const sandboxExitCode = await maybeLaunchClaimSandboxSession({
        enabled: Boolean(args.sandbox && claimSucceeded && postClaimSandboxWorktree),
        id,
        worktreePath: postClaimSandboxWorktree || process.cwd(),
        argv: process.argv,
    });
    if (sandboxExitCode !== null && sandboxExitCode !== EXIT_CODES.SUCCESS) {
        die(`Post-claim sandbox command exited with code ${sandboxExitCode}.\n` +
            `Claim for ${id} remains active. Resume from ${postClaimSandboxWorktree || process.cwd()}.`);
    }
}
// Guard main() for testability (WU-1366)
// WU-1071: 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-claim.js.map