fix(skills): inject skill list into system prompt for LLM awareness

Problem: Agent could not invoke appropriate skills when user asked about
financial reports because LLM didn't know which skills were available.

Root causes:
1. System prompt lacked available skill list
2. SkillManifest struct missing 'triggers' field
3. SKILL.md loader not parsing triggers list
4. "财报" keyword not matching "财务报告" trigger

Changes:
- Add triggers field to SkillManifest struct
- Parse triggers list from SKILL.md frontmatter
- Inject skill list into system prompt in kernel.rs
- Add "财报", "财务数据", "盈利", "营收" triggers to finance-tracker
- Add "财报分析" trigger to analytics-reporter
- Document fix in troubleshooting.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-24 15:39:18 +08:00
parent 504d5746aa
commit 9981a4674e
6 changed files with 256 additions and 13 deletions

View File

@@ -41,6 +41,8 @@ pub fn parse_skill_md(content: &str) -> Result<SkillManifest> {
let mut mode = SkillMode::PromptOnly;
let mut capabilities = Vec::new();
let mut tags = Vec::new();
let mut triggers = Vec::new();
let mut in_triggers_list = false;
// Parse frontmatter if present
if content.starts_with("---") {
@@ -51,6 +53,15 @@ pub fn parse_skill_md(content: &str) -> Result<SkillManifest> {
if line.is_empty() || line == "---" {
continue;
}
// Handle triggers list items
if in_triggers_list && line.starts_with("- ") {
triggers.push(line[2..].trim().trim_matches('"').to_string());
continue;
} else {
in_triggers_list = false;
}
if let Some((key, value)) = line.split_once(':') {
let key = key.trim();
let value = value.trim().trim_matches('"');
@@ -69,6 +80,16 @@ pub fn parse_skill_md(content: &str) -> Result<SkillManifest> {
.map(|s| s.trim().to_string())
.collect();
}
"triggers" => {
// Check if it's a list on next lines or inline
if value.is_empty() {
in_triggers_list = true;
} else {
triggers = value.split(',')
.map(|s| s.trim().trim_matches('"').to_string())
.collect();
}
}
_ => {}
}
}
@@ -137,6 +158,7 @@ pub fn parse_skill_md(content: &str) -> Result<SkillManifest> {
input_schema: None,
output_schema: None,
tags,
triggers,
enabled: true,
})
}
@@ -159,6 +181,7 @@ pub fn parse_skill_toml(content: &str) -> Result<SkillManifest> {
let mut mode = "prompt_only".to_string();
let mut capabilities = Vec::new();
let mut tags = Vec::new();
let mut triggers = Vec::new();
for line in content.lines() {
let line = line.trim();
@@ -189,6 +212,13 @@ pub fn parse_skill_toml(content: &str) -> Result<SkillManifest> {
.filter(|s| !s.is_empty())
.collect();
}
"triggers" => {
let value = value.trim_start_matches('[').trim_end_matches(']');
triggers = value.split(',')
.map(|s| s.trim().trim_matches('"').to_string())
.filter(|s| !s.is_empty())
.collect();
}
_ => {}
}
}
@@ -215,6 +245,7 @@ pub fn parse_skill_toml(content: &str) -> Result<SkillManifest> {
input_schema: None,
output_schema: None,
tags,
triggers,
enabled: true,
})
}