mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-11-01 07:02:30 +00:00
- Extract shared test infrastructure (MockExecutionEnvironment, TestProfile, MockLlmProvider) replacing 11 duplicate mock implementations across tests - Deduplicate tool execution logic between sequential and parallel paths - Narrow ProviderProfile trait from 14 to 7 required methods via ProfileCapabilities struct and default implementations - Replace stringly-typed HashMap event data with typed EventData enum - Extract shared assemble_system_prompt helper and register_subagent_tools default method, eliminating copy-paste across all 3 profiles - Replace fragile shell-based glob with glob crate, fix rg detection - Add delete_file to ExecutionEnvironment, wire git context into env block - Remove dead code (AgentError::Io, count_turns, trivial derived-trait tests) - Use match-based lookups in truncation instead of per-call HashMap allocation Net reduction: -1,401 lines across 20 files. All 180 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
4.3 KiB
Rust
135 lines
4.3 KiB
Rust
pub mod anthropic;
|
|
pub mod gemini;
|
|
pub mod openai;
|
|
|
|
pub use anthropic::AnthropicProfile;
|
|
pub use gemini::GeminiProfile;
|
|
pub use openai::OpenAiProfile;
|
|
|
|
use crate::execution_env::ExecutionEnvironment;
|
|
|
|
/// Additional context for building environment blocks
|
|
#[derive(Default)]
|
|
pub struct EnvContext {
|
|
pub git_branch: Option<String>,
|
|
pub is_git_repo: bool,
|
|
pub date: String,
|
|
pub model_name: String,
|
|
pub knowledge_cutoff: String,
|
|
pub git_status_short: Option<String>,
|
|
pub git_recent_commits: Option<String>,
|
|
}
|
|
|
|
/// Assembles a complete system prompt from a core prompt template and standard sections.
|
|
///
|
|
/// The `core_prompt` should contain `{env_block}` as a placeholder where the environment
|
|
/// context block will be inserted. Project docs and user instructions are appended at the end.
|
|
#[must_use]
|
|
pub fn assemble_system_prompt(
|
|
core_prompt: &str,
|
|
env: &dyn ExecutionEnvironment,
|
|
env_context: &EnvContext,
|
|
project_docs: &[String],
|
|
user_instructions: Option<&str>,
|
|
) -> String {
|
|
let env_block = build_env_context_block_with(env, env_context);
|
|
let docs_section = if project_docs.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!("\n\n{}", project_docs.join("\n\n"))
|
|
};
|
|
let user_section = match user_instructions {
|
|
Some(instructions) => format!("\n\n# User Instructions\n{instructions}"),
|
|
None => String::new(),
|
|
};
|
|
|
|
let prompt = core_prompt.replace("{env_block}", &env_block);
|
|
format!("{prompt}{docs_section}{user_section}")
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn build_env_context_block(env: &dyn ExecutionEnvironment) -> String {
|
|
build_env_context_block_with(env, &EnvContext::default())
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn build_env_context_block_with(env: &dyn ExecutionEnvironment, ctx: &EnvContext) -> String {
|
|
let mut lines = vec![
|
|
"<environment>".to_string(),
|
|
format!("Working directory: {}", env.working_directory()),
|
|
format!("Is git repository: {}", ctx.is_git_repo),
|
|
];
|
|
|
|
if let Some(ref branch) = ctx.git_branch {
|
|
lines.push(format!("Git branch: {branch}"));
|
|
}
|
|
|
|
lines.push(format!("Platform: {}", env.platform()));
|
|
lines.push(format!("OS version: {}", env.os_version()));
|
|
|
|
if !ctx.date.is_empty() {
|
|
lines.push(format!("Today's date: {}", ctx.date));
|
|
}
|
|
if !ctx.model_name.is_empty() {
|
|
lines.push(format!("Model: {}", ctx.model_name));
|
|
}
|
|
if !ctx.knowledge_cutoff.is_empty() {
|
|
lines.push(format!("Knowledge cutoff: {}", ctx.knowledge_cutoff));
|
|
}
|
|
|
|
if let Some(ref status) = ctx.git_status_short {
|
|
lines.push(format!("Git status:\n{status}"));
|
|
}
|
|
if let Some(ref commits) = ctx.git_recent_commits {
|
|
lines.push(format!("Recent commits:\n{commits}"));
|
|
}
|
|
|
|
lines.push("</environment>".to_string());
|
|
lines.join("\n")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::test_support::MockExecutionEnvironment;
|
|
|
|
fn linux_env() -> MockExecutionEnvironment {
|
|
MockExecutionEnvironment {
|
|
working_dir: "/home/test",
|
|
platform_str: "linux",
|
|
os_version_str: "Linux 6.1.0".into(),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn env_context_block_contains_platform() {
|
|
let env = linux_env();
|
|
let block = build_env_context_block(&env);
|
|
assert!(block.contains("<environment>"));
|
|
assert!(block.contains("</environment>"));
|
|
assert!(block.contains("linux"));
|
|
assert!(block.contains("/home/test"));
|
|
assert!(block.contains("Linux 6.1.0"));
|
|
}
|
|
|
|
#[test]
|
|
fn env_context_block_with_extra_context() {
|
|
let env = linux_env();
|
|
let ctx = EnvContext {
|
|
git_branch: Some("main".into()),
|
|
is_git_repo: true,
|
|
date: "2026-02-20".into(),
|
|
model_name: "claude-opus-4-6".into(),
|
|
knowledge_cutoff: "May 2025".into(),
|
|
git_status_short: None,
|
|
git_recent_commits: None,
|
|
};
|
|
let block = build_env_context_block_with(&env, &ctx);
|
|
assert!(block.contains("Git branch: main"));
|
|
assert!(block.contains("Is git repository: true"));
|
|
assert!(block.contains("Today's date: 2026-02-20"));
|
|
assert!(block.contains("Model: claude-opus-4-6"));
|
|
assert!(block.contains("Knowledge cutoff: May 2025"));
|
|
}
|
|
}
|