use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

use anyhow::{bail, Context, Result};
use serde::Serialize;

use super::agent_readiness;
use crate::display_safe::display_safe;
use crate::extensions;
use crate::handoff::{self, BranchMode, CheckoutContinuityAdvisory};
use crate::memory::{
    entries as memory_entries, provider as memory_provider, recall as memory_recall,
};
use crate::output::{self, CommandReport, WorkStreamView};
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::repo::registry as repo_registry;
use crate::repo::truth as project_truth;
use crate::session_boundary::{SessionBoundaryAction, SessionBoundaryRecommendation};
use crate::state::compiled as compiled_state;
use crate::state::config_migration;
use crate::state::consistency;
use crate::state::escalation as escalation_state;
use crate::state::machine_presence;
use crate::state::machine_registry as pod_identity;
use crate::state::policy_projection;
use crate::state::runtime as runtime_state;
use crate::state::session as session_state;
use crate::state::session_gates;
use crate::state::validation_profile;
use crate::telemetry::host_loop::{ContextSectionSpec, ContextSessionSnapshot};
use crate::timestamps;
use serde_json::Value;
use tracing::{debug, info_span};

/// Explicit startup disposition — tells consumers what kind of startup
/// state `ccd start` found.  See ccd#135 and the Session Continuity
/// Kernel RFC (ccd#102).
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StartupDisposition {
    ResumeActiveContinuity,
    NoActiveContinuity,
    WorkflowAttentionRequired,
    ResumeBlockedContinuity,
}

#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum StartRenderSection {
    EffectivePolicy,
    EffectiveMemory,
    ExecutionGates,
    Handoff,
}

impl StartRenderSection {
    pub(crate) fn as_str(self) -> &'static str {
        match self {
            Self::EffectivePolicy => "effective_policy",
            Self::EffectiveMemory => "effective_memory",
            Self::ExecutionGates => "execution_gates",
            Self::Handoff => "handoff",
        }
    }
}

pub(crate) const START_RENDER_SECTION_ORDER: &[StartRenderSection] = &[
    StartRenderSection::EffectivePolicy,
    StartRenderSection::EffectiveMemory,
    StartRenderSection::ExecutionGates,
    StartRenderSection::Handoff,
];

const HOST_CONTEXT_NEXT_FOCUS_LIMIT: usize = 3;
const HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS: usize = 120;
const HOST_CONTEXT_REMEMBER_LIMIT: usize = 2;
const HOST_CONTEXT_REMEMBER_MAX_CHARS: usize = 120;
const HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT: usize = 6;
const HOST_CONTEXT_GUARDRAILS_LIMIT: usize = 3;
const HOST_CONTEXT_GUARDRAILS_MAX_CHARS: usize = 100;

#[derive(Serialize)]
pub struct StartReport {
    command: &'static str,
    ok: bool,
    decisions: StartDecisions,
    agent_readiness: agent_readiness::AgentReadinessSummary,
    disposition: StartupDisposition,
    session_boundary: SessionBoundaryRecommendation,
    path: String,
    profile: String,
    project_id: String,
    workspace_path: String,
    work_stream: WorkStreamView,
    locality_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    session_id: Option<String>,
    compact_summary: CompactSummary,
    source_order: &'static str,
    manifest: ManifestView,
    machine_identity: pod_identity::MachineIdentityView,
    machine_presence: machine_presence::MachinePresenceView,
    execution_context: machine_presence::MachineExecutionContextView,
    takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
    coordination_scope: pod_identity::CoordinationScopeView,
    sources: Vec<SourceView>,
    effective_policy: RenderedView,
    policy_projection: policy_projection::PolicyProjectionView,
    effective_memory: MemoryView,
    memory_recall: memory_recall::StartRecallView,
    memory_provider: memory_provider::MemoryProviderView,
    startup_recall: memory_provider::StartupRecallView,
    execution_gates: session_gates::ExecutionGatesView,
    handoff: HandoffView,
    recovery: runtime_state::RuntimeRecoveryView,
    session_state: SessionStateView,
    #[serde(skip_serializing_if = "Option::is_none")]
    activation: Option<session_state::SessionStateStartReport>,
    escalation: escalation_state::EscalationView,
    compiled_state: compiled_state::CompiledStateSurfaceView,
    alerts: Vec<StartAlert>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    refresh_actions: Vec<RefreshAction>,
    warnings: Vec<String>,
}

#[derive(Serialize)]
struct StartDecisions {
    requires_human_choice: bool,
    requires_doctor: bool,
    can_clear_session: bool,
    safe_clean_close: bool,
    needs_full_handover: bool,
    provider_live_check_needed: bool,
    prompt_context_unchanged: bool,
    recommended_next_command: &'static str,
}

const START_CHECK_EXIT_NOT_READY: u8 = 3;
const START_CHECK_EXIT_BLOCKED_ESCALATION: u8 = 2;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StartCheckReadiness {
    Ready,
    Blocked,
    NotReady,
}

#[derive(Serialize)]
pub struct StartCheckReport {
    command: &'static str,
    ok: bool,
    disposition: StartupDisposition,
    path: String,
    profile: String,
    project_id: String,
    workspace_path: String,
    work_stream: WorkStreamView,
    locality_id: String,
    mode: &'static str,
    readiness: StartCheckReadiness,
    agent_readiness: agent_readiness::AgentReadinessSummary,
    escalation: escalation_state::EscalationView,
    alerts: Vec<StartAlert>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    refresh_actions: Vec<RefreshAction>,
    warnings: Vec<String>,
}

#[derive(Serialize)]
struct StartReadinessView {
    status: StartReadinessStatus,
    exit_code: u8,
    note: &'static str,
}

#[derive(Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
enum StartReadinessStatus {
    Ready,
    Blocked,
    NotReady,
}

#[derive(Serialize)]
struct ManifestView {
    path: String,
    status: &'static str,
    entries: Vec<String>,
}

#[derive(Serialize)]
struct SourceView {
    kind: &'static str,
    path: String,
    status: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    content: Option<String>,
}

#[derive(Serialize)]
struct RenderedView {
    status: &'static str,
    content: String,
    parts: Vec<RenderedPart>,
}

#[derive(Serialize)]
struct MemoryView {
    status: &'static str,
    content: String,
    parts: Vec<RenderedPart>,
    structured: memory_entries::StructuredMemoryView,
}

#[derive(Serialize)]
struct RenderedPart {
    kind: &'static str,
    path: String,
    status: &'static str,
}

#[derive(Clone, Copy)]
pub(crate) enum HostContextMode {
    Startup,
    PromptBuild,
}

pub(crate) struct HostContextPayload {
    pub(crate) session_boundary: SessionBoundaryRecommendation,
    pub(crate) context: Value,
    pub(crate) sections: Vec<ContextSectionSpec>,
    pub(crate) remember_selection: Option<RememberSelectionView>,
    pub(crate) source_fingerprint: String,
    pub(crate) session: ContextSessionSnapshot,
    pub(crate) alerts: Vec<StartAlert>,
}

pub(crate) struct HostOperatorSummary {
    pub(crate) session_boundary: SessionBoundaryRecommendation,
    pub(crate) disposition: StartupDisposition,
    pub(crate) compact_summary: CompactSummary,
    pub(crate) recovery: runtime_state::RuntimeRecoveryView,
    pub(crate) escalation: escalation_state::EscalationView,
    pub(crate) alerts: Vec<StartAlert>,
}

#[derive(Clone, Serialize)]
pub(crate) struct RememberSelectionView {
    pub(crate) budget: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) query: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) selected: Vec<RememberSelectionItemView>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) suppressed: Vec<RememberSelectionItemView>,
}

#[derive(Clone, Serialize)]
pub(crate) struct RememberSelectionItemView {
    pub(crate) rendered: String,
    pub(crate) scope: &'static str,
    pub(crate) score: i64,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) matched_tokens: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) reasons: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub(crate) suppressed_reasons: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) created_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) last_touched_session: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub(crate) source_ref: Option<String>,
}

struct EffectiveMemorySurfaces<'a> {
    profile: &'a runtime_state::RuntimeTextSurface,
    locality: &'a runtime_state::RuntimeTextSurface,
    branch: &'a runtime_state::RuntimeTextSurface,
    clone: &'a runtime_state::RuntimeTextSurface,
}

#[derive(Serialize)]
struct HandoffView {
    path: String,
    status: &'static str,
    content: String,
}

#[derive(Serialize)]
struct SessionStateView {
    path: String,
    status: &'static str,
    schema_version: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    session_id: Option<String>,
    started_at_epoch_s: Option<u64>,
    last_started_at_epoch_s: Option<u64>,
    start_count: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    mode: Option<session_state::SessionMode>,
    #[serde(flatten)]
    lifecycle: session_state::SessionLifecycleProjection,
}

#[derive(Clone, Serialize)]
pub(crate) struct StartAlert {
    check: &'static str,
    severity: StartAlertSeverity,
    message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    details: Option<serde_json::Value>,
}

impl StartAlert {
    fn warning(check: &'static str, message: String) -> Self {
        Self {
            check,
            severity: StartAlertSeverity::Warning,
            message,
            details: None,
        }
    }

    fn error(check: &'static str, message: String) -> Self {
        Self {
            check,
            severity: StartAlertSeverity::Error,
            message,
            details: None,
        }
    }

    fn is_provider_related(&self) -> bool {
        matches!(
            self.check,
            "memory_provider"
                | "memory_provider_config"
                | "memory_provider_health"
                | "memory_ingest_provider_config"
        )
    }

    fn same_public_content(&self, other: &Self) -> bool {
        self.check == other.check
            && matches!(
                (self.severity, other.severity),
                (StartAlertSeverity::Info, StartAlertSeverity::Info)
                    | (StartAlertSeverity::Warning, StartAlertSeverity::Warning)
                    | (StartAlertSeverity::Error, StartAlertSeverity::Error)
            )
            && self.message == other.message
            && self.details == other.details
    }
}

#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
enum StartAlertSeverity {
    Info,
    Warning,
    Error,
}

#[derive(Clone, Serialize)]
pub struct RefreshAction {
    target: &'static str,
    status: &'static str,
    message: String,
}

#[derive(Serialize)]
pub(crate) struct CompactSummary {
    current_state: CompactCurrentState,
    next_focus: CompactNextFocus,
    active_guardrails: CompactGuardrails,
}

#[derive(Serialize)]
struct CompactCurrentState {
    source: &'static str,
    title: String,
    current_system_state: Vec<String>,
}

#[derive(Serialize)]
struct CompactNextFocus {
    continuity_immediate_actions: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    execution_gate_anchor: Option<String>,
}

#[derive(Serialize)]
struct CompactGuardrails {
    operational_guardrails: Vec<String>,
    effective_memory: compiled_state::CompiledMemoryView,
}

struct ManifestResolution {
    source_order: &'static str,
    manifest: ManifestView,
    project_truth_sources: Vec<SourceView>,
}

#[derive(Clone)]
struct TextSource {
    kind: &'static str,
    path: PathBuf,
    status: &'static str,
    content: Option<String>,
}

impl StartReport {
    pub(crate) fn with_activation(
        mut self,
        activation: session_state::SessionStateStartReport,
    ) -> Self {
        self.activation = Some(activation);
        self
    }
}

impl CommandReport for StartReport {
    fn exit_code(&self) -> ExitCode {
        ExitCode::SUCCESS
    }

    fn render_text(&self) {
        if let Some(attention) = start_operator_attention(self) {
            println!("Attention: {attention}");
        }
        println!("Next: {}", start_operator_prompt(self));
    }
}

impl CommandReport for StartCheckReport {
    fn exit_code(&self) -> ExitCode {
        ExitCode::from(self.readiness.exit_code())
    }

