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

@@ -8,6 +8,7 @@ use g3_core::ui_writer::UiWriter;
use g3_core::Agent;
use crate::project_files::{combine_project_content, read_agents_config, read_project_memory, read_project_readme};
use crate::language_prompts::{get_language_prompts_for_workspace, get_agent_language_prompts_for_workspace_with_langs};
use crate::simple_output::SimpleOutput;
use crate::embedded_agents::load_agent_prompt;
use crate::ui_writer_impl::ConsoleUiWriter;
@@ -137,16 +138,38 @@ pub async fn run_agent_mode(
"·"
};
output.print(&format!(
" {} README | {} AGENTS.md | {} Memory",
readme_status, agents_status, memory_status
" {} README {} AGENTS.md {} Memory",
readme_status, agents_status, memory_status,
));
// Get language-specific prompts (same mechanism as normal mode)
let language_content = get_language_prompts_for_workspace(&workspace_dir);
// Get agent+language-specific prompts (e.g., carmack.racket.md) and show which languages
let detected_langs = crate::language_prompts::detect_languages(&workspace_dir);
let agent_lang_content = if detected_langs.is_empty() {
None
} else {
let (content, matched_langs) = get_agent_language_prompts_for_workspace_with_langs(&workspace_dir, agent_name);
for lang in matched_langs {
output.print(&format!("{}: {} language guidance", agent_name, lang));
}
content
};
// Append agent+language-specific content to system prompt if available
let system_prompt = if let Some(agent_lang) = agent_lang_content {
format!("{}\n\n{}", system_prompt, agent_lang)
} else {
system_prompt
};
// Combine all content for the agent's context
let combined_content = combine_project_content(
agents_content_opt,
readme_content_opt,
memory_content_opt,
crate::language_prompts::get_language_prompts_for_workspace(&workspace_dir),
language_content,
&workspace_dir,
);

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"));
}
}