use crate::skills::types::SkillDefinition;
pub fn build_skill_context(skills: &[SkillDefinition]) -> String {
if skills.is_empty() {
tracing::debug!("No skills available, returning empty context");
return String::new();
}
tracing::info!(
"Building skill metadata context from {} skill(s): [{}]",
skills.len(),
skills
.iter()
.map(|s| s.id.as_str())
.collect::<Vec<_>>()
.join(", ")
);
let mut context = String::from("\n\n## Skill System\n");
context.push_str(
"Before producing any user-facing response, you MUST perform a skill applicability check.\n\n",
);
context.push_str("### Mandatory Skill Check\n");
context.push_str(
"1. Evaluate the user's request against ALL available skill descriptions below.\n",
);
context.push_str("2. Decide whether at least one skill clearly and unambiguously applies.\n");
context.push_str("3. Do NOT skip this check.\n\n");
context.push_str("### If A Skill Applies\n");
context.push_str("1. Select EXACTLY ONE skill (prefer the most specific match).\n");
context.push_str(
"2. Call `load_skill` with the selected `skill_id` BEFORE producing your response.\n",
);
context.push_str("3. Follow the loaded SKILL.md instructions precisely.\n");
context.push_str("4. Do NOT respond outside the selected skill's defined workflow unless the instructions explicitly allow it.\n\n");
context.push_str("### If No Skill Applies\n");
context.push_str("1. Proceed normally without loading any skill.\n");
context.push_str(
"2. Do NOT call `load_skill` or `read_skill_resource` when no skill applies.\n\n",
);
context.push_str("### Resource Loading Rules\n");
context.push_str("1. Do NOT preload all skills.\n");
context.push_str("2. Call `load_skill` only after selecting one skill.\n");
context.push_str("3. Use `read_skill_resource` only for auxiliary files after `load_skill`.\n");
context.push_str(
"4. When a resource response has `has_more=true`, continue with `next_offset` until you have enough context.\n\n",
);
context.push_str("### Execution Behavior With Injected Context\n");
context.push_str("1. Treat Bamboo-injected workspace and environment context as already available working context.\n");
context.push_str("2. If injected env variables appear sufficient for a skill workflow, prefer a minimal execution or verification attempt before asking the user for more information.\n");
context.push_str("3. When execution fails, diagnose the concrete failure first and only ask follow-up questions for information that remains genuinely missing after using the injected context and available tools.\n");
context.push_str("4. Do NOT ask the user to re-send env var values that Bamboo has already injected by name unless the value is clearly missing, malformed, or the user must change it.\n\n");
context.push_str("### Available Skills\n");
for skill in skills {
tracing::debug!(
"Adding skill metadata '{}' with {} tool(s)",
skill.id,
skill.tool_refs.len(),
);
context.push_str(&format!("\n**{}** (`{}`)\n", skill.name, skill.id));
context.push_str(&format!("- skill_id: `{}`\n", skill.id));
context.push_str(&format!("- Description: {}\n", skill.description));
if !skill.tool_refs.is_empty() {
context.push_str(&format!(
"- Provides tools: {}\n",
skill.tool_refs.join(", ")
));
}
if skill.compatibility.is_some() {
context.push_str("- Compatibility details are available in the loaded skill payload\n");
}
}
context.push_str("\n### Internal Verification\n");
context.push_str(
"Internally confirm `skill_check_completed=true` before each user-facing response.\n",
);
tracing::info!("Skill metadata context built: {} chars", context.len());
context
}
#[cfg(test)]
mod tests {
use crate::skills::types::SkillDefinition;
use super::build_skill_context;
#[test]
fn build_skill_context_returns_empty_for_empty_input() {
assert!(build_skill_context(&[]).is_empty());
}
#[test]
fn build_skill_context_renders_metadata_only() {
let mut skill = SkillDefinition::new(
"demo-skill",
"Demo Skill",
"A demo skill for testing",
"This detailed prompt should NOT appear in context.", )
.with_tool_ref("read_file");
skill.compatibility = Some("Requires Read and Write tools".to_string());
let context = build_skill_context(&[skill]);
assert!(context.contains("## Skill System"));
assert!(context.contains("Mandatory Skill Check"));
assert!(context.contains("Select EXACTLY ONE skill"));
assert!(context.contains("load_skill"));
assert!(context.contains("read_skill_resource"));
assert!(context.contains("skill_check_completed=true"));
assert!(context.contains("Execution Behavior With Injected Context"));
assert!(context.contains("prefer a minimal execution or verification attempt"));
assert!(context.contains("Do NOT ask the user to re-send env var values"));
assert!(context.contains("Demo Skill"));
assert!(context.contains("demo-skill"));
assert!(context.contains("skill_id: `demo-skill`"));
assert!(context.contains("A demo skill for testing"));
assert!(context.contains("Provides tools: read_file"));
assert!(context.contains("Compatibility details are available in the loaded skill payload"));
assert!(!context.contains("This detailed prompt should NOT appear"));
}
}