    fn render_text(&self) {
        println!(
            "Start readiness is {} for profile {} and linked project {}.",
            self.readiness.status_label(),
            self.profile,
            self.project_id
        );
        println!("{}", self.readiness.note());
        println!(
            "Exit codes: 0 ready, {START_CHECK_EXIT_BLOCKED_ESCALATION} blocked, {START_CHECK_EXIT_NOT_READY} not ready, 1 command error."
        );

        if !self.refresh_actions.is_empty() {
            println!("Refresh actions:");
            for action in &self.refresh_actions {
                println!("- {} [{}] {}", action.target, action.status, action.message);
            }
        }

        if self.alerts.is_empty() {
            println!("No readiness alerts are active.");
            if self.readiness.status() == StartReadinessStatus::Blocked {
                println!(
                    "Blocking escalations: {} blocking, {} non-blocking at {}.",
                    self.escalation.blocking_count,
                    self.escalation.non_blocking_count,
                    self.escalation.path
                );
                for entry in self.escalation.entries.iter().filter(|entry| {
                    matches!(entry.kind, escalation_state::EscalationKind::Blocking)
                }) {
                    println!("- {}: {}", entry.id, entry.reason);
                }
            }
            return;
        }

        if self.readiness.status() == StartReadinessStatus::Blocked {
            println!(
                "Blocking escalations: {} blocking, {} non-blocking at {}.",
                self.escalation.blocking_count,
                self.escalation.non_blocking_count,
                self.escalation.path
            );
            for entry in
                self.escalation.entries.iter().filter(|entry| {
                    matches!(entry.kind, escalation_state::EscalationKind::Blocking)
                })
            {
                println!("- {}: {}", entry.id, entry.reason);
            }
        }

        let blocking_alerts: Vec<_> = self
            .alerts
            .iter()
            .filter(|a| !matches!(a.severity, StartAlertSeverity::Info))
            .collect();
        if !blocking_alerts.is_empty() {
            println!("Blocking alerts (warnings also block in `--check`):");
            for alert in &blocking_alerts {
                println!(
                    "- [{}] {}: {}",
                    alert.severity.upper_label(),
                    alert.check,
                    alert.message
                );
            }
        }
        let info_alerts: Vec<_> = self
            .alerts
            .iter()
            .filter(|a| matches!(a.severity, StartAlertSeverity::Info))
            .collect();
        if !info_alerts.is_empty() {
            println!("Informational:");
            for alert in &info_alerts {
                println!(
                    "- [{}] {}: {}",
                    alert.severity.upper_label(),
                    alert.check,
                    alert.message
                );
            }
        }
    }
}

impl StartCheckReadiness {
    fn ok(self) -> bool {
        matches!(self, Self::Ready)
    }

    fn exit_code(self) -> u8 {
        match self {
            Self::Ready => 0,
            Self::Blocked => START_CHECK_EXIT_BLOCKED_ESCALATION,
            Self::NotReady => START_CHECK_EXIT_NOT_READY,
        }
    }

    fn note(self) -> &'static str {
        match self {
            Self::Ready => {
                "No start alerts are active; the workspace-local session context is ready to load."
            }
            Self::Blocked => "Unresolved blocking escalation prevents readiness.",
            Self::NotReady => {
                "One or more start alerts are active; `start --check` is intentionally fail-closed, so warnings also block until the workspace state is reconciled."
            }
        }
    }

    fn status(self) -> StartReadinessStatus {
        match self {
            Self::Ready => StartReadinessStatus::Ready,
            Self::Blocked => StartReadinessStatus::Blocked,
            Self::NotReady => StartReadinessStatus::NotReady,
        }
    }

    fn status_label(self) -> &'static str {
        match self {
            Self::Ready => "ready",
            Self::Blocked => "blocked",
            Self::NotReady => "not_ready",
        }
    }

    fn view(self) -> StartReadinessView {
        StartReadinessView {
            status: self.status(),
            exit_code: self.exit_code(),
            note: self.note(),
        }
    }
}

impl Serialize for StartCheckReadiness {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.view().serialize(serializer)
    }
}

impl StartAlertSeverity {
    fn upper_label(self) -> &'static str {
        match self {
            Self::Info => "INFO",
            Self::Warning => "WARN",
            Self::Error => "ERROR",
        }
    }
}

fn start_operator_prompt(report: &StartReport) -> String {
    if let Some(title) = operator_focus_label(&report.compact_summary.current_state.title) {
        return operator_question(&title);
    }

    if let Some(step) = report
        .compact_summary
        .next_focus
        .continuity_immediate_actions
        .first()
    {
        return operator_question(step);
    }

    if let Some(anchor) = report
        .compact_summary
        .next_focus
        .execution_gate_anchor
        .as_ref()
    {
        return operator_question(anchor);
    }

    operator_question(&report.session_boundary.summary)
}

fn start_operator_attention(report: &StartReport) -> Option<String> {
    if report.escalation.blocking_count > 0 {
        let reasons = report
            .escalation
            .entries
            .iter()
            .filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
            .map(|entry| entry.reason.trim())
            .filter(|reason| !reason.is_empty())
            .take(2)
            .collect::<Vec<_>>();
        if !reasons.is_empty() {
            return Some(reasons.join(" "));
        }
    }

    for check in [
        "session_ownership_conflict",
        "session_lifecycle_conflict",
        "checkout_state",
    ] {
        if let Some(message) = report
            .alerts
            .iter()
            .find(|alert| alert.check == check)
            .map(|alert| alert.message.trim())
            .filter(|message| !message.is_empty())
        {
            return Some(message.to_owned());
        }
    }

    if let Some(warning) = report
        .activation
        .as_ref()
        .and_then(|activation| match activation.lifecycle() {
            session_state::SessionLifecycle::Interactive => None,
            session_state::SessionLifecycle::Autonomous => activation.warning_message(),
        })
        .map(str::trim)
        .filter(|warning| !warning.is_empty())
    {
        return Some(warning.to_owned());
    }

    match report.session_boundary.action {
        SessionBoundaryAction::Continue => None,
        SessionBoundaryAction::Refresh | SessionBoundaryAction::Stop => {
            Some(report.session_boundary.summary.clone())
        }
        SessionBoundaryAction::WrapUp => Some(report.session_boundary.summary.clone()),
    }
}

fn operator_focus_label(value: &str) -> Option<String> {
    let trimmed = value.trim();
    if trimmed.is_empty() {
        return None;
    }

    Some(
        trimmed
            .strip_prefix("Next Session:")
            .or_else(|| trimmed.strip_prefix("Next:"))
            .unwrap_or(trimmed)
            .trim()
            .to_owned(),
    )
}

fn operator_question(value: &str) -> String {
    let trimmed = value.trim().trim_end_matches(['.', '!', '?']);
    if trimmed.is_empty() {
        return "continue?".to_owned();
    }
    format!("{trimmed}?")
}

pub fn run(repo_root: &Path, explicit_profile: Option<&str>, refresh: bool) -> Result<StartReport> {
    let _span = info_span!("start").entered();
    let core = resolve_core(repo_root, explicit_profile, refresh)?;
    build_start_report_from_core(repo_root, core)
}

pub fn run_activated(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    start_options: session_state::SessionStartOptions,
) -> Result<StartReport> {
    let _span = info_span!("start").entered();
    let (core, activation) =
        resolve_core_activated(repo_root, explicit_profile, refresh, start_options)?;
    build_start_report_from_core(repo_root, core).map(|report| report.with_activation(activation))
}

fn build_start_report_from_core(repo_root: &Path, core: ResolvedCore) -> Result<StartReport> {
    handoff::export::cleanup_legacy_markdown(&core.layout)?;
    let disposition = derive_disposition(&core);
    debug!(?disposition, "start disposition determined");
    let compact_summary = build_compact_summary(&core);
    let session_boundary = build_session_boundary(&core, disposition);
    let agent_readiness = build_start_agent_readiness(repo_root, &core);

    // Lazy migration: consolidate legacy config files into config.toml.
    let _ = config_migration::migrate_repo_overlay_if_needed(&core.layout, &core.locality_id);

    let manifest = resolve_manifest(repo_root, &core.layout, &core.locality_id)?;
    let pod_manifest = core
        .active_pod_identity
        .as_ref()
        .map(|identity| read_text_source("pod_manifest", &identity.manifest_path))
        .transpose()?;
    let policy_sources = policy_projection::read_policy_sources(
        &core.layout,
        &core.locality_id,
        core.active_pod_identity.as_ref(),
    )?;
    let effective_policy = render_effective_policy_view(&policy_sources);
    let policy_projection = policy_projection::build_view(
        &policy_sources,
        policy_projection::PolicySessionContext::from_start_view(
            core.loaded_session.view.mode,
            &core.loaded_session.view.lifecycle,
        ),
        &core.escalation,
    );
    let effective_memory = render_effective_memory_view(
        EffectiveMemorySurfaces {
            profile: &core.sources.profile_memory,
            locality: &core.sources.locality_memory,
            branch: &core.sources.branch_memory,
            clone: &core.sources.clone_memory,
        },
        runtime_state::structured_memory_view(&core.sources),
        core.compiled_state.store.pod_identity_active,
        &core.compiled_state.store.effective_memory,
    );
    let handoff_view = into_compiled_handoff_view(
        &core.sources.handoff,
        compiled_state::render_handoff_content(&core.compiled_state.store.handoff),
    );

    let mut sources = manifest.project_truth_sources;
    if let Some(source) = pod_manifest {
        sources.push(into_source_view(source));
    }
    sources.push(runtime_source_view(&core.sources.profile_memory));
    sources.push(runtime_source_view(&core.sources.locality_memory));
    sources.push(runtime_source_view(&core.sources.branch_memory));
    sources.push(runtime_source_view(&core.sources.clone_memory));
    if core.validation_profile.status != "missing" {
        sources.push(validation_profile_source_view(&core.validation_profile));
    }
    sources.push(runtime_source_view(&core.sources.handoff));
    sources.push(execution_gates_source_view(
        &core.execution_gates.view,
        core.execution_gates.raw.as_ref(),
    )?);
    sources.push(session_state_source_view(
        &core.loaded_session.view,
        core.loaded_session.raw.as_ref(),
    )?);
    sources.push(SourceView {
        kind: "escalation_state",
        path: core.escalation.path.clone(),
        status: core.escalation.status,
        content: None,
    });
    sources.push(SourceView {
        kind: "compiled_state",
        path: core.compiled_state.surface.path.clone(),
        status: core.compiled_state.surface.status,
        content: None,
    });

    Ok(StartReport {
        command: "start",
        ok: true,
        decisions: start_decisions(disposition, &session_boundary, &core.alerts),
        agent_readiness,
        disposition,
        session_boundary,
        path: repo_root.display().to_string(),
        profile: core.profile,
        project_id: core.locality_id.clone(),
        workspace_path: repo_root.display().to_string(),
        work_stream: output::work_stream_view(core.git.as_ref()),
        locality_id: core.locality_id,
        session_id: core.session_id,
        compact_summary,
        source_order: manifest.source_order,
        manifest: manifest.manifest,
        machine_identity: core.machine_identity,
        machine_presence: core.machine_presence,
        execution_context: core.execution_context,
        takeover_preconditions: core.takeover_preconditions,
        coordination_scope: core.coordination_scope,
        sources,
        effective_policy,
        policy_projection,
        effective_memory,
        memory_recall: core.memory_recall,
        memory_provider: core.memory_provider,
        startup_recall: core.startup_recall,
        execution_gates: core.execution_gates.view,
        handoff: handoff_view,
        recovery: runtime_state::recovery_view(&core.recovery),
        session_state: core.loaded_session.view,
        activation: None,
        escalation: core.escalation,
        compiled_state: core.compiled_state.surface,
        alerts: core.alerts,
        refresh_actions: core.refresh_actions,
        warnings: core.warnings,
    })
}

