use crate::config::AppConfig;
use cersei_agent::system_prompt::{
build_system_prompt, GitSnapshot, OutputStyle, SystemPromptOptions, SystemPromptPrefix,
};
use cersei_memory::manager::MemoryManager;
pub fn build_cli_system_prompt(
config: &AppConfig,
memory_manager: &MemoryManager,
model: &str,
) -> String {
if config.benchmark_mode {
return build_benchmark_prompt(model, &config.working_dir);
}
let memory_content = memory_manager.build_context();
let git_status = build_git_snapshot(&config.working_dir);
let now = chrono::Local::now();
let extra_dynamic = vec![(
"environment".to_string(),
format!(
"Model: {}\nPlatform: {} {}\nShell: {}\nWorking directory: {}\nGit repo: {}\nDate: {}",
model,
std::env::consts::OS,
std::env::consts::ARCH,
std::env::var("SHELL").unwrap_or_else(|_| "unknown".into()),
config.working_dir.display(),
if git_status.is_some() { "yes" } else { "no" },
now.format("%Y-%m-%d %H:%M %Z"),
),
)];
let mut extra_cached: Vec<(String, String)> = Vec::new();
let instruction_files = collect_instruction_files(&config.working_dir);
for (path_label, content) in instruction_files {
extra_cached.push((
"project_instructions".to_string(),
format!("# From: {}\n{}", path_label, content),
));
}
let project_intel = cersei_tools::tool_primitives::code_intel::scan_project(
&config.working_dir,
20, );
if !project_intel.is_empty() {
let intel_summary =
cersei_tools::tool_primitives::code_intel::format_project_intel(&project_intel);
extra_cached.push((
"project_intel".to_string(),
format!(
"Project structure (top {} files by importance — symbols and imports extracted via tree-sitter):\n{}",
project_intel.len(),
intel_summary
),
));
}
let tools_available: Vec<String> = cersei_tools::all()
.iter()
.map(|t| t.name().to_string())
.collect();
let opts = SystemPromptOptions {
prefix: Some(SystemPromptPrefix::Interactive),
output_style: OutputStyle::from_str(&config.output_style),
working_directory: Some(config.working_dir.display().to_string()),
memory_content,
extra_cached_sections: extra_cached,
extra_dynamic_sections: extra_dynamic,
has_auto_compact: config.auto_compact,
has_memory: config.graph_memory,
tools_available,
git_status,
..Default::default()
};
build_system_prompt(&opts)
}
fn build_git_snapshot(working_dir: &std::path::Path) -> Option<GitSnapshot> {
use std::process::Command;
let check = Command::new("git")
.args(["rev-parse", "--is-inside-work-tree"])
.current_dir(working_dir)
.output()
.ok()?;
if !check.status.success() {
return None;
}
let branch = Command::new("git")
.args(["branch", "--show-current"])
.current_dir(working_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "detached".into());
let user = Command::new("git")
.args(["config", "user.name"])
.current_dir(working_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty());
let status_lines: Vec<String> = Command::new("git")
.args(["status", "--short"])
.current_dir(working_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| {
s.lines()
.filter(|l| !l.is_empty())
.map(String::from)
.collect()
})
.unwrap_or_default();
let recent_commits: Vec<String> = Command::new("git")
.args(["log", "--oneline", "-5"])
.current_dir(working_dir)
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| {
s.lines()
.filter(|l| !l.is_empty())
.map(String::from)
.collect()
})
.unwrap_or_default();
Some(GitSnapshot {
branch,
recent_commits,
status_lines,
user,
})
}
fn collect_instruction_files(working_dir: &std::path::Path) -> Vec<(String, String)> {
use std::path::Path;
const INSTRUCTION_FILES: &[&str] = &[
"AGENTS.md",
"CLAUDE.md",
"CONTEXT.md",
".abstract/instructions.md",
];
let mut found: Vec<(String, String)> = Vec::new();
let mut current = working_dir.to_path_buf();
loop {
for filename in INSTRUCTION_FILES {
let path = current.join(filename);
if path.exists() {
if let Ok(content) = std::fs::read_to_string(&path) {
if !content.trim().is_empty() {
let label = path
.strip_prefix(working_dir)
.map(|p| p.display().to_string())
.unwrap_or_else(|_| path.display().to_string());
found.push((label, content));
}
}
}
}
if !current.pop() {
break;
}
}
found.reverse();
found
}
fn build_file_tree(working_dir: &std::path::Path, max_files: usize) -> Option<String> {
use std::process::Command;
let git_output = Command::new("git")
.args(["ls-files", "--cached", "--others", "--exclude-standard"])
.current_dir(working_dir)
.output()
.ok();
if let Some(output) = git_output {
if output.status.success() {
let files: String = String::from_utf8_lossy(&output.stdout)
.lines()
.take(max_files)
.collect::<Vec<_>>()
.join("\n");
if !files.is_empty() {
let total = String::from_utf8_lossy(&output.stdout).lines().count();
let mut result = files;
if total > max_files {
result.push_str(&format!(
"\n\n({total} files total, showing first {max_files})"
));
}
return Some(result);
}
}
}
let excluded = [
"node_modules",
"target",
".git",
"__pycache__",
".venv",
"venv",
"dist",
"build",
".next",
];
let mut files = Vec::new();
fn walk(
dir: &std::path::Path,
base: &std::path::Path,
excluded: &[&str],
files: &mut Vec<String>,
max: usize,
) {
if files.len() >= max {
return;
}
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(_) => return,
};
for entry in entries.flatten() {
if files.len() >= max {
return;
}
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with('.') || excluded.contains(&name.as_str()) {
continue;
}
if path.is_file() {
if let Ok(rel) = path.strip_prefix(base) {
files.push(rel.display().to_string());
}
} else if path.is_dir() {
walk(&path, base, excluded, files, max);
}
}
}
walk(working_dir, working_dir, &excluded, &mut files, max_files);
files.sort();
if files.is_empty() {
None
} else {
Some(files.join("\n"))
}
}
fn build_benchmark_prompt(model: &str, working_dir: &std::path::Path) -> String {
let wd = working_dir.display();
let mut prompt = format!(
r#"You are a coding agent inside a Docker container. Your ONLY job is to complete the task correctly. NEVER explain or narrate — only run commands and write code.
Model: {model}
Working directory: {wd}
## PHASE 1: RECON (always do this first, ALL calls in parallel)
In your FIRST response, make ALL of these tool calls IN PARALLEL:
1. Bash: `ls -laR {wd}/ 2>/dev/null | head -60`
2. Bash: `find {wd} -type f -name "*.py" -o -name "*.sh" -o -name "*.c" -o -name "*.rs" -o -name "*.js" -o -name "*.toml" -o -name "*.yaml" -o -name "*.json" -o -name "Makefile" 2>/dev/null | head -30 | xargs cat 2>/dev/null | head -300`
3. Bash: `cat {wd}/README* {wd}/*.md {wd}/*.txt 2>/dev/null | head -150`
## PHASE 2: PLAN (mandatory before coding)
After reading the files, make a mental plan:
- What EXACTLY does the task require? What files/outputs must exist?
- What existing code/data is already provided? What must you build?
- If the instruction mentions a test or verification command, note it.
- What's the simplest approach that could work?
## PHASE 3: IMPLEMENT
- Write the SIMPLEST solution that satisfies ALL task requirements.
- ALWAYS read existing files completely before modifying them.
- Use parallel tool calls when operations are independent.
- Install dependencies with `pip install` or `apt-get` as needed.
## PHASE 4: VERIFY
- If the instruction mentions a verification command (e.g. "run test_outputs.py"), run it NOW.
- If no test command: verify by running your solution and checking the output yourself.
- Check that ALL expected output files exist with correct content.
- Re-read the original instruction one more time — did you miss anything?
## PHASE 5: ERROR RECOVERY (if something fails)
When a command or test fails, you MUST:
1. Read the FULL error output — errors are often at the end.
2. Identify the ROOT CAUSE — what specifically went wrong?
3. Think about WHY it happened — wrong assumption? Missing dependency? Wrong format?
4. Fix with a TARGETED change if the approach is sound, OR try a COMPLETELY DIFFERENT approach if the logic is wrong.
Do NOT blindly retry the same command. Do NOT skip this reflection.
## RULES
- NEVER explain. NEVER narrate. NEVER ask questions. Only code and commands.
- Use parallel tool calls whenever operations are independent.
- If output is too long, use `| tail -50` or `| head -50` to see relevant parts.
- If stuck: simplify. The simplest interpretation of the task is usually correct.
- Speed matters. Don't over-engineer.
- If installing packages, prefer `pip install` over building from source.
- For long-running operations (training, compilation), monitor progress with periodic checks.
- Do NOT look for or try to run /tests/run-tests.sh — tests are run externally after you finish.
- Focus all effort on producing correct output in {wd}.
- ALWAYS verify your solution works before finishing.
"#
);
if let Ok(patterns) = std::env::var("ABSTRACT_FAILURE_PATTERNS") {
if !patterns.is_empty() {
prompt.push_str("\n## LEARNED PATTERNS (from previous runs — avoid these mistakes)\n");
prompt.push_str(&patterns);
prompt.push('\n');
}
}
prompt
}