use std::path::Path;
use crate::core::instruction_pipeline::{
AGENT_DELEGATION, BASE_PM, PM_INSTRUCTIONS, SECTION_SEPARATOR, WORKFLOW,
};
pub const OVERRIDE_DIR_NAME: &str = ".trusty-mpm";
pub const FILE_PM_DEPLOYED: &str = "PM_INSTRUCTIONS_DEPLOYED.md";
pub const FILE_AGENT_DELEGATION: &str = "AGENT_DELEGATION.md";
pub const FILE_WORKFLOW: &str = "WORKFLOW.md";
pub const FILE_MEMORY: &str = "MEMORY.md";
pub const FILE_INSTRUCTIONS: &str = "INSTRUCTIONS.md";
const MEMORY_OVERRIDE_HEADING: &str = "## Memory Behavior (project override)";
fn read_override(dir: &Path, name: &str) -> Option<String> {
let path = dir.join(name);
match std::fs::read_to_string(&path) {
Ok(text) => {
let trimmed = text.trim();
if trimmed.is_empty() {
tracing::warn!(
path = %path.display(),
"instruction override file is empty; using bundled default"
);
None
} else {
tracing::info!(path = %path.display(), "applying instruction override");
Some(trimmed.to_string())
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => {
tracing::warn!(
path = %path.display(),
%err,
"instruction override file unreadable; using bundled default"
);
None
}
}
}
pub fn resolve_pm_prompt(project_dir: &Path) -> String {
let dir = project_dir.join(OVERRIDE_DIR_NAME);
let floor = BASE_PM;
if let Some(body) = read_override(&dir, FILE_PM_DEPLOYED) {
let mut sections: Vec<String> = vec![body];
if let Some(extra) = read_override(&dir, FILE_INSTRUCTIONS) {
sections.push(extra);
}
sections.push(floor.trim().to_string());
return join_sections(sections);
}
let workflow =
read_override(&dir, FILE_WORKFLOW).unwrap_or_else(|| WORKFLOW.trim().to_string());
let delegation = read_override(&dir, FILE_AGENT_DELEGATION)
.unwrap_or_else(|| AGENT_DELEGATION.trim().to_string());
let mut sections: Vec<String> = vec![PM_INSTRUCTIONS.trim().to_string()];
if let Some(memory) = read_override(&dir, FILE_MEMORY) {
sections.push(format!("{MEMORY_OVERRIDE_HEADING}\n\n{memory}"));
}
sections.push(workflow);
sections.push(delegation);
if let Some(extra) = read_override(&dir, FILE_INSTRUCTIONS) {
sections.push(extra);
}
sections.push(floor.trim().to_string());
join_sections(sections)
}
fn join_sections(sections: Vec<String>) -> String {
sections
.iter()
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join(SECTION_SEPARATOR)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
fn write_override(project: &Path, name: &str, content: &str) {
let dir = project.join(OVERRIDE_DIR_NAME);
fs::create_dir_all(&dir).expect("create .trusty-mpm");
fs::write(dir.join(name), content).expect("write override");
}
#[test]
fn no_overrides_uses_bundled() {
let tmp = TempDir::new().unwrap();
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# PM Workflow Configuration"));
assert!(prompt.contains("# Agent Delegation Routing"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
let delegation = prompt.find("# Agent Delegation Routing").expect("deleg");
assert!(base > delegation, "BASE_PM floor must be last");
}
#[test]
fn instructions_appended() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_INSTRUCTIONS,
"# Project Rules\n\nALWAYS_RUN_MAKE_CHECK\n",
);
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("ALWAYS_RUN_MAKE_CHECK"));
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# Agent Delegation Routing"));
let extra = prompt.find("ALWAYS_RUN_MAKE_CHECK").expect("extra");
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
assert!(extra < base, "INSTRUCTIONS.md precedes the BASE_PM floor");
}
#[test]
fn workflow_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_WORKFLOW,
"# Custom Workflow\n\nTWO_PHASE_ONLY\n",
);
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("TWO_PHASE_ONLY"));
assert!(
!prompt.contains("# PM Workflow Configuration"),
"bundled workflow heading must be replaced"
);
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# Agent Delegation Routing"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
}
#[test]
fn agent_delegation_override_replaces() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_AGENT_DELEGATION,
"# Custom Routing\n\nROUTE_ALL_TO_ENGINEER\n",
);
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("ROUTE_ALL_TO_ENGINEER"));
assert!(
!prompt.contains("# Agent Delegation Routing"),
"bundled delegation heading must be replaced"
);
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# PM Workflow Configuration"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
}
#[test]
fn memory_override_is_slotted_after_pm_instructions() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_MEMORY,
"Recall from the `team` palace before any task.\n",
);
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains(MEMORY_OVERRIDE_HEADING));
assert!(prompt.contains("Recall from the `team` palace"));
let pm = prompt.find("# PM Agent -- Claude MPM").expect("pm");
let mem = prompt.find(MEMORY_OVERRIDE_HEADING).expect("mem");
let wf = prompt.find("# PM Workflow Configuration").expect("wf");
assert!(pm < mem, "memory block follows PM_INSTRUCTIONS");
assert!(mem < wf, "memory block precedes the workflow section");
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
assert!(wf < base);
}
#[test]
fn pm_deployed_replaces_body_but_keeps_base_floor() {
let tmp = TempDir::new().unwrap();
write_override(
tmp.path(),
FILE_PM_DEPLOYED,
"# Wholly Custom PM\n\nDO_EXACTLY_THIS\n",
);
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("DO_EXACTLY_THIS"));
assert!(
prompt.contains("# BASE_PM Framework Floor"),
"BASE_PM floor must always be appended"
);
assert!(prompt.contains("## Trusty Tool Priority (Non-Overridable)"));
assert!(!prompt.contains("# PM Agent -- Claude MPM"));
assert!(!prompt.contains("# PM Workflow Configuration"));
assert!(!prompt.contains("# Agent Delegation Routing"));
let body = prompt.find("DO_EXACTLY_THIS").expect("body");
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
assert!(body < base, "BASE_PM floor must come after the custom body");
}
#[test]
fn pm_deployed_still_appends_instructions() {
let tmp = TempDir::new().unwrap();
write_override(tmp.path(), FILE_PM_DEPLOYED, "CUSTOM_BODY\n");
write_override(tmp.path(), FILE_INSTRUCTIONS, "PROJECT_ADDENDUM\n");
let prompt = resolve_pm_prompt(tmp.path());
let body = prompt.find("CUSTOM_BODY").expect("body");
let addendum = prompt.find("PROJECT_ADDENDUM").expect("addendum");
let base = prompt.find("# BASE_PM Framework Floor").expect("base");
assert!(body < addendum && addendum < base);
assert!(!prompt.contains("# PM Workflow Configuration"));
}
#[test]
fn missing_override_dir_uses_bundled() {
let tmp = TempDir::new().unwrap();
assert!(!tmp.path().join(OVERRIDE_DIR_NAME).exists());
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("# PM Agent -- Claude MPM"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
}
#[test]
fn empty_override_falls_back() {
let tmp = TempDir::new().unwrap();
write_override(tmp.path(), FILE_WORKFLOW, " \n\t\n");
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("# PM Workflow Configuration"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
}
#[test]
fn unreadable_override_falls_back() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().join(OVERRIDE_DIR_NAME);
fs::create_dir_all(&dir).unwrap();
fs::create_dir(dir.join(FILE_WORKFLOW)).unwrap();
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains("# PM Workflow Configuration"));
assert!(prompt.contains("# BASE_PM Framework Floor"));
}
#[test]
fn separators_are_consistent() {
let tmp = TempDir::new().unwrap();
let prompt = resolve_pm_prompt(tmp.path());
assert!(prompt.contains(SECTION_SEPARATOR));
}
}