pub fn run_check(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
) -> Result<StartCheckReport> {
    let core = resolve_core(repo_root, explicit_profile, refresh)?;
    let disposition = derive_disposition(&core);
    let readiness = start_readiness_view(&core.alerts);
    let agent_readiness = build_start_agent_readiness(repo_root, &core);

    Ok(StartCheckReport {
        command: "start",
        ok: readiness.ok(),
        disposition,
        path: repo_root.display().to_string(),
        profile: core.profile,
        project_id: core.locality_id.clone(),
        workspace_path: repo_root.display().to_string(),
        work_stream: output::work_stream_view(core.git.as_ref()),
        locality_id: core.locality_id,
        mode: "check",
        readiness,
        agent_readiness,
        escalation: core.escalation,
        alerts: core.alerts,
        refresh_actions: core.refresh_actions,
        warnings: core.warnings,
    })
}

fn build_start_agent_readiness(
    repo_root: &Path,
    core: &ResolvedCore,
) -> agent_readiness::AgentReadinessSummary {
    let diagnostics = agent_readiness::build_agent_diagnostics(Some(&core.layout.state_db_path()));
    let (session_readiness, heartbeat_readiness) =
        agent_readiness::session_readiness_for_layout(Some(&core.layout));
    let readiness = start_readiness_view(&core.alerts);
    agent_readiness::build_summary(
        repo_root,
        // `resolve_core` has already loaded a supported repo marker; if marker
        // validation fails, `start` exits before this summary is constructed.
        agent_readiness::MarkerSchemaStatus::Valid,
        &diagnostics,
        session_readiness,
        heartbeat_readiness,
        start_blocking_failures(&core.alerts, readiness),
        start_non_blocking_warnings(&core.alerts, readiness),
    )
}

fn start_blocking_failures(
    alerts: &[StartAlert],
    readiness: StartCheckReadiness,
) -> Vec<agent_readiness::AgentReadinessFinding> {
    alerts
        .iter()
        .filter(|alert| {
            matches!(alert.severity, StartAlertSeverity::Error)
                || (matches!(readiness, StartCheckReadiness::Blocked)
                    && alert.check == "escalation_state")
        })
        .map(|alert| start_alert_finding(alert, agent_readiness::FindingSeverity::Error))
        .collect()
}

fn start_non_blocking_warnings(
    alerts: &[StartAlert],
    readiness: StartCheckReadiness,
) -> Vec<agent_readiness::AgentReadinessFinding> {
    alerts
        .iter()
        .filter(|alert| {
            matches!(alert.severity, StartAlertSeverity::Warning)
                && !(matches!(readiness, StartCheckReadiness::Blocked)
                    && alert.check == "escalation_state")
        })
        .map(|alert| match alert.severity {
            StartAlertSeverity::Info => {
                start_alert_finding(alert, agent_readiness::FindingSeverity::Info)
            }
            StartAlertSeverity::Warning => {
                start_alert_finding(alert, agent_readiness::FindingSeverity::Warning)
            }
            StartAlertSeverity::Error => {
                start_alert_finding(alert, agent_readiness::FindingSeverity::Error)
            }
        })
        .collect()
}

fn start_alert_finding(
    alert: &StartAlert,
    severity: agent_readiness::FindingSeverity,
) -> agent_readiness::AgentReadinessFinding {
    agent_readiness::AgentReadinessFinding::new(alert.check, severity, alert.message.clone(), None)
}

/// Fast path: skip manifest resolution, policy reads, provider diagnostics,
/// provider recall, and all rendered views.
/// `compact_summary` still stays populated from compiled/runtime state so
/// callers can orient quickly without loading the full authored-path surfaces.
/// Fields that require source rendering are populated with `"skipped"` status
/// defaults. Dispatchers route provider/alert fields through the full resolver;
/// callers using this function directly must strip skipped fields before the
/// output reaches a user.
pub fn run_compiled_only(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
) -> Result<StartReport> {
    let core = resolve_compiled_only_core(repo_root, explicit_profile, refresh)?;
    build_compiled_only_report_from_core(repo_root, core, None)
}

pub fn run_compiled_only_activated(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    start_options: session_state::SessionStartOptions,
) -> Result<StartReport> {
    let (core, activation) =
        resolve_compiled_only_core_activated(repo_root, explicit_profile, refresh, start_options)?;
    build_compiled_only_report_from_core(repo_root, core, Some(activation))
}

fn build_compiled_only_report_from_core(
    repo_root: &Path,
    core: ResolvedCore,
    activation: Option<session_state::SessionStateStartReport>,
) -> Result<StartReport> {
    handoff::export::cleanup_legacy_markdown(&core.layout)?;
    let disposition = derive_disposition(&core);
    let compact_summary = build_compact_summary(&core);
    let session_boundary = build_session_boundary(&core, disposition);
    let agent_readiness = build_start_agent_readiness(repo_root, &core);

    Ok(StartReport {
        command: "start",
        ok: true,
        decisions: start_decisions(disposition, &session_boundary, &core.alerts),
        agent_readiness,
        disposition,
        session_boundary,
        path: repo_root.display().to_string(),
        profile: core.profile,
        project_id: core.locality_id.clone(),
        workspace_path: repo_root.display().to_string(),
        work_stream: output::work_stream_view(core.git.as_ref()),
        locality_id: core.locality_id,
        session_id: core.session_id,
        compact_summary,
        source_order: "manifest",
        manifest: ManifestView {
            path: String::new(),
            status: "skipped",
            entries: Vec::new(),
        },
        machine_identity: core.machine_identity,
        machine_presence: core.machine_presence,
        execution_context: core.execution_context,
        takeover_preconditions: core.takeover_preconditions,
        coordination_scope: core.coordination_scope,
        sources: Vec::new(),
        effective_policy: RenderedView {
            status: "skipped",
            content: String::new(),
            parts: Vec::new(),
        },
        policy_projection: policy_projection::skipped_view(),
        effective_memory: MemoryView {
            status: "skipped",
            content: String::new(),
            parts: Vec::new(),
            structured: memory_entries::StructuredMemoryView {
                status: "skipped",
                profile_entries: Vec::new(),
                repo_entries: Vec::new(),
                branch_entries: Vec::new(),
                clone_entries: Vec::new(),
                diagnostics: Vec::new(),
            },
        },
        memory_recall: memory_recall::StartRecallView::skipped(),
        memory_provider: core.memory_provider,
        startup_recall: core.startup_recall,
        execution_gates: core.execution_gates.view,
        handoff: HandoffView {
            path: String::new(),
            status: "skipped",
            content: String::new(),
        },
        recovery: runtime_state::recovery_view(&core.recovery),
        session_state: core.loaded_session.view,
        activation,
        escalation: core.escalation,
        compiled_state: core.compiled_state.surface,
        alerts: core.alerts,
        refresh_actions: core.refresh_actions,
        warnings: core.warnings,
    })
}

pub(crate) fn build_host_context(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    mode: HostContextMode,
) -> Result<HostContextPayload> {
    let core = resolve_core(repo_root, explicit_profile, refresh)?;
    let disposition = derive_disposition(&core);
    let session_boundary = build_session_boundary(&core, disposition);
    let bundle = compiled_state::bundle_view_from_store(&core.compiled_state.store);
    let mut sections = Vec::new();
    sections.push(ContextSectionSpec {
        name: "task",
        inclusion_reason: "always_include_primary_task",
        value: Value::String(bundle.task.clone()),
    });

    if matches!(mode, HostContextMode::Startup) {
        let state = host_context_state_lines(&core);
        if !state.is_empty() {
            sections.push(ContextSectionSpec {
                name: "state",
                inclusion_reason: "startup_only_checkout_orientation",
                value: serde_json::to_value(state)?,
            });
        }
    }

    let next_focus = host_context_next_focus(&bundle);
    if !next_focus.is_empty() {
        sections.push(ContextSectionSpec {
            name: "next_focus",
            inclusion_reason: "active_immediate_actions_and_focus_sections",
            value: serde_json::to_value(next_focus)?,
        });
    }

    let remember_selection = host_context_remember_selection(&core);
    let remember = remember_selection
        .as_ref()
        .map(host_context_remember_items)
        .unwrap_or_default();
    if !remember.is_empty() {
        sections.push(ContextSectionSpec {
            name: "remember",
            inclusion_reason: "ranked_memory_selection",
            value: serde_json::to_value(remember)?,
        });
    }

    let active_guardrails = host_context_section_items(
        &bundle,
        compiled_state::BundleSectionKind::Rules,
        HOST_CONTEXT_GUARDRAILS_LIMIT,
        HOST_CONTEXT_GUARDRAILS_MAX_CHARS,
    );
    if !active_guardrails.is_empty() {
        sections.push(ContextSectionSpec {
            name: "active_guardrails",
            inclusion_reason: "active_operational_guardrails",
            value: serde_json::to_value(active_guardrails)?,
        });
    }

    let mut context = serde_json::Map::new();
    for section in &sections {
        context.insert(section.name.to_owned(), section.value.clone());
    }

    Ok(HostContextPayload {
        session_boundary,
        context: Value::Object(context),
        sections,
        remember_selection,
        source_fingerprint: bundle.source_fingerprint,
        session: ContextSessionSnapshot {
            session_id: core.loaded_session.view.session_id.clone(),
            started_at_epoch_s: core.loaded_session.view.started_at_epoch_s,
            last_started_at_epoch_s: core.loaded_session.view.last_started_at_epoch_s,
            start_count: core.loaded_session.view.start_count,
        },
        alerts: core.alerts,
    })
}

pub(crate) fn build_host_operator_summary(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    carryover_alerts: &[StartAlert],
) -> Result<HostOperatorSummary> {
    let mut core = resolve_compiled_only_core(repo_root, explicit_profile, refresh)?;
    append_provider_carryover_alerts(&mut core.alerts, carryover_alerts);
    let disposition = derive_disposition(&core);
    let session_boundary = build_session_boundary(&core, disposition);
    let compact_summary = build_compact_summary(&core);
    let recovery = runtime_state::recovery_view(&core.recovery);

    Ok(HostOperatorSummary {
        session_boundary,
        disposition,
        compact_summary,
        recovery,
        escalation: core.escalation,
        alerts: core.alerts,
    })
}

fn append_provider_carryover_alerts(alerts: &mut Vec<StartAlert>, carryover_alerts: &[StartAlert]) {
    for alert in carryover_alerts
        .iter()
        .filter(|alert| alert.is_provider_related())
    {
        if !alerts
            .iter()
            .any(|existing| existing.same_public_content(alert))
        {
            alerts.push(alert.clone());
        }
    }
}

/// Shared setup for `run` and `run_compiled_only`: resolves profile, layout,
/// marker, git state, runtime state, compiled state, session state,
/// validation profile, and alerts.
struct ResolvedCore {
    profile: String,
    locality_id: String,
    layout: StateLayout,
    git: Option<handoff::GitState>,
    sources: runtime_state::RuntimeSourceSurfaces,
    recovery: runtime_state::LoadedRuntimeRecoveryState,
    compiled_state: compiled_state::PreparedCompiledState,
    active_pod_identity: Option<pod_identity::LoadedPodIdentity>,
    machine_identity: pod_identity::MachineIdentityView,
    machine_presence: machine_presence::MachinePresenceView,
    execution_context: machine_presence::MachineExecutionContextView,
    takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
    coordination_scope: pod_identity::CoordinationScopeView,
    memory_recall: memory_recall::StartRecallView,
    memory_provider: memory_provider::MemoryProviderView,
    startup_recall: memory_provider::StartupRecallView,
    loaded_session: LoadedSessionState,
    execution_gates: LoadedExecutionGates,
    validation_profile: validation_profile::LoadedValidationProfile,
    session_id: Option<String>,
    escalation: escalation_state::EscalationView,
    alerts: Vec<StartAlert>,
    refresh_actions: Vec<RefreshAction>,
    warnings: Vec<String>,
}

struct WorkspacePhase {
    profile: String,
    locality_id: String,
    layout: StateLayout,
    git: Option<handoff::GitState>,
    session_id: Option<String>,
    refresh_actions: Vec<RefreshAction>,
    checkout_continuity_advisory: Option<CheckoutContinuityAdvisory>,
}

