Skip to content

Commit ee21a4f

Browse files
committed
Refactor prompts into SystemPrompt struct
- Merge prompts module with SystemPrompt struct for cleaner organization - Move mdsh processing from app.rs into prompts.rs - Add SystemPrompt::new() and SystemPrompt::build() methods - Keep all prompt constants in one place
1 parent 2d7c199 commit ee21a4f

2 files changed

Lines changed: 98 additions & 63 deletions

File tree

src/app.rs

Lines changed: 4 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ use ratatui::{
1717
use tokio::sync::oneshot;
1818

1919
use crate::commands::Command;
20-
use crate::config::{AgentRuntimeConfig, Config, CODEY_DIR};
20+
use crate::config::{AgentRuntimeConfig, Config};
2121
use crate::ide::{Ide, IdeEvent, Nvim};
2222
use crate::llm::{Agent, AgentId, AgentRegistry, AgentStep, RequestMode};
2323
#[cfg(feature = "profiling")]
2424
use crate::{profile_frame, profile_span};
25-
use crate::prompts::{COMPACTION_PROMPT, SYSTEM_MD_FILENAME, SYSTEM_PROMPT, WELCOME_MESSAGE};
25+
use crate::prompts::{SystemPrompt, COMPACTION_PROMPT, WELCOME_MESSAGE};
2626
use crate::tool_filter::ToolFilters;
2727
use crate::tools::{
2828
init_agent_context, update_agent_oauth, Effect, ToolCall, ToolDecision, ToolEvent,
@@ -36,62 +36,6 @@ const MIN_FRAME_TIME: Duration = Duration::from_millis(16);
3636
pub const APP_NAME: &str = "Codey";
3737
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
3838

39-
/// Process a SYSTEM.md file through mdsh, executing embedded shell commands.
40-
///
41-
/// Returns the processed content, or the original content if processing fails.
42-
fn process_mdsh(content: &str, path: &std::path::Path) -> String {
43-
use mdsh::Processor;
44-
45-
let workdir = path
46-
.parent()
47-
.map(|p| p.as_os_str())
48-
.unwrap_or_else(|| std::ffi::OsStr::new("."));
49-
50-
let mut output = Vec::new();
51-
let mut processor = mdsh::executor::TheProcessor::new(workdir, &mut output);
52-
53-
if let Err(e) = processor.process(content, &mdsh::cli::FileArg::StdHandle) {
54-
tracing::warn!("Failed to process mdsh content from {:?}: {}", path, e);
55-
return content.to_string();
56-
}
57-
58-
String::from_utf8(output).unwrap_or_else(|_| content.to_string())
59-
}
60-
61-
/// Build the complete system prompt by appending content from SYSTEM.md files.
62-
///
63-
/// SYSTEM.md files are processed through mdsh, which executes embedded shell commands.
64-
/// This allows dynamic content like `$ git branch --show-current` to be included.
65-
///
66-
/// Checks (in order):
67-
/// 1. User config: ~/.config/codey/SYSTEM.md
68-
/// 2. Project: .codey/SYSTEM.md
69-
fn build_system_prompt() -> String {
70-
let mut prompt = SYSTEM_PROMPT.to_string();
71-
72-
// Check user config directory: ~/.config/codey/SYSTEM.md
73-
if let Some(config_dir) = Config::config_dir() {
74-
let user_system_md = config_dir.join(SYSTEM_MD_FILENAME);
75-
if let Ok(content) = std::fs::read_to_string(&user_system_md) {
76-
tracing::debug!("Appending user SYSTEM.md from {:?}", user_system_md);
77-
let processed = process_mdsh(&content, &user_system_md);
78-
prompt.push_str("\n\n");
79-
prompt.push_str(&processed);
80-
}
81-
}
82-
83-
// Check project directory: .codey/SYSTEM.md
84-
let project_system_md = std::path::Path::new(CODEY_DIR).join(SYSTEM_MD_FILENAME);
85-
if let Ok(content) = std::fs::read_to_string(&project_system_md) {
86-
tracing::debug!("Appending project SYSTEM.md from {:?}", project_system_md);
87-
let processed = process_mdsh(&content, &project_system_md);
88-
prompt.push_str("\n\n");
89-
prompt.push_str(&processed);
90-
}
91-
92-
prompt
93-
}
94-
9539
/// Result of handling an action
9640
enum ActionResult {
9741
NoOp,
@@ -374,9 +318,10 @@ impl App {
374318
);
375319

376320
// Use dynamic prompt builder so mdsh commands are re-executed on each LLM call
321+
let system_prompt = SystemPrompt::new();
377322
let mut agent = Agent::with_dynamic_prompt(
378323
AgentRuntimeConfig::foreground(&self.config),
379-
Box::new(build_system_prompt),
324+
Box::new(move || system_prompt.build()),
380325
self.oauth.clone(),
381326
self.tool_executor.tools().clone(),
382327
);

src/prompts.rs

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
//! Centralized prompt definitions for Codey
1+
//! Centralized prompt definitions and system prompt management.
22
//!
3-
//! This module contains all system prompts used throughout the application.
3+
//! This module contains all system prompts used throughout the application,
4+
//! as well as the `SystemPrompt` struct for building dynamic prompts.
5+
6+
use std::path::{Path, PathBuf};
7+
8+
use mdsh::Processor;
9+
10+
use crate::config::{Config, CODEY_DIR};
11+
12+
/// Filename for custom system prompt additions
13+
pub const SYSTEM_MD_FILENAME: &str = "SYSTEM.md";
414

515
/// Welcome message shown when the application starts
616
pub const WELCOME_MESSAGE: &str =
@@ -86,5 +96,85 @@ You have read-only access to:
8696
- If you need to suggest changes, describe them clearly for the primary agent to implement
8797
"#;
8898

89-
/// Filename for custom system prompt additions
90-
pub const SYSTEM_MD_FILENAME: &str = "SYSTEM.md";
99+
/// A system prompt builder that supports dynamic content via mdsh.
100+
///
101+
/// The prompt is composed of:
102+
/// 1. The base system prompt (static)
103+
/// 2. User SYSTEM.md from ~/.config/codey/ (optional, dynamic)
104+
/// 3. Project SYSTEM.md from .codey/ (optional, dynamic)
105+
///
106+
/// SYSTEM.md files are processed through [mdsh](https://github.com/zimbatm/mdsh),
107+
/// allowing embedded shell commands to be executed and their output included.
108+
#[derive(Clone)]
109+
pub struct SystemPrompt {
110+
user_path: Option<PathBuf>,
111+
project_path: PathBuf,
112+
}
113+
114+
impl SystemPrompt {
115+
/// Create a new SystemPrompt with default paths.
116+
pub fn new() -> Self {
117+
let user_path = Config::config_dir().map(|d| d.join(SYSTEM_MD_FILENAME));
118+
let project_path = Path::new(CODEY_DIR).join(SYSTEM_MD_FILENAME);
119+
120+
Self {
121+
user_path,
122+
project_path,
123+
}
124+
}
125+
126+
/// Build the complete system prompt.
127+
///
128+
/// This reads and processes all SYSTEM.md files, executing any embedded
129+
/// shell commands via mdsh. The result is the concatenation of:
130+
/// - Base system prompt
131+
/// - User SYSTEM.md content (if exists)
132+
/// - Project SYSTEM.md content (if exists)
133+
pub fn build(&self) -> String {
134+
let mut prompt = SYSTEM_PROMPT.to_string();
135+
136+
// Append user SYSTEM.md if it exists
137+
if let Some(ref user_path) = self.user_path {
138+
if let Ok(content) = std::fs::read_to_string(user_path) {
139+
tracing::debug!("Appending user SYSTEM.md from {:?}", user_path);
140+
let processed = self.process_mdsh(&content, user_path);
141+
prompt.push_str("\n\n");
142+
prompt.push_str(&processed);
143+
}
144+
}
145+
146+
// Append project SYSTEM.md if it exists
147+
if let Ok(content) = std::fs::read_to_string(&self.project_path) {
148+
tracing::debug!("Appending project SYSTEM.md from {:?}", self.project_path);
149+
let processed = self.process_mdsh(&content, &self.project_path);
150+
prompt.push_str("\n\n");
151+
prompt.push_str(&processed);
152+
}
153+
154+
prompt
155+
}
156+
157+
/// Process content through mdsh, executing embedded shell commands.
158+
fn process_mdsh(&self, content: &str, path: &Path) -> String {
159+
let workdir = path
160+
.parent()
161+
.map(|p| p.as_os_str())
162+
.unwrap_or_else(|| std::ffi::OsStr::new("."));
163+
164+
let mut output = Vec::new();
165+
let mut processor = mdsh::executor::TheProcessor::new(workdir, &mut output);
166+
167+
if let Err(e) = processor.process(content, &mdsh::cli::FileArg::StdHandle) {
168+
tracing::warn!("Failed to process mdsh content from {:?}: {}", path, e);
169+
return content.to_string();
170+
}
171+
172+
String::from_utf8(output).unwrap_or_else(|_| content.to_string())
173+
}
174+
}
175+
176+
impl Default for SystemPrompt {
177+
fn default() -> Self {
178+
Self::new()
179+
}
180+
}

0 commit comments

Comments
 (0)