use std::thread;
use crate::error::ForgeError;
use crate::execution::environment::prepare_detection_environment;
use crate::execution::install::check_tool;
use crate::execution::runtime::from_plan;
use crate::model::{InstallConfig, InstallPreview, SkillStatus, ToolDef, ToolStatus};
use crate::planning::ExecutionPlan;
use crate::skills::agent_dir;
pub fn preview_plan(plan: &ExecutionPlan) -> Result<InstallPreview, ForgeError> {
preview_install(&from_plan(plan))
}
pub(crate) fn preview_install(config: &InstallConfig) -> Result<InstallPreview, ForgeError> {
prepare_detection_environment(config);
let tool_status = check_tools_bounded(&config.tools)?;
let mut skill_status = Vec::new();
for skill in &config.skills {
for agent in &skill.agents {
let agent_dir = agent_dir(*agent);
let installed = agent_dir
.as_ref()
.is_some_and(|root| root.join(&skill.name).join("SKILL.md").is_file());
skill_status.push(SkillStatus {
name: skill.name.clone(),
display_name: skill.display_name.clone(),
optional: skill.optional,
agent: *agent,
agent_dir,
installed,
installable: !skill.source.trim().is_empty(),
});
}
}
Ok(InstallPreview {
tools: tool_status,
skills: skill_status,
})
}
pub(crate) fn check_tools_bounded(tools: &[ToolDef]) -> Result<Vec<ToolStatus>, ForgeError> {
if tools.len() <= 1 {
return Ok(tools.iter().map(check_tool).collect());
}
let workers = thread::available_parallelism()
.map_or(2, usize::from)
.clamp(1, 4)
.min(tools.len());
let chunk_size = tools.len().div_ceil(workers);
let mut indexed = thread::scope(|scope| -> Result<Vec<_>, ForgeError> {
let handles = tools
.chunks(chunk_size)
.enumerate()
.map(|(chunk_index, chunk)| {
scope.spawn(move || {
chunk
.iter()
.enumerate()
.map(|(offset, tool)| (chunk_index * chunk_size + offset, check_tool(tool)))
.collect::<Vec<_>>()
})
})
.collect::<Vec<_>>();
let mut indexed = Vec::with_capacity(tools.len());
for handle in handles {
indexed.extend(
handle
.join()
.map_err(|_| ForgeError::Command("tool check worker panicked".into()))?,
);
}
Ok(indexed)
})?;
indexed.sort_by_key(|(index, _)| *index);
Ok(indexed.into_iter().map(|(_, status)| status).collect())
}