formatSkillsForPrompt function

String formatSkillsForPrompt(
  1. List<Skill> skills, {
  2. bool forModel = true,
  3. Iterable<String> touchedPaths = const [],
  4. String? cwd,
})

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('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;');
  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();
}