feat: Skills execution system (Phase 2B)

- SkillTool: execute skills by name via forked sub-agent
- BundledSkills: 4 built-in skills (verify, debug, review, commit)
- SkillLoader: bundled skills auto-loaded + budget-aware prompt listing
- /skills command: detail view with /skills <name>
- Budget management: 8K char budget, 250 char per-entry cap

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
abel533
2026-04-05 09:39:29 +08:00
co-authored by Copilot
parent cb76b267f1
commit 6088678c4f
5 changed files with 497 additions and 9 deletions
@@ -31,6 +31,24 @@ public class SkillsCommand implements SlashCommand {
SkillLoader loader = new SkillLoader(projectDir);
List<SkillLoader.Skill> skills = loader.loadAll();
// If args provided, show specific skill detail
if (args != null && !args.isBlank()) {
String query = args.strip().toLowerCase();
var match = skills.stream()
.filter(s -> s.name().equalsIgnoreCase(query))
.findFirst();
if (match.isEmpty()) {
match = skills.stream()
.filter(s -> s.name().toLowerCase().contains(query))
.findFirst();
}
if (match.isPresent()) {
return formatSkillDetail(match.get());
}
return AnsiStyle.red(" Skill not found: " + args.strip()) + "\n"
+ AnsiStyle.dim(" Use /skills to list all available skills.");
}
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append(AnsiStyle.bold(" 🎯 Available Skills\n"));
@@ -61,12 +79,43 @@ public class SkillsCommand implements SlashCommand {
if (!skill.whenToUse().isEmpty()) {
sb.append(" ").append(AnsiStyle.dim("When: " + skill.whenToUse())).append("\n");
}
sb.append(" ").append(AnsiStyle.dim("File: " + skill.filePath())).append("\n");
sb.append("\n");
}
sb.append(AnsiStyle.dim(" Total " + skills.size() + " skills\n"));
sb.append(AnsiStyle.dim(" Total " + skills.size() + " skills"));
sb.append(AnsiStyle.dim(" • Use /skills <name> for details\n"));
}
return sb.toString();
}
private String formatSkillDetail(SkillLoader.Skill skill) {
StringBuilder sb = new StringBuilder();
sb.append("\n");
sb.append(AnsiStyle.bold(" 🎯 Skill: " + skill.name())).append("\n");
sb.append(" ").append("".repeat(50)).append("\n\n");
sb.append(" ").append(AnsiStyle.bold("Source: ")).append(skill.source()).append("\n");
if (!skill.description().isEmpty()) {
sb.append(" ").append(AnsiStyle.bold("Description: ")).append(skill.description()).append("\n");
}
if (!skill.whenToUse().isEmpty()) {
sb.append(" ").append(AnsiStyle.bold("When to use: ")).append(skill.whenToUse()).append("\n");
}
sb.append(" ").append(AnsiStyle.bold("File: ")).append(skill.filePath()).append("\n");
sb.append("\n");
// Show content preview
String content = skill.content();
if (content.length() > 500) {
content = content.substring(0, 497) + "...";
}
sb.append(AnsiStyle.dim(" Content:\n"));
for (String line : content.lines().toList()) {
sb.append(AnsiStyle.dim("" + line)).append("\n");
}
sb.append("\n");
sb.append(AnsiStyle.dim(" Tip: Ask AI to execute this skill or type: /verify, /debug, etc.\n"));
return sb.toString();
}
}