struct IdentityPhase {
    active_pod_identity: Option<pod_identity::LoadedPodIdentity>,
    machine_identity: pod_identity::MachineIdentityView,
    coordination_scope: pod_identity::CoordinationScopeView,
}

struct SessionPhase {
    loaded_session: LoadedSessionState,
    execution_gates: LoadedExecutionGates,
    escalation: escalation_state::EscalationView,
    machine_presence: machine_presence::MachinePresenceView,
    execution_context: machine_presence::MachineExecutionContextView,
    takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
}

struct RuntimePhase {
    sources: runtime_state::RuntimeSourceSurfaces,
    recovery: runtime_state::LoadedRuntimeRecoveryState,
    compiled_state: compiled_state::PreparedCompiledState,
    validation_profile: validation_profile::LoadedValidationProfile,
    memory_provider_inspection: memory_provider::MemoryProviderInspection,
    startup_recall: memory_provider::StartupRecallView,
    memory_recall: memory_recall::StartRecallView,
}

struct CompiledOnlyRuntimePhase {
    sources: runtime_state::RuntimeSourceSurfaces,
    recovery: runtime_state::LoadedRuntimeRecoveryState,
    compiled_state: compiled_state::PreparedCompiledState,
    validation_profile: validation_profile::LoadedValidationProfile,
}

fn resolve_core(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
) -> Result<ResolvedCore> {
    let (core, _) = resolve_core_inner(repo_root, explicit_profile, refresh, None)?;
    Ok(core)
}

fn resolve_compiled_only_core(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
) -> Result<ResolvedCore> {
    let (core, _) = resolve_compiled_only_core_inner(repo_root, explicit_profile, refresh, None)?;
    Ok(core)
}

fn resolve_core_activated(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    start_options: session_state::SessionStartOptions,
) -> Result<(ResolvedCore, session_state::SessionStateStartReport)> {
    let (core, activation) =
        resolve_core_inner(repo_root, explicit_profile, refresh, Some(start_options))?;
    let activation =
        activation.context("activation requested but no activation report was produced")?;
    Ok((core, activation))
}

fn resolve_compiled_only_core_activated(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    start_options: session_state::SessionStartOptions,
) -> Result<(ResolvedCore, session_state::SessionStateStartReport)> {
    let (core, activation) = resolve_compiled_only_core_inner(
        repo_root,
        explicit_profile,
        refresh,
        Some(start_options),
    )?;
    let activation =
        activation.context("activation requested but no activation report was produced")?;
    Ok((core, activation))
}

fn resolve_core_inner(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    activation: Option<session_state::SessionStartOptions>,
) -> Result<(ResolvedCore, Option<session_state::SessionStateStartReport>)> {
    let workspace = resolve_workspace_phase(repo_root, explicit_profile, refresh)?;
    let identity = resolve_identity_phase(&workspace.layout, &workspace.locality_id)?;
    // Activation writes session state only; run it before loading the session
    // phase so the single start report reflects the active lifecycle record.
    let activation = activation
        .map(|options| {
            session_state::start(
                repo_root,
                explicit_profile,
                Some(workspace.locality_id.as_str()),
                options,
            )
        })
        .transpose()?;
    let session = resolve_session_phase(&workspace, &identity)?;
    let runtime = resolve_runtime_phase(repo_root, &workspace, &identity)?;
    let (alerts, warnings) =
        build_start_alerts(repo_root, &workspace, &identity, &session, &runtime)?;
    let session_id = activation
        .as_ref()
        .and_then(session_state::SessionStateStartReport::session_id)
        .map(str::to_owned)
        .or(workspace.session_id);

    Ok((
        ResolvedCore {
            profile: workspace.profile,
            locality_id: workspace.locality_id,
            layout: workspace.layout,
            git: workspace.git,
            sources: runtime.sources,
            recovery: runtime.recovery,
            compiled_state: runtime.compiled_state,
            active_pod_identity: identity.active_pod_identity,
            machine_identity: identity.machine_identity,
            machine_presence: session.machine_presence,
            execution_context: session.execution_context,
            takeover_preconditions: session.takeover_preconditions,
            coordination_scope: identity.coordination_scope,
            memory_recall: runtime.memory_recall,
            memory_provider: runtime.memory_provider_inspection.view,
            startup_recall: runtime.startup_recall,
            loaded_session: session.loaded_session,
            execution_gates: session.execution_gates,
            validation_profile: runtime.validation_profile,
            session_id,
            escalation: session.escalation,
            alerts,
            refresh_actions: workspace.refresh_actions,
            warnings,
        },
        activation,
    ))
}

fn resolve_compiled_only_core_inner(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
    activation: Option<session_state::SessionStartOptions>,
) -> Result<(ResolvedCore, Option<session_state::SessionStateStartReport>)> {
    let workspace = resolve_workspace_phase(repo_root, explicit_profile, refresh)?;
    let identity = resolve_identity_phase(&workspace.layout, &workspace.locality_id)?;
    // Activation writes session state only; run it before loading the session
    // phase so the provider-light report reflects the active lifecycle record.
    let activation = activation
        .map(|options| {
            session_state::start(
                repo_root,
                explicit_profile,
                Some(workspace.locality_id.as_str()),
                options,
            )
        })
        .transpose()?;
    let session = resolve_session_phase(&workspace, &identity)?;
    let runtime = resolve_compiled_only_runtime_phase(repo_root, &workspace, &identity)?;
    let (alerts, warnings) = build_compiled_only_start_alerts(
        repo_root,
        &workspace,
        &session,
        &runtime.validation_profile,
        &runtime.compiled_state,
        &runtime.recovery,
    )?;
    let session_id = activation
        .as_ref()
        .and_then(session_state::SessionStateStartReport::session_id)
        .map(str::to_owned)
        .or(workspace.session_id);

    Ok((
        ResolvedCore {
            profile: workspace.profile,
            locality_id: workspace.locality_id,
            layout: workspace.layout,
            git: workspace.git,
            sources: runtime.sources,
            recovery: runtime.recovery,
            compiled_state: runtime.compiled_state,
            active_pod_identity: identity.active_pod_identity,
            machine_identity: identity.machine_identity,
            machine_presence: session.machine_presence,
            execution_context: session.execution_context,
            takeover_preconditions: session.takeover_preconditions,
            coordination_scope: identity.coordination_scope,
            memory_recall: memory_recall::StartRecallView::skipped(),
            memory_provider: skipped_memory_provider_view(),
            startup_recall: skipped_startup_recall_view(),
            loaded_session: session.loaded_session,
            execution_gates: session.execution_gates,
            validation_profile: runtime.validation_profile,
            session_id,
            escalation: session.escalation,
            alerts,
            refresh_actions: workspace.refresh_actions,
            warnings,
        },
        activation,
    ))
}

fn resolve_workspace_phase(
    repo_root: &Path,
    explicit_profile: Option<&str>,
    refresh: bool,
) -> Result<WorkspacePhase> {
    let profile = profile::resolve(explicit_profile)?;
    let layout = StateLayout::resolve(repo_root, profile.clone())?;
    ensure_profile_exists(&layout)?;
    debug!(profile = %profile, "profile resolved");

    let marker = repo_marker::load(repo_root)?.ok_or_else(|| {
        let attach_command = if layout.profile().as_str() == profile::DEFAULT_PROFILE {
            format!("ccd attach --path {}", repo_root.display())
        } else {
            format!(
                "ccd attach --path {} --profile {}",
                repo_root.display(),
                layout.profile()
            )
        };
        let link_command = if layout.profile().as_str() == profile::DEFAULT_PROFILE {
            format!("ccd link --path {}", repo_root.display())
        } else {
            format!(
                "ccd link --path {} --profile {}",
                repo_root.display(),
                layout.profile()
            )
        };
        anyhow::anyhow!(
            "ccd start cannot load policy, memory, handoff, or dispatch state because this workspace is not linked: {} is missing. Run `{attach_command}` to bootstrap a new project overlay, or `{link_command}` to reconnect to an existing one.",
            repo_root.join(repo_marker::MARKER_FILE).display(),
        )
    })?;
    let locality_id = marker.locality_id;
    ensure_repo_registry_exists(&layout, &locality_id)?;
    debug!(locality_id = %locality_id, "repo linked");

    let clone_profile_root = layout.clone_profile_root();
    fs::create_dir_all(&clone_profile_root).with_context(|| {
        format!(
            "failed to create directory {}",
            clone_profile_root.display()
        )
    })?;

    let session_id = session_state::load_session_id(&layout)?;
    let _ = refresh;
    let refresh_actions = Vec::new();
    let git = if layout.resolved_substrate().is_git() {
        Some(handoff::read_git_state(
            repo_root,
            BranchMode::AllowDetachedHead,
        )?)
    } else {
        None
    };
    let checkout_continuity_advisory = git
        .as_ref()
        .and_then(|git| handoff::checkout_continuity_advisory(repo_root, git));

    Ok(WorkspacePhase {
        profile: profile.to_string(),
        locality_id,
        layout,
        git,
        session_id,
        refresh_actions,
        checkout_continuity_advisory,
    })
}

fn resolve_identity_phase(layout: &StateLayout, locality_id: &str) -> Result<IdentityPhase> {
    let active_pod_identity = pod_identity::resolve_active_identity(layout)?;
    let machine_identity = pod_identity::resolve_machine_identity_view(layout)?;
    let coordination_scope = pod_identity::resolve_coordination_scope_view(layout, locality_id)?;

    Ok(IdentityPhase {
        active_pod_identity,
        machine_identity,
        coordination_scope,
    })
}

fn resolve_session_phase(
    workspace: &WorkspacePhase,
    identity: &IdentityPhase,
) -> Result<SessionPhase> {
    let loaded_session = load_session_state(&workspace.layout)?;
    let execution_gates = load_execution_gates(&workspace.layout)?;
    let escalation_entries = escalation_state::load_for_layout(&workspace.layout)?;
    let escalation = escalation_state::build_view(&workspace.layout, &escalation_entries);
    let machine_presence = machine_presence::resolve_machine_presence_view(&workspace.layout)?;
    let execution_context = machine_presence::build_execution_context_view(
        &workspace.layout,
        Some(&workspace.locality_id),
        &identity.machine_identity,
        &machine_presence,
        Some(&loaded_session.view.lifecycle),
    );
    let takeover_preconditions = machine_presence::build_takeover_preconditions_view(
        &execution_context,
        Some(&loaded_session.view.lifecycle),
        escalation.blocking_count > 0,
        escalation.blocking_count,
        true,
    );

    Ok(SessionPhase {
        loaded_session,
        execution_gates,
        escalation,
        machine_presence,
        execution_context,
        takeover_preconditions,
    })
}

