#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * Orchestrate Initiative Status CLI
 *
 * Compact status view for initiative orchestration.
 * Shows progress of WUs in an initiative.
 *
 * Usage:
 *   pnpm orchestrate:init-status --initiative INIT-001
 */
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import { Command } from 'commander';
import { loadInitiativeWUs, formatProgress, getLaneAvailability, resolveLaneConfigsFromConfig, analyseScopeShape, formatScopeAdvisory, } from '@lumenflow/initiatives';
import { ORCHESTRATION_ARTIFACT_DIR, } from '@lumenflow/control-plane-sdk';
import { EXIT_CODES } from '@lumenflow/core/wu-constants';
import { getConfig } from '@lumenflow/core/config';
import { getErrorMessage, ProcessExitError } from '@lumenflow/core/error-handler';
import chalk from 'chalk';
import { runCLI } from './cli-entry-point.js';
import { applyExecutionSummaryToWus, EXECUTION_STATES, formatExecutionStateLabel, formatQualitySignalsSection, loadInitiativeExecutionSummary, } from './orchestrate-monitor.js';
import { defaultWorktreeFrom } from '@lumenflow/core/wu-paths';
import { readMonitorQualitySignalsConfig, runQualityDetectionForActiveWorktrees, } from './orchestrate-monitor-quality.js';
import { createNextSafeActionsDebouncer } from './kernel-event-sync/narrow-emissions.js';
import { emitOrgCapacitySnapshot } from './capacity-snapshot-emitter.js';
const LOG_PREFIX = '[orchestrate:init-status]';
const ATTEMPT_PREFIX = 'attempt-';
const STATUS_FILENAME = 'status.json';
/**
 * WU-2643: Read `next_safe_actions` from the latest orchestration status.json
 * attempt. Returns an empty array when no attempts exist yet — the narrow
 * emission debouncer will still fire once with count=0 so the phone-home
 * screen knows the initiative has no pending actions.
 */
function loadLatestNextSafeActions(baseDir, initiativeId) {
    const attemptsDir = join(baseDir, ORCHESTRATION_ARTIFACT_DIR, initiativeId);
    if (!existsSync(attemptsDir)) {
        return [];
    }
    let latestAttempt = -1;
    let latestPath = null;
    for (const entry of readdirSync(attemptsDir, { withFileTypes: true })) {
        if (!entry.isDirectory() || !entry.name.startsWith(ATTEMPT_PREFIX)) {
            continue;
        }
        const parsed = Number.parseInt(entry.name.slice(ATTEMPT_PREFIX.length), 10);
        if (!Number.isNaN(parsed) && parsed > latestAttempt) {
            latestAttempt = parsed;
            latestPath = join(attemptsDir, entry.name, STATUS_FILENAME);
        }
    }
    if (!latestPath || !existsSync(latestPath)) {
        return [];
    }
    try {
        const parsed = JSON.parse(readFileSync(latestPath, 'utf8'));
        if (Array.isArray(parsed.next_safe_actions)) {
            return parsed.next_safe_actions;
        }
    }
    catch {
        // Corrupt status.json: treat as no pending actions rather than crash the
        // phone-home refresh. The action set will be re-emitted on the next tick.
    }
    return [];
}
/**
 * WU-2643: Resolve the conductor sink for narrow emissions. The sink pushes
 * through the control-plane SDK when workspace.yaml declares one; otherwise
 * returns a fail-silent noop so local-only workspaces keep working.
 */
