Add agent-specific language prompt injection

When running in agent mode (e.g., --agent carmack) in a workspace with
detected languages, inject agent+language-specific prompts from
prompts/langs/<agent>.<lang>.md at the end of the system prompt.

Changes:
- Add AGENT_LANGUAGE_PROMPTS static array for compile-time embedding
- Add get_agent_language_prompt() to look up specific agent+lang combos
- Add get_agent_language_prompts_for_workspace_with_langs() that returns
  both content and matched languages for display
- Update agent_mode.rs to inject prompts and show which languages loaded
- Display format: '✓ carmack: racket language guidance'
- Add tests for new functionality

Uses the same detect_languages() mechanism as regular language prompts
to avoid code-path aliasing.
This commit is contained in:
Dhanji R. Prasanna
2026-01-15 06:43:29 +05:30
parent eefc067aae
commit 5d8dbc43f8
2 changed files with 98 additions and 3 deletions

View File

@@ -18,6 +18,13 @@ static LANGUAGE_PROMPTS: &[(&str, &[&str], &str)] = &[
),
];
/// Embedded agent-specific language prompts.
/// Format: (agent_name, language_name, prompt_content)
static AGENT_LANGUAGE_PROMPTS: &[(&str, &str, &str)] = &[
// (agent_name, language_name, prompt_content)
("carmack", "racket", include_str!("../../../prompts/langs/carmack.racket.md")),
];
/// Detect languages present in the workspace by scanning for file extensions.
/// Returns a list of detected language names.
pub fn detect_languages(workspace_dir: &Path) -> Vec<&'static str> {
@@ -118,6 +125,46 @@ pub fn list_available_languages() -> Vec<&'static str> {
LANGUAGE_PROMPTS.iter().map(|(name, _, _)| *name).collect()
}
/// Get agent-specific language prompt for a specific agent and language.
pub fn get_agent_language_prompt(agent_name: &str, lang: &str) -> Option<&'static str> {
AGENT_LANGUAGE_PROMPTS
.iter()
.find(|(agent, language, _)| *agent == agent_name && *language == lang)
.map(|(_, _, content)| *content)
}
/// Get agent-specific language prompts for detected languages in the workspace.
/// Returns formatted content ready for injection into the agent's system prompt.
#[allow(dead_code)]
pub fn get_agent_language_prompts_for_workspace(
workspace_dir: &Path,
agent_name: &str,
) -> Option<String> {
let (content, _) = get_agent_language_prompts_for_workspace_with_langs(workspace_dir, agent_name);
content
}
/// Get agent-specific language prompts for detected languages in the workspace.
/// Returns both the formatted content and the list of languages that had matching prompts.
pub fn get_agent_language_prompts_for_workspace_with_langs(
workspace_dir: &Path,
agent_name: &str,
) -> (Option<String>, Vec<&'static str>) {
let detected = detect_languages(workspace_dir);
let mut prompts = Vec::new();
let mut matched_langs = Vec::new();
for lang in detected {
if let Some(content) = get_agent_language_prompt(agent_name, lang) {
prompts.push(content.to_string());
matched_langs.push(lang);
}
}
let content = if prompts.is_empty() { None } else { Some(prompts.join("\n\n---\n\n")) };
(content, matched_langs)
}
#[cfg(test)]
mod tests {
use super::*;
@@ -166,4 +213,29 @@ mod tests {
assert!(content.contains("🔧 Language-Specific Guidance"));
assert!(content.contains("raco"));
}
#[test]
fn test_carmack_racket_prompt_embedded() {
let prompt = get_agent_language_prompt("carmack", "racket");
assert!(prompt.is_some());
assert!(prompt.unwrap().contains("RACKET-SPECIFIC GUIDANCE"));
}
#[test]
fn test_agent_language_prompt_not_found() {
let prompt = get_agent_language_prompt("nonexistent", "racket");
assert!(prompt.is_none());
}
#[test]
fn test_get_agent_prompts_for_workspace() {
let temp_dir = TempDir::new().unwrap();
let rkt_file = temp_dir.path().join("main.rkt");
fs::write(&rkt_file, "#lang racket\n").unwrap();
let prompts = get_agent_language_prompts_for_workspace(temp_dir.path(), "carmack");
assert!(prompts.is_some());
let content = prompts.unwrap();
assert!(content.contains("RACKET-SPECIFIC GUIDANCE"));
}
}