fn resolve_runtime_phase(
    repo_root: &Path,
    workspace: &WorkspacePhase,
    identity: &IdentityPhase,
) -> Result<RuntimePhase> {
    let raw = runtime_state::load_raw_start_runtime_sources(
        repo_root,
        &workspace.layout,
        &workspace.locality_id,
        identity.active_pod_identity.is_some(),
    )?;
    debug!("runtime state loaded");
    let validation_profile =
        validation_profile::load_for_layout(&workspace.layout, &workspace.locality_id)?;
    // Skip live provider health check during normal startup (#628).
    // Provider health is surfaced as `not_checked` here; `ccd doctor` remains
    // the live fail-closed health-check surface.
    let memory_provider_inspection = memory_provider::inspect_provider(
        repo_root,
        &workspace.layout,
        &workspace.locality_id,
        false,
    )?;
    let start_result = compiled_state::ensure_for_start(&workspace.layout, raw)?;
    debug!("compiled state ready");
    let compiled_state = start_result.compiled;
    let sources = start_result.sources;
    let recovery = start_result.recovery;
    let mut recall_setup =
        memory_provider::prepare_start_recall(&workspace.layout, repo_root, &workspace.locality_id);
    let memory_recall = if let Some(error) = recall_setup.config_error.clone() {
        memory_recall::StartRecallView {
            status: "error",
            configured_provider: None,
            configured_provider_kind: None,
            used_provider: None,
            used_provider_kind: None,
            fallback_used: false,
            query: None,
            budget: recall_setup.budget,
            search_results: Vec::new(),
            described_results: Vec::new(),
            expanded_results: Vec::new(),
            error: Some(error),
            warnings: Vec::new(),
        }
    } else if let Some(provider) = recall_setup.provider.as_mut() {
        let fallback_provider = if provider.is_builtin_markdown() {
            None
        } else {
            Some(&mut recall_setup.fallback as &mut dyn memory_recall::RecallProvider)
        };
        memory_recall::run_start_recall(
            provider,
            fallback_provider,
            memory_recall::build_start_query(
                &compiled_state.store.handoff.title,
                &compiled_state.store.handoff.immediate_actions,
            ),
            recall_setup.budget,
        )
    } else {
        memory_recall::StartRecallView::missing()
    };
    let legacy_startup_query = memory_provider::startup_recall_query(
        &compiled_state.store.handoff.title,
        &compiled_state.store.handoff.immediate_actions,
    );
    let startup_recall = memory_provider::startup_recall_from_start_recall(
        &memory_provider_inspection,
        &memory_recall,
        legacy_startup_query,
    );

    Ok(RuntimePhase {
        sources,
        recovery,
        compiled_state,
        validation_profile,
        memory_provider_inspection,
        startup_recall,
        memory_recall,
    })
}

fn resolve_compiled_only_runtime_phase(
    repo_root: &Path,
    workspace: &WorkspacePhase,
    identity: &IdentityPhase,
) -> Result<CompiledOnlyRuntimePhase> {
    let raw = runtime_state::load_raw_start_runtime_sources(
        repo_root,
        &workspace.layout,
        &workspace.locality_id,
        identity.active_pod_identity.is_some(),
    )?;
    debug!("compiled-only runtime state loaded");
    let validation_profile =
        validation_profile::load_for_layout(&workspace.layout, &workspace.locality_id)?;
    let start_result = compiled_state::ensure_for_start(&workspace.layout, raw)?;
    debug!("compiled-only state ready");

    Ok(CompiledOnlyRuntimePhase {
        sources: start_result.sources,
        recovery: start_result.recovery,
        compiled_state: start_result.compiled,
        validation_profile,
    })
}

fn build_start_alerts(
    repo_root: &Path,
    workspace: &WorkspacePhase,
    _identity: &IdentityPhase,
    session: &SessionPhase,
    runtime: &RuntimePhase,
) -> Result<(Vec<StartAlert>, Vec<String>)> {
    let mut alerts = workspace
        .refresh_actions
        .iter()
        .filter(|action| action.status == "failed")
        .map(|action| StartAlert::warning(action.target, action.message.clone()))
        .collect::<Vec<_>>();
    for issue in &runtime.memory_provider_inspection.issues {
        alerts.push(StartAlert::warning(issue.check, issue.message.clone()));
    }
    if runtime.startup_recall.status == "fallback" {
        if let Some(message) = &runtime.startup_recall.message {
            alerts.push(StartAlert::warning("memory_provider", message.clone()));
        }
    }
    if let Some(error) = runtime.memory_recall.error.as_deref() {
        alerts.push(StartAlert::warning(
            "memory_provider",
            format!("memory recall provider failed during start recall: {error}"),
        ));
    }
    for warning in &runtime.memory_recall.warnings {
        alerts.push(StartAlert::warning("memory_provider", warning.clone()));
    }
    push_common_start_alerts(
        repo_root,
        workspace,
        session,
        &runtime.validation_profile,
        &runtime.compiled_state,
        &runtime.recovery,
        &mut alerts,
    )?;
    let warnings = start_alert_warnings(&alerts);

    Ok((alerts, warnings))
}

fn build_compiled_only_start_alerts(
    repo_root: &Path,
    workspace: &WorkspacePhase,
    session: &SessionPhase,
    validation_profile: &validation_profile::LoadedValidationProfile,
    compiled_state: &compiled_state::PreparedCompiledState,
    recovery: &runtime_state::LoadedRuntimeRecoveryState,
) -> Result<(Vec<StartAlert>, Vec<String>)> {
    let mut alerts = workspace
        .refresh_actions
        .iter()
        .filter(|action| action.status == "failed")
        .map(|action| StartAlert::warning(action.target, action.message.clone()))
        .collect::<Vec<_>>();
    push_common_start_alerts(
        repo_root,
        workspace,
        session,
        validation_profile,
        compiled_state,
        recovery,
        &mut alerts,
    )?;
    let warnings = start_alert_warnings(&alerts);

    Ok((alerts, warnings))
}

fn push_common_start_alerts(
    repo_root: &Path,
    workspace: &WorkspacePhase,
    session: &SessionPhase,
    validation_profile: &validation_profile::LoadedValidationProfile,
    compiled_state: &compiled_state::PreparedCompiledState,
    recovery: &runtime_state::LoadedRuntimeRecoveryState,
    alerts: &mut Vec<StartAlert>,
) -> Result<()> {
    if let Some(alert) = StartSessionAssessment::from_view(&session.loaded_session.view)
        .into_alert(&session.loaded_session.view)
    {
        alerts.push(alert);
    }
    if validation_profile.status == "invalid" {
        alerts.push(StartAlert::error(
            "repo_validation_profile",
            format!(
                "failed to load {}: {}",
                validation_profile.path.display(),
                validation_profile
                    .error
                    .clone()
                    .unwrap_or_else(|| "invalid validation profile".to_owned())
            ),
        ));
    }
    let current_digests = compiled_state
        .store
        .projection_digests
        .clone()
        .unwrap_or_else(|| compiled_state::compute_projection_digests(&compiled_state.store));
    let latest_projection_observation =
        consistency::load_latest_projection_observation(&workspace.layout)?;
    let compiled_handoff = compiled_handoff_state(&compiled_state.store.handoff);
    let consistency = consistency::evaluate_with_inputs(consistency::ConsistencyInputs {
        repo_root,
        layout: &workspace.layout,
        locality_id: &workspace.locality_id,
        handoff: &compiled_handoff,
        checkpoint: recovery.state.checkpoint.as_ref(),
        include_checkpoint_references: false,
        source_fingerprint: &compiled_state.store.source_fingerprint,
        current_digests: &current_digests,
        latest_observation: latest_projection_observation.as_ref(),
    })?;
    for axis in consistency.axes {
        if axis.status.is_drift() {
            alerts.push(StartAlert::warning(axis.id, axis.summary));
        }
    }

    if let Some(advisory) = workspace.checkout_continuity_advisory.as_ref() {
        alerts.push(StartAlert::warning(
            "checkout_state",
            advisory.start_alert_message(
                workspace
                    .git
                    .as_ref()
                    .expect("checkout advisory requires git"),
            ),
        ));
    }
    for ext_diag in
        extensions::health_diagnostics(&workspace.layout, repo_root, &workspace.locality_id)?
    {
        alerts.push(StartAlert {
            check: ext_diag.check,
            severity: match ext_diag.severity {
                "error" => StartAlertSeverity::Error,
                "info" => StartAlertSeverity::Info,
                _ => StartAlertSeverity::Warning,
            },
            message: ext_diag.message,
            details: ext_diag.details,
        });
    }
    if let Some(message) = project_truth::legacy_roadmap_exclusion_warning(
        repo_root,
        &workspace.layout,
        &workspace.locality_id,
    )? {
        alerts.push(StartAlert::warning("legacy_roadmap", message));
    }
    if session.escalation.blocking_count > 0 {
        let blocking_reasons = session
            .escalation
            .entries
            .iter()
            .filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
            .map(|entry| entry.reason.clone())
            .collect::<Vec<_>>();
        let reason = match blocking_reasons.as_slice() {
            [] => "no blocking reasons recorded".to_owned(),
            [single] => single.clone(),
            reasons => reasons.join("; "),
        };
        let label = if session.escalation.blocking_count == 1 {
            "blocking escalation"
        } else {
            "blocking escalations"
        };
        alerts.push(StartAlert::warning(
            "escalation_state",
            format!(
                "{} {}: {}",
                session.escalation.blocking_count, label, reason
            ),
        ));
    }

    Ok(())
}

fn start_alert_warnings(alerts: &[StartAlert]) -> Vec<String> {
    alerts
        .iter()
        .filter(|alert| matches!(alert.severity, StartAlertSeverity::Warning))
        .map(|alert| alert.message.clone())
        .collect()
}

fn skipped_memory_provider_view() -> memory_provider::MemoryProviderView {
    memory_provider::MemoryProviderView {
        status: "skipped",
        authoritative_provider: "skipped".to_owned(),
        configured_recall_provider: None,
        effective_recall_provider: memory_provider::ProviderIdentityView {
            name: "skipped".to_owned(),
            kind: "skipped".to_owned(),
            source: "compiled-only".to_owned(),
            capabilities: Vec::new(),
            allowed_scopes: Vec::new(),
        },
        start_recall_policy: memory_provider::StartRecallPolicyView {
            mode: "skipped".to_owned(),
            search_limit: 0,
            preview_chars: 0,
        },
        health: memory_provider::ProviderHealthView {
            status: "not_checked".to_owned(),
            message: "provider inspection skipped by compiled-only start".to_owned(),
            checked_at_epoch_s: None,
        },
        issues: Vec::new(),
        advisory_ingest_provider: memory_provider::AdvisoryIngestProviderView {
            status: "skipped",
            configured_provider: None,
            effective_provider: None,
            sync_status: memory_provider::AdvisoryIngestSyncStatusView {
                status: "not_checked".to_owned(),
                message: "ingest provider inspection skipped by compiled-only start".to_owned(),
                checked_at_epoch_s: None,
                latest_successful_ingest_at_epoch_s: None,
                lag_seconds: None,
                scoped_source_coverage: Vec::new(),
            },
            source_map: memory_provider::AdvisoryIngestSourceMapView {
                status: "not_checked".to_owned(),
                message: "ingest provider source map skipped by compiled-only start".to_owned(),
                sources: Vec::new(),
            },
            issues: Vec::new(),
        },
    }
}

fn skipped_startup_recall_view() -> memory_provider::StartupRecallView {
    memory_provider::StartupRecallView {
        status: "skipped".to_owned(),
        query: None,
        provider: None,
        results: Vec::new(),
        message: Some("provider recall skipped by compiled-only start".to_owned()),
    }
}

fn compiled_handoff_state(
    handoff: &compiled_state::CompiledHandoffView,
) -> runtime_state::RuntimeHandoffState {
    runtime_state::RuntimeHandoffState {
        title: handoff.title.clone(),
        immediate_actions: compiled_handoff_items(&handoff.immediate_actions),
        completed_state: compiled_handoff_items(&handoff.completed_state),
        operational_guardrails: compiled_handoff_items(&handoff.operational_guardrails),
        key_files: compiled_handoff_items(&handoff.key_files),
        definition_of_done: compiled_handoff_items(&handoff.definition_of_done),
    }
}

fn compiled_handoff_items(items: &[String]) -> Vec<runtime_state::RuntimeHandoffItem> {
    items
        .iter()
        .map(|text| runtime_state::RuntimeHandoffItem {
            text: text.clone(),
            lifecycle: runtime_state::RuntimeLifecycle::Active,
        })
        .collect()
}

