#[cfg(feature = "test-utils")]
use std::path::{Path, PathBuf};
use crate::agents::AgentRegistry;
#[cfg(feature = "test-utils")]
use crate::app::effect::{AppEffect, AppEffectHandler, AppEffectResult};
#[cfg(feature = "test-utils")]
use crate::app::effect_handler::RealAppEffectHandler;
#[cfg(feature = "test-utils")]
use crate::app::runner::command_handlers::handle_plumbing_commands;
#[cfg(feature = "test-utils")]
use crate::app::runner::{validate_and_setup_agents, AgentSetupParams};
#[cfg(feature = "test-utils")]
fn set_current_dir_with_handler(
handler: &mut dyn AppEffectHandler,
path: &Path,
) -> anyhow::Result<()> {
match handler.execute(AppEffect::SetCurrentDir {
path: path.to_path_buf(),
}) {
AppEffectResult::Ok => Ok(()),
AppEffectResult::Error(err) => anyhow::bail!("Failed to set working directory: {err}"),
_ => Ok(()),
}
}
#[cfg(feature = "test-utils")]
fn discover_repo_root_for_workspace(
override_dir: Option<&Path>,
handler: &mut dyn AppEffectHandler,
) -> anyhow::Result<PathBuf> {
if let Some(dir) = override_dir {
set_current_dir_with_handler(handler, dir)?;
return Ok(dir.to_path_buf());
}
if let AppEffectResult::Error(err) = handler.execute(AppEffect::GitRequireRepo) {
anyhow::bail!("Not in a git repository: {err}");
}
let root = match handler.execute(AppEffect::GitGetRepoRoot) {
AppEffectResult::Path(path) => path,
AppEffectResult::Error(err) => anyhow::bail!("Failed to get repo root: {err}"),
_ => anyhow::bail!("Unexpected result from GitGetRepoRoot"),
};
set_current_dir_with_handler(handler, &root)?;
Ok(root)
}
#[cfg(feature = "test-utils")]
pub fn run_with_config(
args: Args,
executor: std::sync::Arc<dyn ProcessExecutor>,
config: crate::config::Config,
registry: AgentRegistry,
) -> anyhow::Result<()> {
let mut handler = RealAppEffectHandler::new();
run_with_config_and_resolver(
args,
executor,
config,
registry,
&crate::config::RealConfigEnvironment,
&mut handler,
None, )
}
#[cfg(feature = "test-utils")]
pub fn run_with_config_and_resolver<P: crate::config::ConfigEnvironment, H: AppEffectHandler>(
args: Args,
executor: std::sync::Arc<dyn ProcessExecutor>,
config: crate::config::Config,
registry: AgentRegistry,
path_resolver: &P,
handler: &mut H,
workspace: Option<std::sync::Arc<dyn crate::workspace::Workspace>>,
) -> anyhow::Result<()> {
use crate::cli::{
handle_extended_help, handle_init_global_with, handle_init_local_config_with,
handle_list_work_guides, handle_smart_init_with,
};
let colors = Colors::new();
let logger = Logger::new(colors);
if let Some(ref override_dir) = args.working_dir_override {
std::env::set_current_dir(override_dir)?;
}
if let Err(e) =
crate::config::loader::load_config_from_path_with_env(args.config.as_deref(), path_resolver)
{
eprintln!("{}", e.format_errors());
return Err(anyhow::anyhow!("Configuration validation failed"));
}
if args.recovery.extended_help {
handle_extended_help();
if args.work_guide_list.list_work_guides {
println!();
let _ = handle_list_work_guides(colors);
}
return Ok(());
}
if args.work_guide_list.list_work_guides && handle_list_work_guides(colors) {
return Ok(());
}
if args.unified_init.init.is_some()
&& handle_smart_init_with(
args.unified_init.init.as_deref(),
args.unified_init.force_init,
colors,
path_resolver,
)?
{
return Ok(());
}
if args.unified_init.init_config && handle_init_global_with(colors, path_resolver)? {
return Ok(());
}
if args.unified_init.init_global && handle_init_global_with(colors, path_resolver)? {
return Ok(());
}
if args.unified_init.init_local_config
&& handle_init_local_config_with(colors, path_resolver, args.unified_init.force_init)?
{
return Ok(());
}
let config_path = std::path::PathBuf::from("test-config");
let validated = resolve_required_agents(
&config,
&crate::app::config_init::AgentResolutionSources {
local_config_path: None,
global_config_path: Some(config_path.clone()),
built_in_defaults: true,
},
)?;
let developer_agent = validated.developer_agent;
let reviewer_agent = validated.reviewer_agent;
if handle_listing_commands(&args, ®istry, colors) {
return Ok(());
}
if args.recovery.diagnose {
let diagnose_workspace = workspace
.as_ref()
.map(std::convert::AsRef::as_ref)
.ok_or_else(|| anyhow::anyhow!("--diagnose requires workspace context"))?;
handle_diagnose(
&mut std::io::stdout(),
colors,
&config,
®istry,
crate::cli::ConfigInfo {
path: &config_path,
sources: &[],
},
&*executor,
diagnose_workspace,
);
return Ok(());
}
let repo_root = workspace
.as_ref()
.map(|ws| ws.root().to_path_buf())
.map_or_else(
|| discover_repo_root_for_workspace(args.working_dir_override.as_deref(), handler),
Ok,
)?;
let workspace = workspace.unwrap_or_else(|| {
std::sync::Arc::new(crate::workspace::WorkspaceFs::new(repo_root.clone()))
});
if handle_plumbing_commands(&args, &logger, colors, handler, Some(workspace.as_ref()))? {
return Ok(());
}
if !command_requires_prompt_setup(&args)
&& handle_repo_commands_without_prompt_setup(RepoCommandParams {
args: &args,
config: &config,
registry: ®istry,
developer_agent: &developer_agent,
reviewer_agent: &reviewer_agent,
logger: &logger,
colors,
executor: &executor,
app_handler: handler,
repo_root: &repo_root,
workspace: &workspace,
})?
{
return Ok(());
}
if args.recovery.inspect_checkpoint {
crate::app::resume::inspect_checkpoint(workspace.as_ref(), &logger)?;
return Ok(());
}
let Some(repo_root) = validate_and_setup_agents(
&AgentSetupParams {
config: &config,
registry: ®istry,
developer_agent: &developer_agent,
reviewer_agent: &reviewer_agent,
config_path: &config_path,
colors,
logger: &logger,
working_dir_override: args.working_dir_override.as_deref(),
},
handler,
)?
else {
return Ok(());
};
(prepare_pipeline_or_exit(PipelinePreparationParams {
args,
config,
registry,
developer_agent,
reviewer_agent,
repo_root,
logger,
colors,
executor,
handler,
workspace,
})?)
.map_or_else(|| Ok(()), |ctx| run_pipeline(&ctx))
}
#[cfg(feature = "test-utils")]
pub struct RunWithHandlersParams<'a, 'ctx, P, A, E>
where
P: crate::config::ConfigEnvironment,
A: AppEffectHandler,
E: crate::reducer::EffectHandler<'ctx> + crate::app::StatefulHandler,
{
pub args: Args,
pub executor: std::sync::Arc<dyn ProcessExecutor>,
pub config: crate::config::Config,
pub registry: AgentRegistry,
pub path_resolver: &'a P,
pub app_handler: &'a mut A,
pub effect_handler: &'a mut E,
pub workspace: Option<std::sync::Arc<dyn crate::workspace::Workspace>>,
pub _marker: std::marker::PhantomData<&'ctx ()>,
}
#[cfg(feature = "test-utils")]
pub fn run_with_config_and_handlers<'a, 'ctx, P, A, E>(
params: RunWithHandlersParams<'a, 'ctx, P, A, E>,
) -> anyhow::Result<()>
where
P: crate::config::ConfigEnvironment,
A: AppEffectHandler,
E: crate::reducer::EffectHandler<'ctx> + crate::app::StatefulHandler,
{
use crate::cli::{
handle_extended_help, handle_init_global_with, handle_init_local_config_with,
handle_list_work_guides, handle_smart_init_with,
};
let RunWithHandlersParams {
args,
executor,
config,
registry,
path_resolver,
app_handler,
effect_handler,
workspace,
..
} = params;
let colors = Colors::new();
let logger = Logger::new(colors);
if let Some(ref override_dir) = args.working_dir_override {
std::env::set_current_dir(override_dir)?;
}
if let Err(e) =
crate::config::loader::load_config_from_path_with_env(args.config.as_deref(), path_resolver)
{
eprintln!("{}", e.format_errors());
return Err(anyhow::anyhow!("Configuration validation failed"));
}
if args.recovery.extended_help {
handle_extended_help();
if args.work_guide_list.list_work_guides {
println!();
let _ = handle_list_work_guides(colors);
}
return Ok(());
}
if args.work_guide_list.list_work_guides && handle_list_work_guides(colors) {
return Ok(());
}
if args.unified_init.init.is_some()
&& handle_smart_init_with(
args.unified_init.init.as_deref(),
args.unified_init.force_init,
colors,
path_resolver,
)?
{
return Ok(());
}
if args.unified_init.init_config && handle_init_global_with(colors, path_resolver)? {
return Ok(());
}
if args.unified_init.init_global && handle_init_global_with(colors, path_resolver)? {
return Ok(());
}
if args.unified_init.init_local_config
&& handle_init_local_config_with(colors, path_resolver, args.unified_init.force_init)?
{
return Ok(());
}
let config_path = std::path::PathBuf::from("test-config");
let validated = resolve_required_agents(
&config,
&crate::app::config_init::AgentResolutionSources {
local_config_path: None,
global_config_path: Some(config_path.clone()),
built_in_defaults: true,
},
)?;
let developer_agent = validated.developer_agent;
let reviewer_agent = validated.reviewer_agent;
if handle_listing_commands(&args, ®istry, colors) {
return Ok(());
}
if args.recovery.diagnose {
let diagnose_workspace = workspace
.as_ref()
.map(std::convert::AsRef::as_ref)
.ok_or_else(|| anyhow::anyhow!("--diagnose requires workspace context"))?;
handle_diagnose(
&mut std::io::stdout(),
colors,
&config,
®istry,
crate::cli::ConfigInfo {
path: &config_path,
sources: &[],
},
&*executor,
diagnose_workspace,
);
return Ok(());
}
let repo_root = workspace
.as_ref()
.map(|ws| ws.root().to_path_buf())
.map_or_else(
|| discover_repo_root_for_workspace(args.working_dir_override.as_deref(), app_handler),
Ok,
)?;
let workspace = workspace.unwrap_or_else(|| {
std::sync::Arc::new(crate::workspace::WorkspaceFs::new(repo_root.clone()))
});
if handle_plumbing_commands(
&args,
&logger,
colors,
app_handler,
Some(workspace.as_ref()),
)? {
return Ok(());
}
if !command_requires_prompt_setup(&args)
&& handle_repo_commands_without_prompt_setup(RepoCommandParams {
args: &args,
config: &config,
registry: ®istry,
developer_agent: &developer_agent,
reviewer_agent: &reviewer_agent,
logger: &logger,
colors,
executor: &executor,
app_handler,
repo_root: &repo_root,
workspace: &workspace,
})?
{
return Ok(());
}
if args.recovery.inspect_checkpoint {
crate::app::resume::inspect_checkpoint(workspace.as_ref(), &logger)?;
return Ok(());
}
let Some(repo_root) = validate_and_setup_agents(
&AgentSetupParams {
config: &config,
registry: ®istry,
developer_agent: &developer_agent,
reviewer_agent: &reviewer_agent,
config_path: &config_path,
colors,
logger: &logger,
working_dir_override: args.working_dir_override.as_deref(),
},
app_handler,
)?
else {
return Ok(());
};
let ctx = prepare_pipeline_or_exit(PipelinePreparationParams {
args,
config,
registry,
developer_agent,
reviewer_agent,
repo_root,
logger,
colors,
executor,
handler: app_handler,
workspace,
})?;
ctx.map_or_else(
|| Ok(()),
|ctx| run_pipeline_with_effect_handler(&ctx, effect_handler),
)
}