Skills, comandes i hooks: com organitzar un plugin de Claude Code per a un equip

Un plugin reuneix skills, comandes i hooks que l'agent aplica sol. Com s'estructura, què fa cada peça i per què les regles crítiques van en un hook.

Cover for Skills, comandes i hooks: com organitzar un plugin de Claude Code per a un equip

Un hook que passa el linter cada vegada que l’agent edita un fitxer cap en aquest JSON (exemple simplificat):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/lint-on-edit.sh\"",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Cada vegada que l’agent edita o crea un fitxer, Claude Code executa un script del plugin. Si el linter troba errors, l’script els retorna a l’agent, que els veu abans de continuar amb la tasca. Ningú no s’ha de recordar de demanar-ho.

Al meu equip fem servir un plugin intern amb skills i comandes. No en puc publicar el contingut, així que en aquest article els exemples de hooks i comandes són genèrics i estan marcats com a tals, i l’estructura d’un plugin l’ensenyo amb un altre: el de la skill d’Illuminate que hem desenvolupat a eLearning Media, que empaqueta una skill per generar SQL de Blackboard Data / Illuminate. El que sí que explico amb detall és per què cada peça va on va.

Què fa l’script que crida el hook

El hook només declara quan s’executa una cosa. La lògica és a l’script, i un script d’aquest tipus té més decisions de les que sembla. Una versió mínima, com a exemple:

#!/usr/bin/env bash
# Exemple: lint després de cada edició d'un fitxer Kotlin.
set -uo pipefail

# Claude Code passa per stdin un JSON amb l'eina utilitzada i els seus paràmetres.
FILE="$(jq -r '.tool_input.file_path // empty')"

# Només actua sobre Kotlin; qualsevol altre fitxer surt sense fer res.
case "$FILE" in
  *.kt|*.kts) ;;
  *) exit 0 ;;
esac

# Busca el gradlew més proper; si no n'hi ha, el hook no s'aplica en aquest repo.
dir="$(dirname "$FILE")"
while [ "$dir" != "/" ] && [ ! -x "$dir/gradlew" ]; do dir="$(dirname "$dir")"; done
[ -x "$dir/gradlew" ] || exit 0

if ! OUT="$(cd "$dir" && ./gradlew -q ktlintCheck 2>&1)"; then
  echo "El linter ha fallat. Corregeix aquests errors:" >&2
  printf '%s\n' "$OUT" | tail -60 >&2
  exit 2
fi
exit 0

En aquestes línies hi ha tres decisions.

L’script llegeix el fitxer editat de l’entrada estàndard, on Claude Code li passa l’eina utilitzada i els seus paràmetres. Així pot actuar només sobre el que acaba de canviar.

Sap retirar-se. Si el fitxer no és Kotlin, o no hi ha cap gradlew en el camí cap a l’arrel, surt amb 0 i no fa res. Un plugin d’equip es fa servir en repos de llenguatges diferents i el hook es dispara en tots després de cada edició; si no sabés apartar-se, seria un destorb fora dels projectes Gradle.

Tria què veu l’agent amb el codi de sortida. En un hook PostToolUse, la sortida 2 fa que Claude Code passi al model el que l’script ha escrit a stderr. L’edició ja està feta, així que el hook no la bloqueja, però l’agent rep els errors amb una instrucció explícita de corregir-los. Amb sortida 0 no rep res i continua treballant. És un principi que em sembla sa per a qualsevol comprovació automàtica: l’èxit és silenciós i només parlen els errors.

Per què un hook i no una regla a la skill

Les convencions d’estil i les regles d’arquitectura es poden escriure en una skill, i al plugin del meu equip hi són: quines eines de qualitat es fan servir, en quin ordre i com corregir cada tipus d’error.

El problema d’una regla escrita en una skill és que depèn que l’agent la carregui i que se’n recordi d’aplicar-la en el moment adequat. Una skill s’activa per la seva descripció, i l’activació és probabilística (ho desenvolupo a com crear una skill que funcioni). Encara que estigui carregada, res no obliga l’agent a passar el linter després de cada canvi; pot decidir fer-ho al final, o no fer-ho.