/// Builds the fast-path summary from state already resolved by `resolve_core`.
/// Keep this infallible unless the command contract changes to allow extra I/O.
fn build_compact_summary(core: &ResolvedCore) -> CompactSummary {
    let current_state_source = if core.git.is_some() {
        "live_git"
    } else {
        "directory_workspace"
    };
    let mut current_system_state =
        handoff::current_system_state_lines(core.git.as_ref(), core.session_id.as_deref());
    if let Some(mode_line) = session_mode_current_state_line(&core.loaded_session.view) {
        current_system_state.push(mode_line);
    }

    CompactSummary {
        current_state: CompactCurrentState {
            source: current_state_source,
            title: core.compiled_state.store.handoff.title.clone(),
            current_system_state,
        },
        next_focus: CompactNextFocus {
            continuity_immediate_actions: core
                .compiled_state
                .store
                .handoff
                .immediate_actions
                .clone(),
            execution_gate_anchor: core
                .execution_gates
                .view
                .attention_anchor
                .as_ref()
                .map(|anchor| format!("[{}] {}", anchor.status.as_str(), anchor.text)),
        },
        active_guardrails: CompactGuardrails {
            operational_guardrails: core
                .compiled_state
                .store
                .handoff
                .operational_guardrails
                .clone(),
            effective_memory: core.compiled_state.store.effective_memory.clone(),
        },
    }
}

fn host_context_state_lines(core: &ResolvedCore) -> Vec<String> {
    let mut lines = Vec::new();
    if let Some(git) = core.git.as_ref() {
        lines.push(format!("branch:{}", git.branch));
        lines.push(format!("head:{}", git.head));
        if !git.clean {
            lines.push("worktree:dirty".to_owned());
        } else {
            lines.push("worktree:clean".to_owned());
        }
        let tracking = match (git.upstream.as_deref(), git.ahead, git.behind) {
            (Some(_), 0, 0) => Some("tracking:in_sync".to_owned()),
            (Some(_), ahead, 0) => Some(format!("tracking:ahead:{ahead}")),
            (Some(_), 0, behind) => Some(format!("tracking:behind:{behind}")),
            (Some(_), ahead, behind) => Some(format!("tracking:diverged:{ahead}:{behind}")),
            (None, _, _) => Some("tracking:none".to_owned()),
        };
        if let Some(tracking) = tracking {
            lines.push(tracking);
        }
    }
    if let Some(mode) = core.loaded_session.view.mode {
        if mode != session_state::SessionMode::General {
            lines.push(format!("session_mode:{}", mode.as_str()));
        }
    }
    lines
}

fn host_context_next_focus(bundle: &compiled_state::BundleProjectionView) -> Vec<String> {
    let mut items = host_context_section_items(
        bundle,
        compiled_state::BundleSectionKind::Do,
        HOST_CONTEXT_NEXT_FOCUS_LIMIT,
        HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS,
    );
    items.extend(host_context_section_items(
        bundle,
        compiled_state::BundleSectionKind::Focus,
        HOST_CONTEXT_NEXT_FOCUS_LIMIT,
        HOST_CONTEXT_NEXT_FOCUS_MAX_CHARS,
    ));
    dedupe_preserving_order(&items, HOST_CONTEXT_NEXT_FOCUS_LIMIT)
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum RememberScope {
    Clone,
    Branch,
    Repo,
    Profile,
}

impl RememberScope {
    fn as_str(self) -> &'static str {
        match self {
            Self::Clone => "clone",
            Self::Branch => "branch",
            Self::Repo => "repo",
            Self::Profile => "profile",
        }
    }

    fn label(self) -> &'static str {
        match self {
            Self::Clone => "Workspace",
            Self::Branch => "Work stream",
            Self::Repo => "Project",
            Self::Profile => "Profile",
        }
    }

    fn score_bonus(self) -> i64 {
        match self {
            Self::Clone => 40,
            Self::Branch => 30,
            Self::Repo => 20,
            Self::Profile => 5,
        }
    }

    fn sort_rank(self) -> usize {
        match self {
            Self::Clone => 0,
            Self::Branch => 1,
            Self::Repo => 2,
            Self::Profile => 3,
        }
    }
}

#[derive(Clone)]
struct RememberCandidate {
    dedupe_key: String,
    rendered: String,
    scope: RememberScope,
    score: i64,
    matched_tokens: Vec<String>,
    reasons: Vec<String>,
    suppression_reasons: Vec<String>,
    created_at: Option<String>,
    last_touched_session: Option<u64>,
    source_ref: Option<String>,
    created_at_epoch_s: Option<i64>,
}

fn host_context_remember_items(selection: &RememberSelectionView) -> Vec<String> {
    selection
        .selected
        .iter()
        .map(|item| compact_host_context_item(&item.rendered, HOST_CONTEXT_REMEMBER_MAX_CHARS))
        .collect()
}

fn host_context_remember_selection(core: &ResolvedCore) -> Option<RememberSelectionView> {
    let query = memory_recall::build_start_query(
        &core.compiled_state.store.handoff.title,
        &core.compiled_state.store.handoff.immediate_actions,
    );
    let query_tokens = query
        .as_deref()
        .map(remember_query_tokens)
        .unwrap_or_default();
    let memory = runtime_state::memory_state_from_sources(&core.sources);
    let mut candidates = Vec::new();
    remember_scope_candidates(
        &mut candidates,
        &memory.clone,
        RememberScope::Clone,
        &query_tokens,
    );
    remember_scope_candidates(
        &mut candidates,
        &memory.branch,
        RememberScope::Branch,
        &query_tokens,
    );
    remember_scope_candidates(
        &mut candidates,
        &memory.locality,
        RememberScope::Repo,
        &query_tokens,
    );
    remember_scope_candidates(
        &mut candidates,
        &memory.profile,
        RememberScope::Profile,
        &query_tokens,
    );

    if candidates.is_empty() {
        return None;
    }

    apply_recency_bonus(&mut candidates);
    candidates.sort_by(|left, right| {
        right
            .score
            .cmp(&left.score)
            .then_with(|| right.matched_tokens.len().cmp(&left.matched_tokens.len()))
            .then_with(|| left.scope.sort_rank().cmp(&right.scope.sort_rank()))
            .then_with(|| right.last_touched_session.cmp(&left.last_touched_session))
            .then_with(|| right.created_at_epoch_s.cmp(&left.created_at_epoch_s))
            .then_with(|| left.rendered.cmp(&right.rendered))
    });

    let mut selected = Vec::new();
    let mut suppressed = Vec::new();
    let mut seen = std::collections::HashSet::new();

    for mut candidate in candidates {
        if !seen.insert(candidate.dedupe_key.clone()) {
            candidate
                .suppression_reasons
                .push("duplicate_of_higher_ranked_item".to_owned());
            if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
                suppressed.push(remember_candidate_view(candidate));
            }
            continue;
        }

        if candidate.score <= 0 {
            candidate
                .suppression_reasons
                .push("non_positive_score".to_owned());
            if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
                suppressed.push(remember_candidate_view(candidate));
            }
            continue;
        }

        if selected.len() < HOST_CONTEXT_REMEMBER_LIMIT {
            selected.push(remember_candidate_view(candidate));
        } else if suppressed.len() < HOST_CONTEXT_REMEMBER_SUPPRESSED_LIMIT {
            candidate
                .suppression_reasons
                .push("budget_exhausted".to_owned());
            suppressed.push(remember_candidate_view(candidate));
        }
    }

    Some(RememberSelectionView {
        budget: HOST_CONTEXT_REMEMBER_LIMIT,
        query,
        selected,
        suppressed,
    })
}

fn remember_scope_candidates(
    candidates: &mut Vec<RememberCandidate>,
    entries: &[runtime_state::RuntimeMemoryEntry],
    scope: RememberScope,
    query_tokens: &[String],
) {
    for entry in entries {
        if !host_context_memory_entry_is_active(entry) {
            continue;
        }
        if let Some(candidate) = remember_candidate(entry, scope, query_tokens) {
            candidates.push(candidate);
        }
    }
}

fn remember_candidate(
    entry: &runtime_state::RuntimeMemoryEntry,
    scope: RememberScope,
    query_tokens: &[String],
) -> Option<RememberCandidate> {
    let projection_text = entry.projection_text();
    let dedupe_key = projection_text.trim().to_owned();
    if dedupe_key.is_empty() {
        return None;
    }

    let rendered = format!("{}: {}", scope.label(), projection_text);
    let normalized_tokens = remember_text_token_index(&format!(
        "{} {} {}",
        projection_text,
        entry.tags.join(" "),
        entry.source_ref.as_deref().unwrap_or("")
    ));
    let matched_tokens = query_tokens
        .iter()
        .filter(|token| normalized_tokens.contains(token.as_str()))
        .cloned()
        .collect::<Vec<_>>();
    let mut reasons = Vec::new();
    let mut suppression_reasons = Vec::new();

    let query_score = i64::try_from(matched_tokens.len())
        .unwrap_or(i64::MAX)
        .saturating_mul(90);
    if !matched_tokens.is_empty() {
        reasons.push(format!(
            "matched_query_tokens: {}",
            matched_tokens.join(",")
        ));
    }

    let scope_bonus = scope.score_bonus();
    reasons.push(format!("scope_bonus: {}", scope.as_str()));

    let structured_bonus = match entry.origin {
        runtime_state::RuntimeMemoryOrigin::Structured { .. } => {
            reasons.push("structured_memory_bonus".to_owned());
            10
        }
        runtime_state::RuntimeMemoryOrigin::Narrative => 0,
    };

    let mut score = query_score + scope_bonus + structured_bonus;
    if query_tokens.is_empty() {
        score -= 200;
        suppression_reasons.push("no_query_tokens_available".to_owned());
    } else if matched_tokens.is_empty() {
        score -= 120;
        suppression_reasons.push("generic_without_query_match".to_owned());
    }

    let char_count = rendered.chars().count();
    if char_count > HOST_CONTEXT_REMEMBER_MAX_CHARS {
        let penalty = if matched_tokens.is_empty() { 90 } else { 20 };
        score -= penalty;
        suppression_reasons.push("broad_snippet_penalty".to_owned());
    } else if matched_tokens.is_empty() && char_count > 96 {
        score -= 30;
        suppression_reasons.push("broad_snippet_penalty".to_owned());
    }

    Some(RememberCandidate {
        dedupe_key,
        rendered,
        scope,
        score,
        matched_tokens,
        reasons,
        suppression_reasons,
        created_at: entry.created_at.clone(),
        last_touched_session: entry.last_touched_session,
        source_ref: entry.source_ref.clone(),
        created_at_epoch_s: remember_created_at_epoch(entry),
    })
}

fn apply_recency_bonus(candidates: &mut [RememberCandidate]) {
    let mut ranked = candidates
        .iter()
        .enumerate()
        .filter_map(|(index, candidate)| {
            (candidate.last_touched_session.is_some() || candidate.created_at_epoch_s.is_some())
                .then_some((
                    index,
                    candidate.last_touched_session,
                    candidate.created_at_epoch_s,
                ))
        })
        .collect::<Vec<_>>();
    ranked.sort_by(|left, right| right.1.cmp(&left.1).then_with(|| right.2.cmp(&left.2)));

    for (rank, (index, _, _)) in ranked.into_iter().enumerate() {
        let bonus = match rank {
            0 => 20,
            1 => 10,
            2 => 5,
            _ => 0,
        };
        if bonus == 0 {
            continue;
        }
        let candidate = &mut candidates[index];
        candidate.score += bonus;
        candidate.reasons.push(format!("recency_bonus:{bonus}"));
    }
}

fn remember_candidate_view(candidate: RememberCandidate) -> RememberSelectionItemView {
    RememberSelectionItemView {
        rendered: compact_host_context_item(&candidate.rendered, HOST_CONTEXT_REMEMBER_MAX_CHARS),
        scope: candidate.scope.as_str(),
        score: candidate.score,
        matched_tokens: candidate.matched_tokens,
        reasons: candidate.reasons,
        suppressed_reasons: candidate.suppression_reasons,
        created_at: candidate.created_at,
        last_touched_session: candidate.last_touched_session,
        source_ref: candidate.source_ref,
    }
}

