Keep workspace skills visible when the prompt budget truncates
The skills prompt renderer was re-sorting every discovered skill by name, which discarded workspace/source precedence at the last mile. Under a large global skills set, higher-priority workspace skills from directories such as `.claude/skills` could be pushed past the prompt budget and disappear from the model-visible skills list even though discovery had found them correctly. This keeps stable ordering in discovery and preserves registry order during rendering, then adds a regression test that proves a workspace-priority skill survives when lower-priority global skills overflow the prompt budget. Constraint: Session-time skill rendering must preserve cross-tool/workspace precedence Rejected: Raise the prompt budget cap | would hide the ordering bug and bloat prompts Rejected: Special-case `.claude/skills` during rendering | precedence belongs to registry order, not path-specific branches Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not re-sort rendered skills without re-proving precedence behavior under prompt truncation Tested: cargo test --all-features; cargo fmt --all -- --check; cargo clippy --all-targets --all-features Not-tested: Manual TUI interaction beyond automated skills prompt and QA PTY coverage
This commit is contained in:
@@ -113,6 +113,9 @@ impl SkillRegistry {
|
||||
let mut visited = HashSet::new();
|
||||
Self::discover_recursive(dir, 0, &mut registry, &mut visited);
|
||||
registry
|
||||
.skills
|
||||
.sort_by(|a, b| a.name.cmp(&b.name).then_with(|| a.path.cmp(&b.path)));
|
||||
registry
|
||||
}
|
||||
|
||||
fn discover_recursive(
|
||||
@@ -496,9 +499,6 @@ fn render_skills_block(registry: &SkillRegistry) -> Option<String> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut skills = registry.list().to_vec();
|
||||
skills.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
let mut out = String::new();
|
||||
out.push_str("## Skills\n");
|
||||
out.push_str(
|
||||
@@ -510,7 +510,7 @@ instructions when using a specific skill.\n\n",
|
||||
out.push_str("### Available skills\n");
|
||||
|
||||
let mut omitted = 0usize;
|
||||
for skill in skills {
|
||||
for skill in registry.list() {
|
||||
// Use the real on-disk path captured at discovery — the directory
|
||||
// name can differ from the frontmatter `name` for community
|
||||
// installs, in which case `<dir>/<name>/SKILL.md` would not exist
|
||||
@@ -770,6 +770,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_skills_block_preserves_registry_precedence_under_prompt_budget() {
|
||||
let tmpdir = TempDir::new().unwrap();
|
||||
let mut registry = super::SkillRegistry::default();
|
||||
registry.skills.push(super::Skill {
|
||||
name: "workspace-priority".to_string(),
|
||||
description: "must survive truncation".to_string(),
|
||||
body: "body".to_string(),
|
||||
path: tmpdir
|
||||
.path()
|
||||
.join(".claude")
|
||||
.join("skills")
|
||||
.join("workspace-priority")
|
||||
.join("SKILL.md"),
|
||||
});
|
||||
|
||||
let big_desc = "y".repeat(super::MAX_SKILL_DESCRIPTION_CHARS - 20);
|
||||
for i in 0..200 {
|
||||
registry.skills.push(super::Skill {
|
||||
name: format!("aaa-global-{i:03}"),
|
||||
description: big_desc.clone(),
|
||||
body: "body".to_string(),
|
||||
path: tmpdir
|
||||
.path()
|
||||
.join(".deepseek")
|
||||
.join("skills")
|
||||
.join(format!("aaa-global-{i:03}"))
|
||||
.join("SKILL.md"),
|
||||
});
|
||||
}
|
||||
|
||||
let rendered = super::render_skills_block(®istry).expect("skill context");
|
||||
assert!(
|
||||
rendered.contains("workspace-priority"),
|
||||
"higher-precedence workspace skills must not be reordered behind globals:\n{rendered}"
|
||||
);
|
||||
assert!(
|
||||
rendered.contains("additional skills omitted from this prompt budget"),
|
||||
"fixture should exceed prompt budget"
|
||||
);
|
||||
}
|
||||
|
||||
fn write_skill(dir: &std::path::Path, name: &str, description: &str, body: &str) {
|
||||
let skill_dir = dir.join(name);
|
||||
std::fs::create_dir_all(&skill_dir).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user