mod acceptance_state;
mod archive_state;
mod builder;
mod cleanup;
mod conflict;
mod dispatch;
mod dynamic_queue;
mod events;
mod executor;
mod merge;
mod orchestration;
mod output_bridge;
mod queue_state;
mod types;
mod workspace;
pub use crate::events::ExecutionEvent as ParallelEvent;
#[cfg(all(test, feature = "heavy-tests"))]
#[allow(unused_imports)]
pub use merge::{base_dirty_reason, resolve_deferred_merge};
pub use types::{FailedChangeTracker, MergeResult, MergeTaskOutcome, WorkspaceResult};
#[cfg(test)]
pub use crate::vcs::Workspace;
#[cfg(all(test, feature = "heavy-tests"))]
pub use merge::MergeAttempt;
use crate::ai_command_runner::{AiCommandRunner, SharedStaggerState};
use crate::config::OrchestratorConfig;
use crate::hooks::HookRunner;
use crate::vcs::WorkspaceManager;
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::{Arc, Mutex as StdMutex, OnceLock};
use tokio::sync::{mpsc, Mutex, RwLock};
use tokio_util::sync::CancellationToken;
use crate::orchestration::state::OrchestratorState;
type DependencyBlockerFingerprint = Vec<(String, String)>;
const DEFAULT_MAX_CONFLICT_RETRIES: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SchedulerLifetime {
Finite,
Persistent,
}
static GLOBAL_MERGE_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
static ACTIVE_POST_ARCHIVE_MERGES: OnceLock<StdMutex<HashSet<String>>> = OnceLock::new();
fn global_merge_lock() -> &'static Mutex<()> {
GLOBAL_MERGE_LOCK.get_or_init(|| Mutex::new(()))
}
fn active_post_archive_merges() -> &'static StdMutex<HashSet<String>> {
ACTIVE_POST_ARCHIVE_MERGES.get_or_init(|| StdMutex::new(HashSet::new()))
}
pub struct ParallelExecutor {
workspace_manager: Box<dyn WorkspaceManager>,
config: OrchestratorConfig,
apply_command: String,
archive_command: String,
event_tx: Option<mpsc::Sender<ParallelEvent>>,
max_conflict_retries: u32,
repo_root: PathBuf,
no_resume: bool,
failed_tracker: FailedChangeTracker,
change_dependencies: HashMap<String, Vec<String>>,
resolve_wait_changes: HashSet<String>,
reject_wait_changes: HashSet<String>,
merge_wait_changes: HashSet<String>,
dependency_blocker_fingerprints: HashMap<String, DependencyBlockerFingerprint>,
force_recreate_worktree: HashSet<String>,
hooks: Option<Arc<HookRunner>>,
cancel_token: Option<CancellationToken>,
last_queue_change_at: Arc<Mutex<Option<std::time::Instant>>>,
last_available_slots: Option<usize>,
dynamic_queue: Option<Arc<crate::tui::queue::DynamicQueue>>,
ai_runner: AiCommandRunner,
#[allow(dead_code)]
shared_stagger_state: SharedStaggerState,
apply_history: Arc<Mutex<crate::history::ApplyHistory>>,
archive_history: Arc<Mutex<crate::history::ArchiveHistory>>,
acceptance_history: Arc<Mutex<crate::history::AcceptanceHistory>>,
acceptance_tail_injected: Arc<Mutex<std::collections::HashMap<String, bool>>>,
manual_resolve_count: Option<Arc<std::sync::atomic::AtomicUsize>>,
auto_resolve_count: Arc<std::sync::atomic::AtomicUsize>,
pending_merge_count: Arc<std::sync::atomic::AtomicUsize>,
scheduler_lifetime: SchedulerLifetime,
shared_orchestrator_state: Option<Arc<RwLock<OrchestratorState>>>,
last_dispatched_resolve_wait_changes: HashSet<String>,
last_dispatched_reject_wait_changes: HashSet<String>,
resolve_wait_retry_triggered: bool,
last_resolve_wait_base_dirty: Option<bool>,
queue_reconciliation_diagnostics_seen: HashSet<(String, String)>,
no_analysis_diagnostics_seen: HashSet<(Vec<String>, usize, usize, String)>,
dependency_blocker_diagnostics_seen: HashSet<(String, DependencyBlockerFingerprint)>,
}
#[cfg(test)]
mod tests;