fn remember_query_tokens(query: &str) -> Vec<String> {
    const STOPWORDS: &[&str] = &[
        "active",
        "again",
        "all",
        "also",
        "and",
        "any",
        "are",
        "before",
        "branch",
        "cache",
        "changes",
        "choose",
        "continuing",
        "follow",
        "focus",
        "from",
        "github",
        "handoff",
        "here",
        "into",
        "issue",
        "keep",
        "main",
        "memory",
        "names",
        "next",
        "numbers",
        "only",
        "open",
        "queue",
        "refresh",
        "review",
        "resume",
        "scope",
        "scoped",
        "session",
        "snapshot",
        "start",
        "state",
        "step",
        "startup",
        "target",
        "text",
        "the",
        "that",
        "this",
        "too",
        "use",
        "using",
        "via",
        "whether",
        "with",
        "work",
        "wrapper",
    ];
    const HIGH_SIGNAL_SHORT_TOKENS: &[&str] = &[
        "api", "ccd", "ci", "db", "gh", "id", "mcp", "pod", "pr", "ui", "ux",
    ];

    let mut seen = std::collections::HashSet::new();
    remember_text_tokens(query)
        .into_iter()
        .filter(|token| {
            token.len() >= 4
                || (token.len() >= 2 && token.chars().all(|ch| ch.is_ascii_digit()))
                || HIGH_SIGNAL_SHORT_TOKENS.contains(&token.as_str())
        })
        .filter(|token| !STOPWORDS.contains(&token.as_str()))
        .filter(|token| seen.insert(token.clone()))
        .collect()
}

fn remember_text_tokens(value: &str) -> Vec<String> {
    value
        .split(|ch: char| !ch.is_ascii_alphanumeric())
        .map(|token| token.trim().to_ascii_lowercase())
        .filter(|token| !token.is_empty())
        .collect()
}

fn remember_text_token_index(value: &str) -> std::collections::HashSet<String> {
    remember_text_tokens(value).into_iter().collect()
}

fn remember_created_at_epoch(entry: &runtime_state::RuntimeMemoryEntry) -> Option<i64> {
    entry
        .created_at
        .as_deref()
        .and_then(timestamps::parse_rfc3339)
        .map(|value| value.unix_timestamp())
}

fn host_context_memory_entry_is_active(entry: &runtime_state::RuntimeMemoryEntry) -> bool {
    if !entry.lifecycle.is_active() {
        return false;
    }

    let Some(expires_at) = entry.expires_at.as_deref() else {
        return true;
    };

    match timestamps::parse_rfc3339(expires_at) {
        Some(expires_at) => expires_at > timestamps::now_utc(),
        None => true,
    }
}

fn host_context_section_items(
    bundle: &compiled_state::BundleProjectionView,
    kind: compiled_state::BundleSectionKind,
    limit: usize,
    max_chars_per_item: usize,
) -> Vec<String> {
    let items = bundle
        .sections
        .iter()
        .find(|section| section.kind == kind)
        .map(|section| section.items.clone())
        .unwrap_or_default();
    let compact = items
        .into_iter()
        .map(|item| compact_host_context_item(&item, max_chars_per_item))
        .filter(|item| !item.is_empty())
        .collect::<Vec<_>>();
    dedupe_preserving_order(&compact, limit)
}

fn dedupe_preserving_order(items: &[String], limit: usize) -> Vec<String> {
    let mut seen = std::collections::HashSet::new();
    let mut deduped = Vec::new();
    for item in items {
        if seen.insert(item.clone()) {
            deduped.push(item.clone());
        }
        if deduped.len() >= limit {
            break;
        }
    }
    deduped
}

fn compact_host_context_item(item: &str, max_chars: usize) -> String {
    let compact = item.split_whitespace().collect::<Vec<_>>().join(" ");
    if compact.chars().count() <= max_chars {
        return compact;
    }
    if max_chars <= 3 {
        return ".".repeat(max_chars);
    }

    let mut shortened = compact.chars().take(max_chars - 3).collect::<String>();
    shortened.push_str("...");
    shortened
}

/// Disposition-qualifying warning checks — these represent ambiguous or
/// contested continuity state that must be resolved before the disposition can
/// be trusted. Informational warnings do not qualify.
const DISPOSITION_QUALIFYING_WARNINGS: &[&str] =
    &["session_ownership_conflict", "session_lifecycle_conflict"];

fn derive_disposition(core: &ResolvedCore) -> StartupDisposition {
    // Step 1: error alerts → workflow attention required
    if core
        .alerts
        .iter()
        .any(|a| matches!(a.severity, StartAlertSeverity::Error))
    {
        return StartupDisposition::WorkflowAttentionRequired;
    }

    // Step 2: blocking escalation → resume blocked continuity
    if core.escalation.blocking_count > 0 {
        return StartupDisposition::ResumeBlockedContinuity;
    }

    // Step 3: qualifying warning alerts → workflow attention required
    if core.alerts.iter().any(|a| {
        matches!(a.severity, StartAlertSeverity::Warning)
            && DISPOSITION_QUALIFYING_WARNINGS.contains(&a.check)
    }) {
        return StartupDisposition::WorkflowAttentionRequired;
    }

    // Step 4: active handoff → resume
    let title = &core.compiled_state.store.handoff.title;
    let handoff_content = &core.sources.handoff.content;
    if title != "No active session" && !handoff_content.trim().is_empty() {
        return StartupDisposition::ResumeActiveContinuity;
    }

    // Step 5: genuinely idle
    StartupDisposition::NoActiveContinuity
}

fn start_decisions(
    disposition: StartupDisposition,
    session_boundary: &SessionBoundaryRecommendation,
    alerts: &[StartAlert],
) -> StartDecisions {
    let requires_doctor = alerts
        .iter()
        .any(|alert| matches!(alert.severity, StartAlertSeverity::Error));
    let requires_human_choice = requires_doctor
        || matches!(
            disposition,
            StartupDisposition::WorkflowAttentionRequired
                | StartupDisposition::ResumeBlockedContinuity
        )
        || matches!(session_boundary.action, SessionBoundaryAction::Stop);
    let provider_live_check_needed = alerts.iter().any(|alert| alert.check == "memory_provider");
    let needs_full_handover = matches!(session_boundary.action, SessionBoundaryAction::WrapUp);
    let recommended_next_command = match session_boundary.action {
        SessionBoundaryAction::Continue => "continue",
        SessionBoundaryAction::Refresh => "ccd start --refresh",
        SessionBoundaryAction::WrapUp => "ccd --output json handover",
        SessionBoundaryAction::Stop if requires_doctor => "ccd doctor",
        SessionBoundaryAction::Stop => "resolve_human_choice",
    };

    StartDecisions {
        requires_human_choice,
        requires_doctor,
        can_clear_session: false,
        safe_clean_close: false,
        needs_full_handover,
        provider_live_check_needed,
        prompt_context_unchanged: false,
        recommended_next_command,
    }
}

fn build_session_boundary(
    core: &ResolvedCore,
    disposition: StartupDisposition,
) -> SessionBoundaryRecommendation {
    let recovery_note = recovery_boundary_note(&core.recovery);

    if core.escalation.blocking_count > 0 {
        let mut evidence = core
            .escalation
            .entries
            .iter()
            .filter(|entry| matches!(entry.kind, escalation_state::EscalationKind::Blocking))
            .map(|entry| format!("{}: {}", entry.id, entry.reason))
            .collect::<Vec<_>>();
        append_recovery_note(&mut evidence, recovery_note.as_deref());
        return SessionBoundaryRecommendation::new(
            SessionBoundaryAction::Stop,
            "Stop and resolve blocking escalations before continuing this session.",
            evidence,
        );
    }

    let session_conflict_alerts = core
        .alerts
        .iter()
        .filter(|alert| {
            matches!(
                alert.check,
                "session_ownership_conflict" | "session_lifecycle_conflict"
            )
        })
        .collect::<Vec<_>>();
    if !session_conflict_alerts.is_empty() {
        let mut evidence = session_conflict_alerts
            .iter()
            .map(|alert| alert.message.clone())
            .collect::<Vec<_>>();
        append_recovery_note(&mut evidence, recovery_note.as_deref());
        return SessionBoundaryRecommendation::new(
            SessionBoundaryAction::Stop,
            "Resolve the recorded session conflict before continuing this session.",
            evidence,
        );
    }

    let stop_alerts = core
        .alerts
        .iter()
        .filter(|alert| alert.check == "checkout_state")
        .collect::<Vec<_>>();
    if !stop_alerts.is_empty() {
        let mut evidence = stop_alerts
            .iter()
            .map(|alert| alert.message.clone())
            .collect::<Vec<_>>();
        append_recovery_note(&mut evidence, recovery_note.as_deref());
        return SessionBoundaryRecommendation::new(
            SessionBoundaryAction::Stop,
            "Stop here and choose the next step from a live checkout before continuing.",
            evidence,
        );
    }

    if matches!(disposition, StartupDisposition::NoActiveContinuity) {
        let mut evidence = vec![
            "No active continuity snapshot or explicit next-step selection is loaded yet."
                .to_owned(),
        ];
        append_recovery_note(&mut evidence, recovery_note.as_deref());
        return SessionBoundaryRecommendation::new(
            SessionBoundaryAction::Stop,
            "Stop here and confirm the next step before continuing this session.",
            evidence,
        );
    }

    let mut evidence = Vec::new();
    if !core
        .compiled_state
        .store
        .handoff
        .immediate_actions
        .is_empty()
    {
        evidence.push(format!(
            "The handoff already carries {} immediate action(s).",
            core.compiled_state.store.handoff.immediate_actions.len()
        ));
    }
    append_recovery_note(&mut evidence, recovery_note.as_deref());

    SessionBoundaryRecommendation::new(
        SessionBoundaryAction::Continue,
        "Continue with the current continuity context.",
        evidence,
    )
}

fn recovery_boundary_note(recovery: &runtime_state::LoadedRuntimeRecoveryState) -> Option<String> {
    (recovery.state.checkpoint.is_some() || recovery.state.working_buffer.is_some()).then_some(
        "Recovery artifacts are loaded as supporting context only; durable continuity remains authoritative."
            .to_owned(),
    )
}

fn append_recovery_note(evidence: &mut Vec<String>, note: Option<&str>) {
    if let Some(note) = note {
        evidence.push(note.to_owned());
    }
}

fn start_readiness_view(alerts: &[StartAlert]) -> StartCheckReadiness {
    let non_info_alerts: Vec<_> = alerts
        .iter()
        .filter(|a| !matches!(a.severity, StartAlertSeverity::Info))
        .collect();
    if non_info_alerts.is_empty() {
        StartCheckReadiness::Ready
    } else if non_info_alerts
        .iter()
        .all(|alert| alert.check == "escalation_state")
    {
        StartCheckReadiness::Blocked
    } else {
        // Check mode intentionally fails closed: any non-info alert means the clone
        // needs explicit actor follow-up before automation can rely on it.
        StartCheckReadiness::NotReady
    }
}

fn ensure_profile_exists(layout: &StateLayout) -> Result<()> {
    let profile_root = layout.profile_root();
    if profile_root.is_dir() {
        return Ok(());
    }

    bail!(
        "profile `{}` does not exist at {}; bootstrap it with `ccd attach` before using `ccd start`",
        layout.profile(),
        profile_root.display()
    )
}

fn ensure_repo_registry_exists(layout: &StateLayout, locality_id: &str) -> Result<()> {
    let registry_path = layout.repo_metadata_path(locality_id)?;
    if repo_registry::load(&registry_path)?.is_some() {
        return Ok(());
    }

    bail!(
        "project ID `{locality_id}` (`locality_id` compatibility) is not linked in the registry: {} is missing; re-run `ccd link --project-id {locality_id}` or `ccd attach`",
        registry_path.display()
    )
}

fn resolve_manifest(
    repo_root: &Path,
    layout: &StateLayout,
    locality_id: &str,
) -> Result<ManifestResolution> {
    let resolved = project_truth::resolve_manifest(repo_root, layout, locality_id)?;
    let sources = resolved
        .project_truth_paths
        .iter()
        .map(|path| read_existing_source("project_truth", path))
        .collect::<Result<Vec<_>>>()?;

    Ok(ManifestResolution {
        source_order: resolved.source_order,
        manifest: ManifestView {
            path: resolved.manifest_path.display().to_string(),
            status: resolved.manifest_status,
            entries: resolved
                .entries
                .iter()
                .map(|entry| entry.display().to_string())
                .collect(),
        },
        project_truth_sources: sources,
    })
}

