feat(core): always-on INSTRUCTIONS.md in skill directories

A skill directory may hold INSTRUCTIONS.md, with or without SKILL.md. Its body
(frontmatter cut) goes into every system prompt in <skill_instructions>, re-read
on every run: 16 KB per file, 64 KB in total, truncation marked with the file's
path. /skills marks skills with instructions and lists instruction-only dirs.
This commit is contained in:
EugeneTes
2026-10-12 10:15:05 +00:00
parent dccdf3a8ed
commit bb1c534a7a
9 changed files with 253 additions and 25 deletions

View File

@@ -226,9 +226,15 @@ public sealed class ChatCommands(TelegramSender sender, ChatTimeZoneService? zon
private static string Skills(ChatContext chat)
{
var skills = SkillCatalog.Load(chat.Session.Cwd);
var instructions = SkillCatalog.LoadInstructions(chat.Session.Cwd);
var where = $"`{Path.GetRelativePath(chat.Session.Cwd, SkillCatalog.Directory(chat.Session.Cwd))}/<name>/SKILL.md`";
if (skills.Count == 0) return $"No skills yet. Ask me to write one, or put it in {where}.";
return $"**Skills** ({where})\n" + string.Join('\n', skills.Select(s => $"• **{s.Name}** – {s.Description}"));
if (skills.Count == 0 && instructions.Count == 0) return $"No skills yet. Ask me to write one, or put it in {where}.";
static string Mark(SkillInstructions i) => i.Truncated ? " (+ INSTRUCTIONS.md, truncated in the prompt)" : " (+ INSTRUCTIONS.md)";
var byDir = instructions.ToDictionary(i => Path.GetDirectoryName(i.Path)!, StringComparer.Ordinal);
var lines = skills.Select(s => $"• **{s.Name}** – {s.Description}" + (byDir.Remove(Path.GetDirectoryName(s.Path)!, out var i) ? Mark(i) : ""))
.Concat(byDir.Values.Select(i => $"• **{i.Name}** – INSTRUCTIONS.md only" + (i.Truncated ? " (truncated in the prompt)" : "")));
return $"**Skills** ({where})\n" + string.Join('\n', lines);
}
private string Jobs(ChatContext chat)