bot_forge/execution/
verifier.rs1use std::thread;
4
5use crate::error::ForgeError;
6use crate::execution::environment::prepare_detection_environment;
7use crate::execution::install::check_tool;
8use crate::execution::runtime::from_plan;
9use crate::model::{InstallConfig, InstallPreview, SkillStatus, ToolDef, ToolStatus};
10use crate::planning::ExecutionPlan;
11use crate::skills::agent_dir;
12
13pub fn preview_plan(plan: &ExecutionPlan) -> Result<InstallPreview, ForgeError> {
19 preview_install(&from_plan(plan))
20}
21
22pub(crate) fn preview_install(config: &InstallConfig) -> Result<InstallPreview, ForgeError> {
23 prepare_detection_environment(config);
26 let tool_status = check_tools_bounded(&config.tools)?;
27
28 let mut skill_status = Vec::new();
29 for skill in &config.skills {
30 for agent in &skill.agents {
31 let agent_dir = agent_dir(*agent);
32 let installed = agent_dir
33 .as_ref()
34 .is_some_and(|root| root.join(&skill.name).join("SKILL.md").is_file());
35 skill_status.push(SkillStatus {
36 name: skill.name.clone(),
37 display_name: skill.display_name.clone(),
38 optional: skill.optional,
39 agent: *agent,
40 agent_dir,
41 installed,
42 installable: !skill.source.trim().is_empty(),
43 });
44 }
45 }
46 Ok(InstallPreview {
47 tools: tool_status,
48 skills: skill_status,
49 })
50}
51
52pub(crate) fn check_tools_bounded(tools: &[ToolDef]) -> Result<Vec<ToolStatus>, ForgeError> {
53 if tools.len() <= 1 {
54 return Ok(tools.iter().map(check_tool).collect());
55 }
56 let workers = thread::available_parallelism()
57 .map_or(2, usize::from)
58 .clamp(1, 4)
59 .min(tools.len());
60 let chunk_size = tools.len().div_ceil(workers);
61 let mut indexed = thread::scope(|scope| -> Result<Vec<_>, ForgeError> {
62 let handles = tools
63 .chunks(chunk_size)
64 .enumerate()
65 .map(|(chunk_index, chunk)| {
66 scope.spawn(move || {
67 chunk
68 .iter()
69 .enumerate()
70 .map(|(offset, tool)| (chunk_index * chunk_size + offset, check_tool(tool)))
71 .collect::<Vec<_>>()
72 })
73 })
74 .collect::<Vec<_>>();
75 let mut indexed = Vec::with_capacity(tools.len());
76 for handle in handles {
77 indexed.extend(
78 handle
79 .join()
80 .map_err(|_| ForgeError::Command("tool check worker panicked".into()))?,
81 );
82 }
83 Ok(indexed)
84 })?;
85 indexed.sort_by_key(|(index, _)| *index);
86 Ok(indexed.into_iter().map(|(_, status)| status).collect())
87}