fn read_existing_source(kind: &'static str, path: &Path) -> Result<SourceView> {
    let source = read_text_source(kind, path)?;
    Ok(into_source_view(source))
}

fn read_text_source(kind: &'static str, path: &Path) -> Result<TextSource> {
    match fs::read_to_string(path) {
        Ok(contents) => Ok(TextSource {
            kind,
            path: path.to_path_buf(),
            status: "loaded",
            content: Some(contents),
        }),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(TextSource {
            kind,
            path: path.to_path_buf(),
            status: "missing",
            content: None,
        }),
        Err(error) => Err(error).with_context(|| format!("failed to read {}", path.display())),
    }
}

fn into_compiled_handoff_view(
    source: &runtime_state::RuntimeTextSurface,
    compiled_content: String,
) -> HandoffView {
    HandoffView {
        path: source.path.display().to_string(),
        status: if source.status.is_loaded_native() {
            "loaded"
        } else {
            source.status.as_str()
        },
        content: compiled_content,
    }
}

struct LoadedSessionState {
    view: SessionStateView,
    raw: Option<session_state::SessionStateFile>,
}

struct LoadedExecutionGates {
    view: session_gates::ExecutionGatesView,
    raw: Option<session_gates::ExecutionGateStateFile>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum StartSessionAssessment {
    Missing,
    InteractiveResume,
    InteractiveStaleRepairable,
    LegacyRefresh,
    ActiveAutonomousConflict,
    StaleAutonomousConflict,
}

impl StartSessionAssessment {
    fn from_view(view: &SessionStateView) -> Self {
        match (
            view.status,
            view.lifecycle.lifecycle,
            view.session_id.is_some(),
        ) {
            ("missing", _, _) => Self::Missing,
            ("active", Some(session_state::SessionLifecycle::Interactive), _) => {
                Self::InteractiveResume
            }
            ("stale", Some(session_state::SessionLifecycle::Interactive), false) => {
                Self::LegacyRefresh
            }
            ("stale", Some(session_state::SessionLifecycle::Interactive), true) => {
                Self::InteractiveStaleRepairable
            }
            ("active", Some(session_state::SessionLifecycle::Autonomous), _) => {
                Self::ActiveAutonomousConflict
            }
            ("stale", Some(session_state::SessionLifecycle::Autonomous), _) => {
                Self::StaleAutonomousConflict
            }
            _ => Self::Missing,
        }
    }

    fn into_alert(self, view: &SessionStateView) -> Option<StartAlert> {
        let actor_id = view.lifecycle.actor_id.as_deref().unwrap_or("unknown");
        let actor_id = display_safe(actor_id);
        match self {
            Self::ActiveAutonomousConflict => Some(StartAlert::warning(
                "session_ownership_conflict",
                format!(
                    "an autonomous CCD session owned by `{actor_id}` is still active in this workspace; wait for the lease to expire, or resolve that session before starting interactive work"
                ),
            )),
            Self::StaleAutonomousConflict => Some(StartAlert::warning(
                "session_lifecycle_conflict",
                format!(
                    "a stale autonomous CCD session owned by `{actor_id}` is still recorded in this workspace; clear it or use explicit takeover before starting interactive work"
                ),
            )),
            Self::Missing
            | Self::InteractiveResume
            | Self::InteractiveStaleRepairable
            | Self::LegacyRefresh => None,
        }
    }
}

fn load_session_state(layout: &StateLayout) -> Result<LoadedSessionState> {
    let path = layout.state_db_path();
    let Some(state) = session_state::load_for_layout(layout)? else {
        return Ok(LoadedSessionState {
            view: SessionStateView {
                path: path.display().to_string(),
                status: "missing",
                schema_version: None,
                session_id: None,
                started_at_epoch_s: None,
                last_started_at_epoch_s: None,
                start_count: None,
                mode: None,
                lifecycle: session_state::SessionLifecycleProjection::missing(),
            },
            raw: None,
        });
    };

    let now = session_state::now_epoch_s()?;
    let activity = session_state::load_activity_for_layout(layout)?;
    let lifecycle = session_state::lifecycle_projection(&state, now, None, activity.as_ref());
    let status = if lifecycle.stale == Some(true) || state.session_id.is_none() {
        "stale"
    } else {
        "active"
    };

    Ok(LoadedSessionState {
        view: SessionStateView {
            path: path.display().to_string(),
            status,
            schema_version: Some(state.schema_version),
            session_id: state.session_id.clone(),
            started_at_epoch_s: Some(state.started_at_epoch_s),
            last_started_at_epoch_s: Some(state.last_started_at_epoch_s),
            start_count: Some(state.start_count),
            mode: Some(state.mode),
            lifecycle,
        },
        raw: Some(state),
    })
}

fn session_mode_current_state_line(view: &SessionStateView) -> Option<String> {
    match (view.status, view.mode) {
        ("active", Some(mode)) if mode != session_state::SessionMode::General => {
            Some(format!("Session mode: `{}`", mode.as_str()))
        }
        _ => None,
    }
}

fn load_execution_gates(layout: &StateLayout) -> Result<LoadedExecutionGates> {
    let raw = session_gates::load_for_layout(layout)?;
    Ok(LoadedExecutionGates {
        view: session_gates::build_view(layout, raw.clone()),
        raw,
    })
}

fn session_state_source_view(
    view: &SessionStateView,
    raw: Option<&session_state::SessionStateFile>,
) -> Result<SourceView> {
    let content = raw.map(serde_json::to_string_pretty).transpose()?;
    Ok(SourceView {
        kind: "session_state",
        path: view.path.clone(),
        status: view.status,
        content,
    })
}

fn execution_gates_source_view(
    view: &session_gates::ExecutionGatesView,
    raw: Option<&session_gates::ExecutionGateStateFile>,
) -> Result<SourceView> {
    let content = raw.map(serde_json::to_string_pretty).transpose()?;
    Ok(SourceView {
        kind: "execution_gates",
        path: view.path.clone(),
        status: view.status,
        content,
    })
}

fn validation_profile_source_view(
    profile: &validation_profile::LoadedValidationProfile,
) -> SourceView {
    SourceView {
        kind: "repo_validation_profile",
        path: profile.path.display().to_string(),
        status: profile.status,
        content: profile
            .contents
            .clone()
            .and_then(|content| non_empty_content(&content)),
    }
}

fn render_effective_policy_view(
    parts_in_order: &[policy_projection::PolicySource],
) -> RenderedView {
    let mut parts = Vec::new();
    let mut segments = Vec::new();

    for source in parts_in_order {
        parts.push(RenderedPart {
            kind: source.kind,
            path: source.path.display().to_string(),
            status: source.status,
        });

        if let Some(content) = &source.content {
            if !content.is_empty() {
                segments.push(content.clone());
            }
        }
    }

    let content = segments.join("\n\n");
    let status = if content.is_empty() {
        "empty"
    } else {
        "loaded"
    };

    RenderedView {
        status,
        content,
        parts,
    }
}

fn render_effective_memory_view(
    surfaces: EffectiveMemorySurfaces<'_>,
    structured: memory_entries::StructuredMemoryView,
    pod_identity_active: bool,
    compiled_memory: &compiled_state::CompiledMemoryView,
) -> MemoryView {
    let compiled_content =
        compiled_state::render_memory_content(compiled_memory, pod_identity_active);
    let parts = vec![
        runtime_rendered_part(surfaces.profile),
        runtime_rendered_part(surfaces.locality),
        runtime_rendered_part(surfaces.branch),
        runtime_rendered_part(surfaces.clone),
    ];

    MemoryView {
        status: if compiled_content.is_empty() {
            "empty"
        } else {
            "loaded"
        },
        content: compiled_content,
        parts,
        structured,
    }
}

fn runtime_rendered_part(source: &runtime_state::RuntimeTextSurface) -> RenderedPart {
    RenderedPart {
        kind: source.kind,
        path: source.path.display().to_string(),
        status: source.status.as_str(),
    }
}

fn runtime_source_view(source: &runtime_state::RuntimeTextSurface) -> SourceView {
    SourceView {
        kind: source.kind,
        path: source.path.display().to_string(),
        status: source.status.as_str(),
        content: non_empty_content(&source.content),
    }
}

fn into_source_view(source: TextSource) -> SourceView {
    let content = source.content.unwrap_or_default();
    let status = if source.status == "loaded" && content.is_empty() {
        "empty"
    } else {
        source.status
    };

    SourceView {
        kind: source.kind,
        path: source.path.display().to_string(),
        status,
        content: non_empty_content(&content),
    }
}

fn non_empty_content(content: &str) -> Option<String> {
    if content.is_empty() {
        None
    } else {
        Some(content.to_owned())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn hostile_view(status: &'static str) -> SessionStateView {
        let lifecycle = session_state::SessionLifecycleProjection {
            lifecycle: Some(session_state::SessionLifecycle::Autonomous),
            owner_kind: Some(session_state::SessionOwnerKind::RuntimeWorker),
            // Actor id carries a fake log line and an ANSI screen-clear
            // escape; the alert message must escape both.
            actor_id: Some("worker\nFAKE 00:00:00 info\x1b[2Jclear".to_owned()),
            supervisor_id: None,
            lease_ttl_secs: Some(3600),
            last_heartbeat_at_epoch_s: Some(0),
            lease_expires_at_epoch_s: Some(3600),
            stale: Some(status == "stale"),
            revision: Some(1),
            current_activity: None,
            activity_updated_at_epoch_s: None,
            ownership_conflict: None,
        };
        SessionStateView {
            path: "/tmp/irrelevant".to_owned(),
            status,
            schema_version: Some(4),
            session_id: Some("ses_hostile".to_owned()),
            started_at_epoch_s: Some(0),
            last_started_at_epoch_s: Some(0),
            start_count: Some(1),
            mode: Some(session_state::SessionMode::General),
            lifecycle,
        }
    }

    /// ccd#576: `actor_id` is accepted as an unrestricted `String`
    /// upstream. When it surfaces in the operator-facing `StartAlert`
    /// message for a session ownership/lifecycle conflict, newlines
    /// and ANSI escapes must be rendered in escaped form so a
    /// malicious worker cannot inject fake log lines or clear the
    /// operator's terminal.
    #[test]
    fn start_alert_escapes_newlines_in_active_autonomous_conflict() {
        let view = hostile_view("active");
        let alert = StartSessionAssessment::ActiveAutonomousConflict
            .into_alert(&view)
            .expect("ActiveAutonomousConflict must produce an alert");
        assert!(
            !alert.message.contains('\n'),
            "alert message must not contain literal newlines; got: {:?}",
            alert.message
        );
        assert!(
            !alert.message.contains('\x1b'),
            "alert message must not contain literal ESC bytes; got: {:?}",
            alert.message
        );
        assert!(
            alert.message.contains(r"worker\nFAKE"),
            "actor id must render in escaped form; got: {:?}",
            alert.message
        );
    }

    #[test]
    fn start_alert_escapes_newlines_in_stale_autonomous_conflict() {
        let view = hostile_view("stale");
        let alert = StartSessionAssessment::StaleAutonomousConflict
            .into_alert(&view)
            .expect("StaleAutonomousConflict must produce an alert");
        assert!(
            !alert.message.contains('\n'),
            "alert message must not contain literal newlines; got: {:?}",
            alert.message
        );
        assert!(
            !alert.message.contains('\x1b'),
            "alert message must not contain literal ESC bytes; got: {:?}",
            alert.message
        );
        assert!(
            alert.message.contains(r"worker\nFAKE"),
            "actor id must render in escaped form; got: {:?}",
            alert.message
        );
    }
}