async function resolveConductorEventSink() {
    try {
        const { resolveWuLifecycleEventSink } = await import('./wu-lifecycle-sync/control-plane-sink.js');
        const sink = await resolveWuLifecycleEventSink({});
        return (event) => {
            // KernelEvent and SdkKernelEvent overlap on the wire shapes that the
            // cloud accepts (ADR-011 §2); the sink typechecks against KernelEvent
            // for v1 compat while the SDK discriminant drives v2 routing.
            void sink.push([event]);
        };
    }
    catch {
        return () => {
            /* noop */
        };
    }
}
function readOrgIdFromConfig(config) {
    const controlPlane = config.control_plane;
    const orgId = controlPlane?.org_id;
    return typeof orgId === 'string' && orgId.trim().length > 0 ? orgId.trim() : undefined;
}
function normalizeLifecycleStatus(value) {
    return typeof value === 'string' ? value.trim().toLowerCase() : '';
}
function hasIncompletePhase(phases) {
    if (!Array.isArray(phases) || phases.length === 0) {
        return false;
    }
    return phases.some((phase) => {
        if (phase === null || typeof phase !== 'object') {
            return true;
        }
        const status = normalizeLifecycleStatus(phase.status);
        return status !== 'done';
    });
}
function deriveInitiativeLifecycleStatus(status, phases) {
    const normalizedStatus = normalizeLifecycleStatus(status);
    if (normalizedStatus === 'done' && hasIncompletePhase(phases)) {
        return 'in_progress';
    }
    return normalizedStatus || 'in_progress';
}
function formatLaneAvailability(availability, laneConfigs) {
    const lanes = Object.keys(availability).sort((a, b) => a.localeCompare(b));
    if (lanes.length === 0) {
        return '  (no lanes found)';
    }
    return lanes
        .map((lane) => {
        const entry = availability[lane];
        const wipLimit = laneConfigs[lane]?.wip_limit ?? 1;
        const statusLabel = entry.available ? chalk.green('available') : chalk.red('occupied');
        const occupiedBy = entry.occupiedBy ?? 'none';
        return `  ${lane}: ${statusLabel} (wip_limit=${wipLimit}, lock_policy=${entry.policy}, in_progress=${entry.inProgressCount}, blocked=${entry.blockedCount}, occupied_by=${occupiedBy})`;
    })
        .join('\n');
}
export function formatWuExecutionStatus(wu) {
    const details = [];
    if (typeof wu.logicalWave === 'number') {
        details.push(`logical wave ${wu.logicalWave}`);
    }
    if (typeof wu.launchAttempt === 'number') {
        details.push(`attempt ${wu.launchAttempt}`);
    }
    details.push(wu.reason);
    return `${formatExecutionStateLabel(wu.state)}; ${details.join('; ')}`;
}
const program = new Command()
    .name('orchestrate:init-status')
    .description('Show initiative progress status')
    .requiredOption('-i, --initiative <id>', 'Initiative ID (e.g., INIT-001)')
    .action(async (opts) => {
    try {
        console.log(chalk.cyan(`${LOG_PREFIX} Loading initiative ${opts.initiative}...`));
        const { initiative, wus } = loadInitiativeWUs(opts.initiative);
        const lifecycleStatus = deriveInitiativeLifecycleStatus(initiative.status, initiative.phases);
        const rawStatus = normalizeLifecycleStatus(initiative.status);
        const executionSummary = await loadInitiativeExecutionSummary(process.cwd(), opts.initiative, wus);
        const reconciledWus = applyExecutionSummaryToWus(wus, executionSummary);
        console.log(chalk.bold(`\nInitiative: ${initiative.id} - ${initiative.title}`));
        console.log(chalk.bold(`Lifecycle Status: ${lifecycleStatus}`));
        if (rawStatus && rawStatus !== lifecycleStatus) {
            console.log(chalk.yellow(`Lifecycle mismatch: metadata status '${initiative.status}' conflicts with phase state; reporting '${lifecycleStatus}'.`));
        }
        console.log('');
        const progress = executionSummary.progress;
        console.log(chalk.bold('Progress:'));
        console.log(formatProgress(progress));
        if (progress.workerReturned > 0 || progress.handoffEmitted > 0) {
            console.log(`  Returned incomplete: ${progress.workerReturned}\n  Handoff emitted: ${progress.handoffEmitted}`);
        }
        console.log('');
        // Show WU status breakdown
        console.log(chalk.bold('WUs:'));
        for (const wu of executionSummary.wus) {
            const status = formatWuExecutionStatus(wu);
            console.log(`  ${wu.wuId}: ${wu.title} [${status}]`);
        }
        // WU-2155: Scope advisory analysis
        const scopeResult = analyseScopeShape(wus);
        const scopeOutput = formatScopeAdvisory(scopeResult);
        if (scopeOutput) {
            console.log('');
            console.log(chalk.yellow(scopeOutput));
        }
        const config = getConfig();
        const laneConfigs = resolveLaneConfigsFromConfig(config);
        const availability = getLaneAvailability(reconciledWus, { laneConfigs });
        console.log('');
        console.log(chalk.bold('Lane Availability:'));
        console.log(formatLaneAvailability(availability, laneConfigs));
        // WU-2667: mid-execution quality signal. Non-blocking warning when an
        // active worktree has prod commits but zero test commits past the
        // grace window. Also writes to signals.jsonl for mem:inbox polls.
        const qualityPolicy = readMonitorQualitySignalsConfig(config);
        if (qualityPolicy.enabled) {
            const activeWorktrees = executionSummary.wus
                .filter((wu) => wu.worktreePresent && wu.state !== EXECUTION_STATES.DONE)
                .map((wu) => {
                const worktreePath = defaultWorktreeFrom({ lane: wu.lane, id: wu.wuId });
                if (!worktreePath)
                    return null;
                return {
                    wuId: wu.wuId,
                    lane: wu.lane,
                    worktreePath,
                    claimedAt: wu.claimedAt ?? wu.latestCheckpointAt,
                };
            })
                .filter((wt) => wt !== null);
            const qualitySignals = await runQualityDetectionForActiveWorktrees({
                baseDir: process.cwd(),
                now: new Date(),
                policy: qualityPolicy,
                activeWorktrees,
            });
            if (qualitySignals.length > 0) {
                console.log('');
                console.log(formatQualitySignalsSection(qualitySignals));
            }
        }
        // WU-2643: narrow conductor emissions for the phone-home screen.
        // Fire-and-forget: phone-home refresh must stay non-blocking.
        const orgId = readOrgIdFromConfig(config);
        if (orgId) {
            const sink = await resolveConductorEventSink();
            emitOrgCapacitySnapshot({ orgId, availability, sink });
            const actions = loadLatestNextSafeActions(process.cwd(), opts.initiative);
            const debouncer = createNextSafeActionsDebouncer({ sink });
            debouncer.push({ initiative: opts.initiative, actions });
        }
    }
    catch (err) {
        if (err instanceof ProcessExitError) {
            throw err;
        }
        const message = `${LOG_PREFIX} Error: ${getErrorMessage(err)}`;
        console.error(chalk.red(message));
        throw new ProcessExitError(message, EXIT_CODES.ERROR);
    }
});
export async function main() {
    await program.parseAsync(process.argv);
}
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=orchestrate-init-status.js.map