navori 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/core/core-assets/agents/leader.md +5 -3
- package/dist/assets/core/core-assets/hooks/guard-destructive.sh +18 -1
- package/dist/assets/core/core-assets/hooks/quality-gate-pre-commit.sh +14 -1
- package/dist/assets/core/core-assets/managed/orquestacion.md +34 -9
- package/dist/assets/core/core-assets/presets/express/managed/stack.md +20 -0
- package/dist/assets/core/core-assets/presets/express.json +44 -0
- package/dist/assets/core/core-assets/presets/vite-react-ts/managed/stack.md +7 -0
- package/dist/assets/core/core-assets/presets/vite-react-ts.json +23 -0
- package/dist/assets/core/core-assets/skills/loop-back-debug.md +1 -1
- package/dist/assets/plugins/cognitive/scripts/check-cognitive.sh +15 -2
- package/dist/assets/plugins/engram/skills/engram-leader.md +3 -3
- package/dist/assets/plugins/jscpd/scripts/check-jscpd.sh +15 -2
- package/dist/assets/plugins/semgrep/scripts/check-semgrep.sh +15 -2
- package/dist/index.js +998 -580
- package/package.json +2 -2
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: leader
|
|
3
|
-
description:
|
|
3
|
+
description: NO invocar como subagente. Playbook de orquestación que el agente principal ENCARNA (ver "## Rol: orquestador" en CLAUDE.md). Delegarlo a un subagente serializa el trabajo y tira el paralelismo.
|
|
4
4
|
tools: Read, Glob, Grep, Bash, Agent
|
|
5
5
|
model: {{models.leader}}
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Playbook del Orquestador (encarnado por el agente principal)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
> Este archivo es **referencia de profundidad** — el rol de orquestador **lo encarna el agente principal**, no un subagente. La mecánica esencial (tabla de escalado, paralelismo, síntesis) vive inline en el bloque "## Rol: orquestador" de `CLAUDE.md`, que se auto-carga. Aquí está el detalle extendido y, abajo, las **Reglas del proyecto**. NO invoques `Agent(subagent_type: leader)`.
|
|
11
|
+
|
|
12
|
+
Tu único trabajo como orquestador es **descomponer y coordinar**, nunca implementar.
|
|
11
13
|
|
|
12
14
|
## Protocolo de arranque
|
|
13
15
|
|
|
@@ -13,7 +13,24 @@
|
|
|
13
13
|
# bottom — `$cmd` is already parsed and in scope there.
|
|
14
14
|
set -euo pipefail
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# Extract .tool_input.command from the PreToolUse payload WITHOUT hard-depending
|
|
17
|
+
# on jq (NOT preinstalled on macOS — a missing jq used to make this guard wave
|
|
18
|
+
# every command through). Try jq, then node (Claude Code's own runtime), and if
|
|
19
|
+
# no JSON parser is on PATH fall back to a best-effort sed unwrap so the guard
|
|
20
|
+
# still inspects the command instead of failing open.
|
|
21
|
+
payload=$(cat)
|
|
22
|
+
extract_cmd() {
|
|
23
|
+
if command -v jq >/dev/null 2>&1; then
|
|
24
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
25
|
+
fi
|
|
26
|
+
if command -v node >/dev/null 2>&1; then
|
|
27
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
28
|
+
fi
|
|
29
|
+
# No JSON parser on PATH: pull the "command" string out with sed. Best-effort
|
|
30
|
+
# (won't handle a literal embedded quote), but far better than failing open.
|
|
31
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
32
|
+
}
|
|
33
|
+
cmd=$(extract_cmd)
|
|
17
34
|
[ -z "$cmd" ] && exit 0
|
|
18
35
|
|
|
19
36
|
base="{{branchBase}}"
|
|
@@ -9,7 +9,20 @@
|
|
|
9
9
|
# bottom — they keep `cmd` and the original exit codes in scope.
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# Extract .tool_input.command WITHOUT hard-depending on jq (not preinstalled on
|
|
13
|
+
# macOS). Try jq, then node (Claude Code's own runtime), then a best-effort sed
|
|
14
|
+
# unwrap. If nothing extracts a command $cmd stays empty and the gate skips.
|
|
15
|
+
payload=$(cat)
|
|
16
|
+
extract_cmd() {
|
|
17
|
+
if command -v jq >/dev/null 2>&1; then
|
|
18
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
19
|
+
fi
|
|
20
|
+
if command -v node >/dev/null 2>&1; then
|
|
21
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
22
|
+
fi
|
|
23
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
24
|
+
}
|
|
25
|
+
cmd=$(extract_cmd)
|
|
13
26
|
|
|
14
27
|
case "$cmd" in
|
|
15
28
|
'git commit'*|'git push'*)
|
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
## Rol: orquestador
|
|
1
|
+
## Rol: orquestador (centro de gravedad)
|
|
2
2
|
|
|
3
|
-
Ante una tarea no trivial **actúas como el
|
|
3
|
+
Ante una tarea no trivial **tú actúas como el orquestador**: descompones y coordinas, no implementas el código directamente. Este rol **lo encarnas tú, el agente principal** — es el único que puede abrir un abanico de subagentes en paralelo. **NUNCA lo delegues**: no invoques `Agent(subagent_type: leader)`. El archivo `.claude/agents/leader.md` es referencia de profundidad (y las "Reglas del proyecto"), no un subagente para delegar; delegarlo serializa el trabajo y tira el paralelismo. El catálogo de subagentes hoja está en "## Agentes disponibles".
|
|
4
4
|
|
|
5
|
-
### Cómo
|
|
5
|
+
### Cómo descomponer (tabla de escalado)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
| Complejidad | Subagentes |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Trivial (1 archivo) | 1 `implementer` |
|
|
10
|
+
| Media (2–3 archivos) | 1 `implementer` → 1 `reviewer` |
|
|
11
|
+
| Multi-bug independiente (sin shared state) | N `implementer` en paralelo (1 por bug, scopes aislados) → 1 `reviewer` que valida los N diffs juntos |
|
|
12
|
+
| Compleja (migración, refactor multi-capa) | `ticket-audit` → 2–3 `researcher`/`explorer` en paralelo → `implementer` → `reviewer` → `commit-pr-pilot` |
|
|
13
|
+
| Muy compleja | Divide en sub-tareas y re-aplica la tabla |
|
|
14
|
+
|
|
15
|
+
Investigación con preguntas acotadas → `researcher`; mapas amplios (¿dónde vive X?) → `explorer`. Con audit previo, pásale al `implementer` la ruta de `.claude/progress/audit_<ID>.md`.
|
|
16
|
+
|
|
17
|
+
### Paralelismo (la palanca — mecánica, no opcional)
|
|
18
|
+
|
|
19
|
+
El paralelismo es **analítico**, no solo velocidad: el valor está en partir el problema en piezas genuinamente independientes y en cómo integras lo que vuelve. La mecánica: cuando la tabla dice "en paralelo", eso se logra emitiendo **TODAS las llamadas `Agent` en un MISMO turno**. Claude por defecto las lanza en serie; el paralelo hay que pedirlo explícito, en un solo mensaje.
|
|
20
|
+
|
|
21
|
+
- ✅ En un mensaje, invoca `Agent` 3 veces (`explorer` auth, db, api). Corren concurrentes; el total ≈ el más lento.
|
|
22
|
+
- ❌ Invocar auth, esperar su `done -> archivo`, luego db, luego api. Eso es serie y tira lo que el paralelo ahorra.
|
|
23
|
+
|
|
24
|
+
Regla: sub-tareas **independientes** (no comparten estado ni una depende del output de otra) → mismo turno. Serializa solo con dependencia real (`implementer` → `reviewer`). **`implementer` en paralelo SOLO con archivos disjuntos** (dos que tocan el mismo archivo se pisan → van en serie; en la duda, serie). Reparte el scope explícito antes de abrir el abanico.
|
|
25
|
+
|
|
26
|
+
**Fan-out → síntesis:** para una pregunta amplia, descompónla en sub-preguntas y lanza un investigador por cada una en paralelo. Cuando vuelven los `done -> archivo`, **recopila y analiza a fondo TÚ**: lee los N archivos juntos, cruza hallazgos (contradicciones, gaps, qué falta) y recién ahí decides la implementación. La síntesis no se delega.
|
|
27
|
+
|
|
28
|
+
### Ejecución continua (no pausar entre tareas)
|
|
29
|
+
|
|
30
|
+
Aprobado el plan/scope, ejecuta TODAS las sub-tareas sin pedir confirmación entre nodos. No hagas "hice la 1, ¿sigo con la 2?" — ejecuta el plan. Solo paras por: **BLOCKED** (subagente bloqueado que no puedes resolver), **spec ambigua mid-flight** (gap real fuera de scope), o **ciclo completo** (listo para PR). Cap: 2 ciclos `CHANGES_REQUESTED` sobre la misma tarea → escala al usuario en vez de reintentar en loop.
|
|
31
|
+
|
|
32
|
+
### Síntesis sin teléfono descompuesto
|
|
33
|
+
|
|
34
|
+
Instruye a los subagentes a **escribir en `.claude/progress/<archivo>.md`**; tú recibes solo `done -> archivo`. Verifica el diff/evidencia tú mismo, no confíes ciego en el reporte. Al cerrar el ciclo, cuando `review_<feature>.md` diga `APPROVED`, invoca `commit-pr-pilot` (pre-flight: working tree limpio, no en `{{branchBase}}`, `{{qualityGate.fast}}` verde, `gh auth status` ok). Si dice `CHANGES_REQUESTED`, lanza otro `implementer` — no el pilot.
|
|
10
35
|
|
|
11
36
|
### Cuándo NO orquestar (hazlo tú directo)
|
|
12
37
|
|
|
13
|
-
- Pregunta conceptual
|
|
14
|
-
- Cambios en `docs/`, `.claude/`, `CLAUDE.md`, `progress/` → edítalos t
|
|
15
|
-
- Una sola línea trivial en un archivo conocido → puede no valer el overhead.
|
|
38
|
+
- Pregunta conceptual / lectura pura → responde sin subagentes.
|
|
39
|
+
- Cambios en `docs/`, `.claude/`, `CLAUDE.md`, `progress/` → **edítalos tú** (esos sí los tocas; el código fuente del proyecto NUNCA — eso es del `implementer`).
|
|
40
|
+
- Una sola línea trivial en un archivo conocido → puede no valer el overhead del fan-out. El fan-out cuesta contexto; no abras 5 explorers para una tarea chica.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
## Stack — Express (TypeScript)
|
|
2
|
+
|
|
3
|
+
Backend HTTP sobre Express en TypeScript, agnóstico de base de datos (Socket.IO, PeerJS, DB nativo, sin DB, etc.). Las peticiones fluyen en capas: `route → validate(schema) → asyncHandler → controller → capa de datos → ApiResponse`. Los errores se propagan vía `ApiError` y las respuestas se envuelven en `ApiResponse`. El logging va por el `Logger` de winston, nunca `console.log`.
|
|
4
|
+
|
|
5
|
+
Regla de oro: nada de `res.json` / `res.status(500)` crudos; nada de `console.log`; nada de `process.env` fuera del módulo de config. La validación SIEMPRE ocurre en el boundary (con el validador del repo — Zod o Joi). Aplica las skills `express-routes` y `winston-logging` del preset según la capa que toques. Las skills de la capa de datos (mongoose, socketio, etc.) y de validación se inyectan según las dependencias que detecte navori en el repo — si están en `.claude/skills/`, aplícalas.
|
|
6
|
+
|
|
7
|
+
El trabajo de un ticket sigue el pipeline documentado en la skill `ticket-intake` (la orquestadora). No es un generador de specs: es un protocolo que el orquestador ejecuta invocando agentes y skills en orden, con gates objetivos y artefactos en `.claude/progress/`. Mapeo de fases a la infraestructura de navori:
|
|
8
|
+
|
|
9
|
+
| Fase | Quién la cubre | Artefacto |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Audit | agente `ticket-audit` | `audit_<id>.md` |
|
|
12
|
+
| Explore | agente `explorer` (2-3 en paralelo) | `explore_<dim>.md` |
|
|
13
|
+
| Design | skill `new-endpoint` según alcance | (en el plan) |
|
|
14
|
+
| Implement | agente `implementer` (aplica las skills de stack) | `impl_<feature>.md` |
|
|
15
|
+
| Verify | skill core `verify-before-done` (Iron Law) | (evidencia en turno) |
|
|
16
|
+
| Review | agente `reviewer` + skill core `review-diff` | `review_<feature>.md` |
|
|
17
|
+
| Debug | skill core `loop-back-debug` | — |
|
|
18
|
+
| PR | skill `pr-create` | URL del PR |
|
|
19
|
+
|
|
20
|
+
navori bootstrapea `current.md` e `history.md`; el resto de artefactos los crea el flujo en runtime.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://navori.dev/schema/navori.preset.v1.json",
|
|
3
|
+
"id": "express",
|
|
4
|
+
"displayName": "Express backend (DB-agnostic)",
|
|
5
|
+
"extends": "core",
|
|
6
|
+
"extras": {
|
|
7
|
+
"managed": [
|
|
8
|
+
{
|
|
9
|
+
"id": "stack-express",
|
|
10
|
+
"relPath": "presets/express/managed/stack.md"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"agents": [],
|
|
14
|
+
"skills": [
|
|
15
|
+
{
|
|
16
|
+
"id": "express-routes",
|
|
17
|
+
"relPath": "presets/express-mongoose/skills/express-routes.md",
|
|
18
|
+
"destRelPath": ".claude/skills/express-routes.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "winston-logging",
|
|
22
|
+
"relPath": "presets/express-mongoose/skills/winston-logging.md",
|
|
23
|
+
"destRelPath": ".claude/skills/winston-logging.md"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "new-endpoint",
|
|
27
|
+
"relPath": "presets/express-mongoose/skills/new-endpoint.md",
|
|
28
|
+
"destRelPath": ".claude/skills/new-endpoint.md"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "ticket-intake",
|
|
32
|
+
"relPath": "presets/express-mongoose/skills/ticket-intake.md",
|
|
33
|
+
"destRelPath": ".claude/skills/ticket-intake.md"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "pr-create",
|
|
37
|
+
"relPath": "presets/express-mongoose/skills/pr-create.md",
|
|
38
|
+
"destRelPath": ".claude/skills/pr-create.md"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"hooks": []
|
|
42
|
+
},
|
|
43
|
+
"invariants": ["express-routes", "asyncHandler", "ApiResponse", "ticket-intake"]
|
|
44
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Stack — Vite + React + TypeScript
|
|
2
|
+
|
|
3
|
+
SPA sobre Vite + React 18 + TypeScript, agnóstica de UI lib (CSS Modules, Tailwind, styled-components, o una lib de componentes). Organización por feature: cada feature vive en su carpeta con sus componentes, hooks y estado local; lo compartido (UI primitives, utils, hooks genéricos) va a carpetas comunes. Los componentes son funcionales con hooks; nada de class components nuevos.
|
|
4
|
+
|
|
5
|
+
Regla de oro: tipado estricto (sin `any` injustificado — ver el bloque de tipado); side-effects en `useEffect` con deps completas; data-fetching por la capa que use el repo (fetch/axios, TanStack Query si está — se inyecta como library-skill según deps). El estado del servidor NO se duplica en estado global; el estado global (Redux/Zustand/Context) es solo para lo genuinamente compartido y de cliente. Aplica la skill `new-feature` para dar de alta una feature nueva con la estructura del repo. Las skills de UI lib, forms y state se inyectan según las dependencias que detecte navori.
|
|
6
|
+
|
|
7
|
+
El trabajo de un ticket sigue el pipeline de la infraestructura de navori: `ticket-audit` → `explorer` (en paralelo) → `implementer` (aplica las skills de stack) → `verify-before-done` → `reviewer` + `review-diff` → `commit-pr-pilot`. navori bootstrapea `current.md` e `history.md`; el resto de artefactos los crea el flujo en runtime bajo `.claude/progress/`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://navori.dev/schema/navori.preset.v1.json",
|
|
3
|
+
"id": "vite-react-ts",
|
|
4
|
+
"displayName": "Vite + React + TS SPA (UI-lib-agnostic)",
|
|
5
|
+
"extends": "core",
|
|
6
|
+
"extras": {
|
|
7
|
+
"managed": [
|
|
8
|
+
{
|
|
9
|
+
"id": "stack-vite-react-ts",
|
|
10
|
+
"relPath": "presets/vite-react-ts/managed/stack.md"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"agents": [],
|
|
14
|
+
"skills": [
|
|
15
|
+
{
|
|
16
|
+
"id": "new-feature",
|
|
17
|
+
"relPath": "presets/vite-react-ts-mantine/skills/new-feature.md",
|
|
18
|
+
"destRelPath": ".claude/skills/new-feature.md"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"hooks": []
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -87,7 +87,7 @@ No es debilidad — es eficiencia. 3 intentos a ciegas valen menos que 1 convers
|
|
|
87
87
|
## Conexión con el resto del harness
|
|
88
88
|
|
|
89
89
|
- `implementer`: invoca este skill cuando el primer fix no resuelve el síntoma. NO devuelve `done` hasta haber pasado por Reset hipótesis si el repro inicial falla.
|
|
90
|
-
- `verify-before-done`: este skill se aplica
|
|
90
|
+
- `verify-before-done`: este skill se aplica ANTES de verify-before-done — primero validas que el fix realmente arregló el síntoma (esto), luego validas que el resto del quality gate sigue verde (verify-before-done).
|
|
91
91
|
- `ticket-audit`: cuando un bug entra al agente ticket-audit, la "Hipótesis de causa raíz" es el primer candidate del loop. Si el fix de esa hipótesis no funciona, ticket-audit puede ser reinvocado con la info nueva.
|
|
92
92
|
|
|
93
93
|
## Cierre
|
|
@@ -16,8 +16,21 @@
|
|
|
16
16
|
set -euo pipefail
|
|
17
17
|
|
|
18
18
|
# PreToolUse(Bash) passes the command — gate to commit/push. The Stop hook
|
|
19
|
-
# passes no command — run unconditionally at session close.
|
|
20
|
-
|
|
19
|
+
# passes no command — run unconditionally at session close. Extract without
|
|
20
|
+
# hard-depending on jq (not preinstalled on macOS): try jq, then node (Claude
|
|
21
|
+
# Code's own runtime), then a best-effort sed unwrap. No command extracted →
|
|
22
|
+
# empty $cmd → runs unconditionally.
|
|
23
|
+
payload=$(cat)
|
|
24
|
+
extract_cmd() {
|
|
25
|
+
if command -v jq >/dev/null 2>&1; then
|
|
26
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
27
|
+
fi
|
|
28
|
+
if command -v node >/dev/null 2>&1; then
|
|
29
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
30
|
+
fi
|
|
31
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
32
|
+
}
|
|
33
|
+
cmd=$(extract_cmd)
|
|
21
34
|
if [ -n "$cmd" ]; then
|
|
22
35
|
case "$cmd" in
|
|
23
36
|
'git commit'*|'git push'*) ;;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: engram-leader-extension
|
|
3
|
-
description: Protocolo Engram para el agente líder.
|
|
3
|
+
description: Protocolo Engram para el agente líder. Busca contexto antes de descomponer, guarda decisiones proactivamente, cierra sesión con summary.
|
|
4
4
|
type: behavior
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## Engram (memoria persistente)
|
|
8
8
|
|
|
9
|
-
Antes de descomponer trabajo: **
|
|
9
|
+
Antes de descomponer trabajo: **busca contexto** con `mem_search` usando keywords del ticket. Si encuentras un audit previo de la misma área o una decisión arquitectónica relacionada, léelo antes de tirar al `implementer`. No re-descubrir lo que ya está guardado.
|
|
10
10
|
|
|
11
|
-
Después de cada decisión arquitectónica, plugin nuevo o convención establecida en la sesión: `mem_save` proactivo con tipo apropiado (`decision`, `convention`, `pattern`, `bugfix`).
|
|
11
|
+
Después de cada decisión arquitectónica, plugin nuevo o convención establecida en la sesión: `mem_save` proactivo con tipo apropiado (`decision`, `convention`, `pattern`, `bugfix`). Encabeza con qué decisión + por qué + dónde aplica.
|
|
12
12
|
|
|
13
13
|
Antes de cerrar la sesión: `mem_session_summary` obligatorio con:
|
|
14
14
|
|
|
@@ -9,8 +9,21 @@
|
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
|
|
11
11
|
# PreToolUse(Bash) passes the command — gate to commit/push. The Stop hook
|
|
12
|
-
# passes no command — run unconditionally at session close.
|
|
13
|
-
|
|
12
|
+
# passes no command — run unconditionally at session close. Extract without
|
|
13
|
+
# hard-depending on jq (not preinstalled on macOS): try jq, then node (Claude
|
|
14
|
+
# Code's own runtime), then a best-effort sed unwrap. No command extracted →
|
|
15
|
+
# empty $cmd → runs unconditionally.
|
|
16
|
+
payload=$(cat)
|
|
17
|
+
extract_cmd() {
|
|
18
|
+
if command -v jq >/dev/null 2>&1; then
|
|
19
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
20
|
+
fi
|
|
21
|
+
if command -v node >/dev/null 2>&1; then
|
|
22
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
23
|
+
fi
|
|
24
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
25
|
+
}
|
|
26
|
+
cmd=$(extract_cmd)
|
|
14
27
|
if [ -n "$cmd" ]; then
|
|
15
28
|
case "$cmd" in
|
|
16
29
|
'git commit'*|'git push'*) ;;
|
|
@@ -9,8 +9,21 @@
|
|
|
9
9
|
set -euo pipefail
|
|
10
10
|
|
|
11
11
|
# PreToolUse(Bash) passes the command — gate to commit/push. The Stop hook
|
|
12
|
-
# passes no command — run unconditionally at session close.
|
|
13
|
-
|
|
12
|
+
# passes no command — run unconditionally at session close. Extract without
|
|
13
|
+
# hard-depending on jq (not preinstalled on macOS): try jq, then node (Claude
|
|
14
|
+
# Code's own runtime), then a best-effort sed unwrap. No command extracted →
|
|
15
|
+
# empty $cmd → runs unconditionally.
|
|
16
|
+
payload=$(cat)
|
|
17
|
+
extract_cmd() {
|
|
18
|
+
if command -v jq >/dev/null 2>&1; then
|
|
19
|
+
printf '%s' "$payload" | jq -r '.tool_input.command // empty' 2>/dev/null && return 0
|
|
20
|
+
fi
|
|
21
|
+
if command -v node >/dev/null 2>&1; then
|
|
22
|
+
printf '%s' "$payload" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try{process.stdout.write(String(JSON.parse(s)?.tool_input?.command??""))}catch{}})' 2>/dev/null && return 0
|
|
23
|
+
fi
|
|
24
|
+
printf '%s' "$payload" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/p'
|
|
25
|
+
}
|
|
26
|
+
cmd=$(extract_cmd)
|
|
14
27
|
if [ -n "$cmd" ]; then
|
|
15
28
|
case "$cmd" in
|
|
16
29
|
'git commit'*|'git push'*) ;;
|