Un hook elimina aquesta dependència. S’executa en un esdeveniment del cicle de vida de l’agent, sempre, i el que retorna entra al context tant si el model ho vol com si no. És la diferència entre demanar a algú que passi el linter i tenir-lo en un pre-commit. La skill continua sent necessària, perquè explica com corregir el que el hook detecta, però el tret ja no en depèn.

Això és el que s’ha començat a anomenar harness engineering: en lloc de confiar que el model recordi les regles, es construeix al voltant de l’agent un entorn que comprova la seva feina i li retorna els errors. El hook és la part d’aquest entorn que s’executa sola.

Com s’organitza un plugin

Un plugin és una carpeta amb un manifest i les peces en carpetes amb un nom conegut. El d’Illuminate està fet per a Codex i és mínim:

blackboard-illuminate-sql/
├── .codex-plugin/plugin.json
├── assets/icon.svg
└── skills/
    └── illuminate-sql-support/
        ├── SKILL.md
        ├── agents/openai.yaml
        └── references/
            ├── reference-guide.md
            ├── database-structure.md
            ├── valid-fields.txt
            ├── illuminate-relations.csv
            ├── sql-examples.md
            └── tool-assignment-types-stage.md

El manifest, retallat:

{
  "name": "blackboard-illuminate-sql",
  "version": "1.0.2",
  "description": "Create, validate, explain, and debug schema-safe Snowflake SQL for Blackboard Data / Illuminate.",
  "author": { "name": "eLearning Media", "url": "https://www.elearningmedia.es" },
  "skills": "./skills/",
  "interface": {
    "displayName": "Illuminate SQL Assistant",
    "shortDescription": "Schema-safe Snowflake SQL",
    "capabilities": ["SQL generation", "SQL validation", "SQL debugging", "Query explanation"],
    "defaultPrompt": [
      "Create an Illuminate SQL query for my reporting goal.",
      "Validate this Illuminate query against the current schema.",
      "Explain and fix this Snowflake SQL error."
    ]
  }
}

La part funcional són tres camps: nom, versió i on són les skills. El bloc interface és presentació: com apareix el plugin a l’eina, amb quina descripció llarga, quines capacitats anuncia i quins prompts suggereix a qui l’instal·la. La skill té a més el seu propi agents/openai.yaml, amb un nom visible i un prompt per defecte que la invoca explícitament amb $illuminate-sql-support.

L’interessant és el que no canvia. La carpeta skills/illuminate-sql-support/ és una skill de l’estàndard obert d’Agent Skills: un SKILL.md amb name i description i les seves references. Per a Claude Code, el manifest aniria a .claude-plugin/plugin.json i la carpeta de la skill podria ser la mateixa. El que s’adapta a cada eina és l’embolcall, no el coneixement.

Un plugin de Claude Code per a un equip afegeix dues carpetes que el d’Illuminate no té (estructura d’exemple):

plugin-de-l-equip/
├── .claude-plugin/plugin.json   nom, versió, descripció
├── skills/<nom>/SKILL.md        + references/ i scripts/ segons la skill
├── commands/<nom>.md            un fitxer per comanda
├── hooks/hooks.json             hooks com el del principi
└── scripts/                     el que executen els hooks i les comandes

Les skills es descobreixen soles a partir de la carpeta, sense declarar-les al manifest.

La diferència entre els dos plugins respon a qui els fa servir. El d’Illuminate està pensat per a algú que demana una consulta SQL en una conversa: només necessita coneixement, regles i límits, i tot això cap en una skill. Un plugin d’equip es fa servir mentre l’agent modifica repositoris, i aquí també importen els procediments amb passos que no es poden saltar i les comprovacions que no poden dependre de la memòria del model.

Tres peces, tres disparadors

Cada peça es dispara d’una manera diferent, i això és el que decideix on va cada cosa:

PeçaQui la disparaQuè hi vaExemple
SkillL’agent, quan la descripció encaixaConeixement: regles, esquemes, procedimentsGenerar SQL vàlid d’Illuminate
ComandaLa persona, escrivint /nomUn procediment amb ordre i punts de controlPassar el quality gate complet
HookClaude Code, en un esdevenimentComprovacions que no poden dependre de ningúLint després de cada edició

Al plugin del meu equip les comandes sempre es recolzen en una skill: la comanda fixa l’ordre i els moments en què cal aturar-se, i la skill té el detall de cada pas. Així el procediment no s’escriu dues vegades.

Les comandes: ordre i punts de control

