Skip to content

Sistema de skills

源码版本v0.73.1

skills.ts implementa la especificación Agent Skills (ver https://agentskills.io/integrate-skills). Una skill es un archivo markdown con frontmatter que describe su uso; el LLM ve la lista de skills en el system prompt y luego lee el SKILL.md correspondiente con la herramienta read para obtener instrucciones detalladas. Este archivo gestiona el descubrimiento, carga, validación, deduplicación y formateo a párrafo de prompt.

Responsabilidades

  1. Reglas de descubrimiento: loadSkillsFromDirInternal escanea recursivamente; al encontrar SKILL.md lo trata como raíz de skill y no recursa más; si no, escanea subdirectorios y archivos .md de la raíz. Ver packages/coding-agent/src/core/skills.ts:173-280.
  2. Validación de frontmatter: validateName comprueba el nombre (a-z0-9-, máx 64 chars, no empieza/termina con guion, sin --, coincide con el nombre del directorio padre); validateDescription exige descripción obligatoria y máx 1024 chars. Ver packages/coding-agent/src/core/skills.ts:93-117 y packages/coding-agent/src/core/skills.ts:122-132.
  3. Carga multi-fuente: loadSkills carga desde el usuario global (~/.pi/agent/skills), el proyecto (./.pi/skills) y paths explícitos skillPaths; en conflictos gana el primero registrado y se genera un diagnóstico collision. Ver packages/coding-agent/src/core/skills.ts:405-504.
  4. Deduplicación: usa realpath para resolver symlinks; el mismo archivo registrado por paths distintos cuenta una sola vez; para skills con el mismo nombre, gana el primero en llegar. Ver packages/coding-agent/src/core/skills.ts:416-445.
  5. Formateo de prompt: formatSkillsForPrompt genera un bloque XML <available_skills> con tripleta <name>/<description>/<location> por skill; las que tienen disableModelInvocation: true no entran en el prompt (sólo se invocan con /skill:name). Ver packages/coding-agent/src/core/skills.ts:340-366.
  6. Reglas de ignore: respeta .gitignore / .ignore / .fdignore; al escanear recursivamente se concatenan los patrones por prefijo de directorio. Ver packages/coding-agent/src/core/skills.ts:48-66 y packages/coding-agent/src/core/skills.ts:25-46.

Motivación de diseño

¿Por qué SKILL.md en vez de escanear todos los .md? Porque una skill puede incluir varios archivos (scripts, sub-docs); SKILL.md es la marca de entrada. Al encontrarlo se para la recursión y se trata el directorio entero como paquete de skill, permitiendo al autor organizar varios archivos sin que se conviertan en skills independientes.

¿Por qué frontmatter y no comentarios? Porque el frontmatter es YAML, estructurado, parseable y validable. Los campos name, description, disable-model-invocation tienen que ser legibles tanto por el LLM como por el programa. validateName fuerza que el nombre coincida con el del directorio padre (name "x" does not match parent directory "y"), evitando que el nombre derive al mover el archivo.

¿Por qué disableModelInvocation? Algunas skills son plantillas de prompt para uso personal del usuario y no deberían dispararse automáticamente desde el LLM (por ejemplo, "usa este estilo al escribir código"); sólo cargan cuando el usuario escribe /skill:name. formatSkillsForPrompt filtra estas skills; el prompt sólo expone las que el LLM puede invocar.

¿Por qué deduplicar por realpath? Porque el usuario puede pasar --skills ./my-skills y a la vez tener ./my-skills enlazado simbólicamente a ~/.pi/agent/skills/my-skills; dos escaneos devolverían paths distintos del mismo archivo. canonicalizePath resuelve el symlink antes de comparar, evitando cargar la misma skill dos veces.

Archivos clave

formatSkillsForPrompt envuelve la lista en etiquetas XML y escapa caracteres especiales:

typescript
// 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 al encontrar SKILL.md devuelve y no recursa más:

typescript
// 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 };
}

Flujo de datos

Skills de disco a prompt:

Límites y fallos

Resumen

skills.ts implementa la especificación Agent Skills: carga desde tres fuentes (usuario global, proyecto, path explícito), valida frontmatter, deduplica por realpath y formatea el prompt en XML. Las skills se referencian en el system prompt y se leen con la herramienta read; ver construcción del system prompt y conjunto de herramientas read/bash/edit/write/grep/find/ls. La entrada que ensambla los paths de skills en entrada y dispatch del CLI.