Runtime de hooks de ciclo de vida
Cinco eventos de ciclo de vida del host de chat (SessionStart, PreToolUse,
PreCompact, SubagentStop, Stop) descargan del prompt tareas repetitivas
del ciclo SDD y aplican políticas de seguridad y control. Este dominio cubre
su registro, su doble implementación (Node.js y Go) y el launcher que decide
cuál usar.
Registro y contrato stdin/stdout
Section titled “Registro y contrato stdin/stdout”hooks/hooks.json es la única fuente de verdad para el binding de hooks:
lista los cinco eventos, cada uno de tipo "command", invocando
node "${CLAUDE_PLUGIN_ROOT}/scripts/hooks/<name>.js" a través del launcher.
| Evento | Script Node | Timeout | Responsabilidad |
|---|---|---|---|
SessionStart | session-start.js | (ninguno) | Valida OpenSpec, refresca la caché de skills, ejecuta escaneos AgentShield. |
PreToolUse | pre-tool-use.js | 5s | Bloquea/pregunta ante comandos peligrosos, evalúa Token Budget Advisor y AgentShield. |
PreCompact | pre-compact.js | 5s | Persiste un resumen recuperable antes de compactar contexto. |
SubagentStop | subagent-stop.js | 5s | Detecta degradación en la resolución de skills. |
Stop | stop.js | 5s | Registra la continuidad mínima de la sesión. |
Todo hook DEBE leer su payload como JSON UTF-8 de stdin (stdin vacío resuelve a
{}), escribir exactamente una línea JSON UTF-8 en stdout antes de salir, y
nunca fallar silenciosamente — los errores deben producir una línea JSON
válida igual.
Doble implementación: Node.js y Go
Section titled “Doble implementación: Node.js y Go”Cada hook existe dos veces:
- Node.js (
scripts/hooks/*.js) — CommonJS, Node 22+, referencia original. - Go (
internal/hooks/*.go, compilado comocmd/ospec-hooks/main.go→ospec-hooks.exe) — versión nativa optimizada para arranque rápido.
flowchart TD
Host[Host de chat] --> Launch[ospec-hooks-launch.js]
Launch --> Cfg{backend en\nopenspec/config.yaml}
Cfg -->|openspec + binario disponible| Go[ospec-hooks.exe]
Cfg -->|workspace-federated\n+ evento federation-aware| Node[Fallback Node .js]
Cfg -->|binario ausente| Node
Reglas de ruteo del launcher (scripts/hooks/ospec-hooks-launch.js)
Section titled “Reglas de ruteo del launcher (scripts/hooks/ospec-hooks-launch.js)”- El launcher prefiere el binario Go compilado cuando está disponible en la plataforma.
- El binario Go no soporta federación de workspaces. Para mantener
paridad, el launcher debe rutear eventos federation-aware
(
session-start,pre-compact,stop) al fallback Node.js cuando el backend resuelto esworkspace-federated. - Si el backend es
openspec(single-repo) o el subcomando no es federation-aware (pre-tool-use,subagent-stop), usa el binario Go si existe. - Protección del hot path: para
pre-tool-useysubagent-stopel launcher NUNCA lee ni parseaopenspec/config.yaml— evita I/O de disco en el camino más frecuente y sensible a latencia. - Si
openspec/config.yamlfalta, es ilegible, o no especificabackend, el launcher asumeopenspecpor defecto.
Detección de drift de dominio
Section titled “Detección de drift de dominio”scripts/lib/ospec-state.js expone un helper de “domain drift”: dado el
commit de baseline registrado para un dominio
(openspec/specs/_baseline/manifest.md) y sus globs de fuente, determina si
el dominio cambió desde ese commit (git diff --name-only <hash>..HEAD,
filtrado por esos globs). Es fail-safe ante cualquier fallo de git (hash
inexistente, repo vacío, HEAD detached, git ausente): retorna “sin datos de
drift” en vez de lanzar, y los llamadores (SessionStart, PreToolUse) nunca
deben bloquearse por este fallo. Un dominio con cambios ya cubiertos por el
scope de un cambio OpenSpec activo se excluye del resultado de drift.
Por qué la arquitectura está diseñada así
Section titled “Por qué la arquitectura está diseñada así”Mantener Node.js como implementación de referencia y Go como optimización de
arranque permite validar el binario nativo contra los mismos tests de
comportamiento (scripts/hooks/parity-contract.test.js), sin bifurcar el
contrato observable. La federación de workspaces es la única capacidad que el
binario Go no replica todavía, así que el launcher hace ese ruteo explícito en
vez de degradar silenciosamente.
Principales puntos de extensión
Section titled “Principales puntos de extensión”- Agregar un hook nuevo: registrar el evento en
hooks/hooks.json, implementar el script Node enscripts/hooks/, y — si aplica paridad Go — el handler correspondiente eninternal/hooks/. - Extender el ruteo del launcher: agregar el subcomando a la lista federation-aware si el nuevo hook necesita fallback a Node en workspaces federados.
Cosas a vigilar al editar
Section titled “Cosas a vigilar al editar”- Un cambio de contrato en el hook Node.js casi siempre exige el espejo en Go
— de lo contrario
parity-contract.test.jsdebe actualizarse deliberadamente, nunca omitirse. - No leer
openspec/config.yamldentro de los handlers depre-tool-useosubagent-stop— rompe la protección de hot path. - Los hooks nunca deben lanzar excepciones sin capturar: siempre deben devolver JSON válido en stdout, incluso en el camino de error.
Mapa de fuentes
Section titled “Mapa de fuentes”/hooks/hooks.json—git log:26509c6(launcher con fallback a Node),8b5495b(migración a binario Go)/scripts/hooks/session-start.js,/scripts/hooks/pre-tool-use.js,/scripts/hooks/pre-compact.js,/scripts/hooks/subagent-stop.js,/scripts/hooks/stop.js/scripts/hooks/ospec-hooks-launch.js,/scripts/hooks/parity-contract.test.js/internal/hooks/,/cmd/ospec-hooks/main.go/scripts/lib/ospec-state.js/openspec/specs/hooks/spec.md,/openspec/specs/launcher/spec.md