formatSkillsForPrompt function
Renders the progressive-disclosure block for the system prompt:
metadata only — the agent loads a skill's file with the read tool when
the task matches its description. Empty when there are no skills.
forModel applies Claude's invocation flags: disable-model-invocation
skills are excluded, and path-gated skills (paths: / applyTo:) enter
only when touchedPaths matches one of their globs.
Implementation
String formatSkillsForPrompt(
List<Skill> skills, {
bool forModel = true,
Iterable<String> touchedPaths = const [],
String? cwd,
}) {
final listed = forModel
? skills
.where(
(s) =>
s.modelInvocable &&
s.matchesTouchedPaths(touchedPaths, cwd: cwd),
)
.toList()
: skills;
if (listed.isEmpty) return '';
String escape(String text) => text
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>');
final buffer = StringBuffer()
..writeln(
'The following skills provide specialized instructions for specific '
'tasks.',
)
..writeln(
"Use the read tool to load a skill's file when the task matches its "
'description.',
)
..writeln(
"When a skill file references a relative path, resolve it against the "
'skill directory (parent of SKILL.md) and use that absolute path in '
'tool commands.',
)
..writeln()
..writeln('<available_skills>');
for (final skill in listed) {
buffer
..writeln(' <skill>')
..writeln(' <name>${escape(skill.name)}</name>')
..writeln(' <description>${escape(skill.description)}</description>')
..writeln(' <location>${escape(skill.filePath)}</location>')
..writeln(' </skill>');
}
buffer.write('</available_skills>');
return buffer.toString();
}