Skills-System
skills.ts implementiert die Agent-Skills-Spezifikation (siehe https://agentskills.io/integrate-skills). Ein Skill ist eine Markdown-Datei mit Frontmatter, das den Zweck des Skills beschreibt. Der LLM sieht die Skill-Liste im Systemprompt und liest dann über das read-Werkzeug die entsprechende SKILL.md, um an die detaillierten Instruktionen zu kommen. Diese Datei ist zuständig für Entdeckung, Laden, Validierung, Deduplizierung und Formatierung als Prompt-Abschnitt.
Zuständigkeiten
- Entdeckungsregel:
loadSkillsFromDirInternalscannt Verzeichnisse rekursiv — trifft es aufSKILL.md, gilt das als Skill-Wurzel und es wird nicht weiter rekursiert; andernfalls werden Unterverzeichnisse und die.md-Dateien im Wurzelverzeichnis gescannt. Siehepackages/coding-agent/src/core/skills.ts:173-280. - Frontmatter-Validierung:
validateNameprüft den Namen (a-z0-9-, max. 64 Zeichen, kein führendes/abschließendes Minus, kein--, Übereinstimmung mit dem Namen des übergeordneten Verzeichnisses);validateDescriptionprüft, dass die Beschreibung pflicht ist und 1024 Zeichen nicht überschreitet. Siehepackages/coding-agent/src/core/skills.ts:93-117undpackages/coding-agent/src/core/skills.ts:122-132. - Laden aus mehreren Quellen:
loadSkillslädt aus drei Quellen: dem globalen Benutzerbereich (~/.pi/agent/skills), dem Projekt (./.pi/skills) und explizitenskillPaths. Bei Konflikten behält der zuerst registrierte Skill den Vortritt, und es wird einecollision-Diagnose erzeugt. Siehepackages/coding-agent/src/core/skills.ts:405-504. - Deduplizierung: Über
realpathwerden symbolische Links aufgelöst; dieselbe Datei, die über verschiedene Pfade registriert wird, zählt nur einmal. Bei gleichnamigen Skills gilt First-Come-First-Served. Siehepackages/coding-agent/src/core/skills.ts:416-445. - Prompt-Formatierung:
formatSkillsForPrompterzeugt den XML-Abschnitt<available_skills>; jeder Skill wird als Tripel<name>/<description>/<location>ausgegeben. Skills mitdisableModelInvocation: truekommen nicht in den Prompt (nur über/skill:nameexplizit aufrufbar). Siehepackages/coding-agent/src/core/skills.ts:340-366. - ignore-Regeln:
.gitignore/.ignore/.fdignorewerden respektiert; beim rekursiven Scan wird das Muster mit dem Verzeichnis-Präfix zusammengesetzt. Siehepackages/coding-agent/src/core/skills.ts:48-66undpackages/coding-agent/src/core/skills.ts:25-46.
Entwurfsmotivation
Warum SKILL.md statt einfach alle .md-Dateien zu scannen? Weil ein Skill mehrere Dateien umfassen kann (Skripte, Sub-Dokumente); SKILL.md ist das Eintritts-Marker. Sobald SKILL.md gefunden wird, stoppt die Rekursion, und das gesamte Verzeichnis wird als Skill-Paket betrachtet. So können Skill-Autoren mehrere Dateien organisieren, ohne dass sie als mehrere unabhängige Skills aufgefasst werden.
Warum Frontmatter statt Kommentare? Weil Frontmatter YAML ist — strukturiert, parsbar, validierbar. Felder wie Name, Beschreibung und disable-model-invocation müssen sowohl für den LLM als auch für das Programm lesbar sein. validateName erzwingt, dass der Name mit dem übergeordneten Verzeichnisnamen übereinstimmt (name "x" does not match parent directory "y"), damit der Name nicht verrutscht, wenn die Skill-Datei verschoben wird.
Warum ist disableModelInvocation nötig? Manche Skills sind Prompt-Vorlagen für den Eigengebrauch und sollen nicht automatisch vom LLM getriggert werden (z. B. „beim Code-Schreiben diesen Stil verwenden"). Sie werden nur geladen, wenn der Nutzer explizit /skill:name aufruft. formatSkillsForPrompt filtert diese Skills heraus, sodass im Prompt nur die Skills auftauchen, die der LLM automatisch aufrufen darf.
Warum Deduplizierung über realpath? Weil der Nutzer über --skills ./my-skills gleichzeitig ./my-skills nach ~/.pi/agent/skills/my-skills linken könnte; zwei Scans würden dann dieselbe Datei mit unterschiedlichen Pfaden liefern. canonicalizePath löst die symbolischen Links auf und vergleicht erst danach, damit derselbe Skill nicht doppelt geladen wird.
Wichtige Dateien
packages/coding-agent/src/core/skills.ts:11-19— Konstanten:MAX_NAME_LENGTH64,MAX_DESCRIPTION_LENGTH1024,IGNORE_FILE_NAMES.packages/coding-agent/src/core/skills.ts:21-46—toPosixPathundprefixIgnorePattern, die ignore-Regeln aus Unterverzeichnissen um das Verzeichnis-Präfix ergänzen.packages/coding-agent/src/core/skills.ts:75-87—Skill-Interface:name,description,filePath,baseDir,sourceInfo,disableModelInvocation.packages/coding-agent/src/core/skills.ts:93-117—validateName, 5 Validierungsregeln.packages/coding-agent/src/core/skills.ts:173-280—loadSkillsFromDirInternal, rekursiver Scan und ignore-Filter.packages/coding-agent/src/core/skills.ts:282-330—loadSkillFromFile, Frontmatter parsen und validieren.packages/coding-agent/src/core/skills.ts:340-366—formatSkillsForPrompt, XML-Abschnitt erzeugen.packages/coding-agent/src/core/skills.ts:405-504—loadSkills, mehrquellen Laden und Deduplizierung.
formatSkillsForPrompt wickelt die Skill-Liste in XML-Tags ein und maskiert XML-Sonderzeichen:
// packages/coding-agent/src/core/skills.ts:340-366
export function formatSkillsForPrompt(skills: Skill[]): string {
const visibleSkills = skills.filter((s) => !s.disableModelInvocation);
if (visibleSkills.length === 0) {
return "";
}
const lines = [
"\n\nThe following skills provide specialized instructions for specific tasks.",
"Use the read tool to load a skill's file when the task matches its description.",
"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
"",
"<available_skills>",
];
for (const skill of visibleSkills) {
lines.push(" <skill>");
lines.push(` <name>${escapeXml(skill.name)}</name>`);
lines.push(` <description>${escapeXml(skill.description)}</description>`);
lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
lines.push(" </skill>");
}
lines.push("</available_skills>");
return lines.join("\n");
}loadSkillsFromDirInternal kehrt sofort zurück, sobald SKILL.md gefunden wird, und rekursiert nicht weiter:
// packages/coding-agent/src/core/skills.ts:199-226
for (const entry of entries) {
if (entry.name !== "SKILL.md") {
continue;
}
const fullPath = join(dir, entry.name);
let isFile = entry.isFile();
if (entry.isSymbolicLink()) {
try {
isFile = statSync(fullPath).isFile();
} catch {
continue;
}
}
const relPath = toPosixPath(relative(root, fullPath));
if (!isFile || ig.ignores(relPath)) {
continue;
}
const result = loadSkillFromFile(fullPath, source);
if (result.skill) {
skills.push(result.skill);
}
diagnostics.push(...result.diagnostics);
return { skills, diagnostics };
}Datenfluss
Vom Disk in den Prompt:
Grenzen und Fehler
- Keine Beschreibung, kein Laden: Ist
frontmatter.descriptionleer oder nach trim leer, wird{ skill: null }zurückgegeben; der Skill kommt nicht in die skillMap. Siehepackages/coding-agent/src/core/skills.ts:310-312. - Name passt trotzdem laden: Die Namensvalidierung erzeugt nur eine Warning-Diagnose und blockiert das Laden nicht — der Nutzer sieht das Problem, der Skill bleibt aber nutzbar. Siehe
packages/coding-agent/src/core/skills.ts:300-307. - Kaputter symbolischer Link: Wenn
statSyncwirft, wird percontinueübersprungen, ohne den gesamten Scan abzubrechen. Siehepackages/coding-agent/src/core/skills.ts:207-213undpackages/coding-agent/src/core/skills.ts:241-252. - node_modules überspringen: Bei der Rekursion wird
node_modulesexplizit ausgelassen, damit keine.md-Dateien aus Abhängigkeiten gescannt werden. Siehepackages/coding-agent/src/core/skills.ts:233-236. - Pfad-Tyauflösung: Explizite
skillPathskönnen Datei oder Verzeichnis sein; Verzeichnisse gehen durchloadSkillsFromDirInternal, Dateien müssen.mdsein. Siehepackages/coding-agent/src/core/skills.ts:478-498. - source-Erkennung: Explizite Pfade werden auch bei
includeDefaults: falseversucht als user/project-Quelle zu erkennen (basierend auf dem Pfad-Präfix), sonst alspathmarkiert. Siehepackages/coding-agent/src/core/skills.ts:464-470.
Zusammenfassung
skills.ts implementiert die Agent-Skills-Spezifikation: Laden aus drei Quellen (globaler Benutzerbereich, Projekt, explizite Pfade), Frontmatter-Validierung, Deduplizierung über realpath, XML-Formatierung und Injektion in den Prompt. Skills werden über den Systemprompt referenziert und dann per read-Werkzeug gelesen — siehe Systemprompt-Erzeugung und Werkzeuge read/bash/edit/write/grep/find/ls. Wo die Skills-Pfade zusammengebaut werden, steht in CLI-Eintritt und Dispatch.