Una comanda és un fitxer Markdown amb frontmatter que es converteix en un prompt quan algú la invoca. Un dels patrons que fem servir és un quality gate en bucle. Una versió genèrica, com a exemple:

---
description: Executa el quality gate complet i corregeix el que falli
argument-hint: "[ruta-del-mòdul]"
allowed-tools: Bash, Read, Edit, Grep
---

Executa el gate complet sobre `$1` (o el directori actual): lint, tests
(incloses les regles d'arquitectura) i verificació de cobertura.

Si alguna cosa falla, corregeix-la i torna a executar el gate. No acabis fins
que tot passi.

No modifiquis llindars ni regles per passar el gate. Si una regla et sembla
incorrecta, digues-ho i atura't.

Al final, resumeix què fallava, què has corregit i l'estat de cada gate.

La línia que més importa és la dels llindars. Un agent a qui es demana deixar un gate en verd té dos camins, i un d’ells és abaixar el llindar de cobertura o desactivar la regla que falla. És la drecera més curta cap a l’objectiu literal, així que cal prohibir-la de manera explícita.

La comanda i el hook comparteixen eina, però no moment. El hook s’executa després de cada edició i només pot fer el que és barat, com l’estil, amb un timeout curt. El gate complet, amb tests i cobertura, es llança abans de donar un canvi per acabat o obrir un PR. Una manera senzilla d’organitzar-ho és un únic script amb dos modes, un de ràpid per al hook i un de complet per a la comanda.

allowed-tools declara quines eines pot fer servir la comanda. A l’exemple, les necessàries per executar el gate i corregir fitxers existents, sense Write.

L’altre patró que fem servir són comandes per a procediments amb passos destructius, com un desplegament. Aquí la comanda fa sobretot de fre: pregunta els valors que falten en lloc de suposar-los, mostra cada pas que escriu en un entorn sensible i espera una confirmació explícita per a aquest pas, i prohibeix esborrats sense aprovació. La skill que l’acompanya conté el detall tècnic; la comanda, els punts en què una persona ha de dir que sí. El cas més llarg d’aquest tipus, una migració d’un projecte legacy per fases, té el seu propi article: modernitzar legacy amb IA.

El plugin no es copia a mà a cada màquina: el distribuïm mitjançant un marketplace del departament, i és allà on anem afegint skills i comandes noves a mesura que apareixen necessitats. Com evitar que aquest creixement acabi en skills que es trepitgen entre si és part del que explico a l’article sobre evals de skills.

Què cuidaria en construir un hook així

L’script del principi és deliberadament mínim. Si el portés a un plugin d’equip, hi ha tres coses que resoldria abans.

La primera és distingir entre «el codi té errors» i «l’eina no està configurada». Si el repo té gradlew però no aplica el plugin del linter, la tasca no existeix i Gradle falla. Un script que tracti aquest error igual que un error d’estil demana a l’agent que corregeixi una cosa que no té solució dins del codi, i probablement l’agent ho intentarà. Comprovar abans que la tasca existeix, o filtrar aquest error concret, ho evita.

La segona és el silenci quan el gate no s’executa. Un hook que es retira amb sortida 0 no diu res a l’agent, i això és el correcte en un repo JavaScript. Però en una execució deliberada, com la de la comanda de quality gate, preferiria que l’script digués ben clar que no ha comprovat res, en lloc de deixar que el resum final sembli un èxit.

La tercera és que no totes les regles d’una skill es poden convertir en hook, i cal separar-les. «Els controladors no accedeixen directament als repositoris» es pot comprovar amb una regla d’arquitectura; «tria noms que expliquin la intenció» no es pot comprovar amb res. Les primeres haurien d’acabar en un hook o en un gate; les segones continuaran depenent de la skill i de la revisió humana.

Cap de les tres canvia la idea de fons, que encara em sembla la correcta: el coneixement va en skills, els procediments amb punts de control en comandes i el que no pot dependre de la memòria de l’agent, en hooks. D’on surt el coneixement que acaba a les skills ho explico a com destil·lar el coneixement d’un equip en skills, i el mapa complet del tema és a la guia d’agent skills.

OshyTech

Enginyeria backend i de dades orientada a sistemes escalables, automatització i IA.

Navegació

Copyright 2026 OshyTech. Tots els drets reservats