@thebattlearena/battle-arena-hooks 0.5.2 → 0.5.3-beta.37
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/.claude-plugin/marketplace.json +13 -0
- package/README.md +128 -37
- package/cli.mjs +89 -65
- package/execution-coach-nudge.mjs +2 -2
- package/lib/battle-order/battle-arena-logo.png +0 -0
- package/lib/battle-order/html.mjs +403 -0
- package/lib/battle-order/pdf.mjs +122 -0
- package/lib/battle-order/styles.mjs +158 -0
- package/lib/local-activity.mjs +217 -0
- package/lib/plugin-install.mjs +128 -0
- package/lib/transport.mjs +54 -0
- package/objective-nudge.mjs +1 -1
- package/package.json +6 -4
- package/plan.mjs +89 -0
- package/plugin/.claude-plugin/plugin.json +7 -0
- package/plugin/hooks/api.ts +118 -0
- package/plugin/hooks/format.ts +157 -0
- package/plugin/hooks/hooks.json +111 -0
- package/plugin/hooks/mock.ts +151 -0
- package/plugin/hooks/pane.tsx +667 -0
- package/plugin/hooks/register.tsx +278 -0
- package/plugin/skills/conquer/SKILL.md +289 -0
- package/plugin/tests/format.test.ts +34 -0
- package/plugin/tests/scorecard.test.tsx +295 -0
- package/plugin/types/index.d.ts +120 -0
- package/pr-review-worktree.mjs +1 -1
- /package/{skill → plugin/skills}/arena-objective/SKILL.md +0 -0
- /package/{skill → plugin/skills}/arena-self-review/SKILL.md +0 -0
- /package/{skill → plugin/skills}/arena-self-review/assets/fonts.css +0 -0
- /package/{skill → plugin/skills}/arena-self-review/assets/leracom-logo.png +0 -0
- /package/{skill → plugin/skills}/arena-self-review/assets/report.css +0 -0
- /package/{skill → plugin/skills}/arena-self-review/assets/style.css +0 -0
- /package/{skill → plugin/skills}/arena-self-review/scripts/gen-fonts.mjs +0 -0
- /package/{skill → plugin/skills}/arena-self-review/scripts/generate-pdf.sh +0 -0
- /package/{skill → plugin/skills}/arena-self-review/scripts/render-report.sh +0 -0
- /package/{skill → plugin/skills}/execution-coach/SKILL.md +0 -0
- /package/{skill → plugin/skills}/pr-review-loop/SKILL.md +0 -0
- /package/{skill → plugin/skills}/scope-technical-task/SKILL.md +0 -0
package/README.md
CHANGED
|
@@ -1,25 +1,117 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Battle Arena en Claude Code (plugin `battlearena`)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Este paquete instala **Battle Arena en Claude Code** como un solo plugin, `battlearena`: los hooks que
|
|
4
|
+
mandan tus sesiones al colector (`apps/public-api`), las skills del equipo y el **Scorecard** de tu día
|
|
5
|
+
junto al chat. Las skills quedan con el espacio de nombres del plugin (`/battlearena:conquer`), así no
|
|
6
|
+
chocan con las de otros plugins y en el autocompletado salen todas juntas al escribir `/battlearena`.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npx @thebattlearena/battle-arena-hooks@latest install \
|
|
10
|
+
--url https://api.battlearena.dev --api-key <tu-llave>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
La llave se genera en [battlearena.dev/api-keys](https://battlearena.dev/api-keys). Para quitarlo:
|
|
14
|
+
`npx @thebattlearena/battle-arena-hooks uninstall`.
|
|
15
|
+
|
|
16
|
+
## Qué trae el plugin
|
|
17
|
+
|
|
18
|
+
### Skills
|
|
19
|
+
|
|
20
|
+
Se invocan escribiendo el comando o pidiéndolo con tus palabras («arma mi plan de mañana»).
|
|
21
|
+
|
|
22
|
+
| Skill | Para qué sirve |
|
|
23
|
+
| ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `/battlearena:conquer` | Arma o actualiza tu **plan de batalla** del siguiente día hábil (o el de **hoy**, si aún no tienes: antes de las 16:00 te pregunta cuál): revisa lo que hiciste hoy (git, tus sesiones de Claude, tus pendientes), te entrevista tema por tema (valor de negocio, DoD, ≤ 3 h, riesgos) y crea tu ticket del día (el de mañana, desde las 16:00 de la víspera) en Linear y tu orden de batalla en PDF. |
|
|
25
|
+
| `/battlearena:arena-objective` | Etiqueta el **objetivo del turno** (nuevo o continuación) y lo manda al colector, para que tu actividad se agrupe por objetivo. Claude la usa solo al inicio de cada turno; no hace falta llamarla. |
|
|
26
|
+
| `/battlearena:arena-self-review` | Tu **revisión semanal de desempeño**, self-service: lee tu actividad de la semana desde el MCP de Battle Arena y te da una autocrítica con evidencia. |
|
|
27
|
+
| `/battlearena:execution-coach` | **Interceptor mayéutico** durante la ejecución: te frena con preguntas cuando el pedido es vago o la sesión da vueltas sin converger. Se activa sola cuando el hook detecta esas señales. |
|
|
28
|
+
| `/battlearena:scope-technical-task` | **Coach de alcance antes de codear**: problema de negocio, sub-problemas, soluciones sin código, estimación en horas. |
|
|
29
|
+
| `/battlearena:pr-review-loop` | Atiende la **review de Copilot** de un PR en segundo plano (corrige → gates → commit → resolver hilos → re-review). Siempre opt-in: pregunta antes y cuántas iteraciones tolera. |
|
|
30
|
+
|
|
31
|
+
### Comandos
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
| Comando | Para qué sirve |
|
|
34
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `/scorecard` | Abre el **Scorecard**: la pelea de tu día junto al chat. Tu plan con el avance y el tiempo de cada punto contra lo comprometido (semáforo verde · amarillo · rojo), los emergentes (clic para ver su motivo), los riesgos siempre visibles con ✋ Levantar la mano y 👍/👎, y el pie con Plan · Reuniones · Fuera · Total. Pestaña **Semana** con tus hábitos. Sin datos, te dice el **siguiente paso** (instalar, armar el plan, entrar a la daily). |
|
|
36
|
+
| `/scorecard-demo` | El Scorecard con datos de muestra; otra vez, vuelve a tus datos. |
|
|
37
|
+
| `/scorecard-empty` | Cómo ve el Scorecard alguien sin hooks; otra vez, vuelve a tus datos. |
|
|
21
38
|
|
|
22
|
-
|
|
39
|
+
Arriba del prompt sale además una **banda** compacta del Scorecard (`LER-640 ✓1 ◐1 ↷1 ⚠1 [Abrir]`).
|
|
40
|
+
Los comandos del Scorecard no llevan el prefijo del plugin (así registra Claude Code los comandos de
|
|
41
|
+
un módulo), por eso su nombre ya es único.
|
|
42
|
+
|
|
43
|
+
### Hooks
|
|
44
|
+
|
|
45
|
+
Scripts **zero-dependency** que corren en la máquina de cada dev. NO les agregues imports de
|
|
46
|
+
node_modules: la promesa es instalación-cero. Viven junto al plugin y su `hooks/hooks.json` los
|
|
47
|
+
llama con `node "${CLAUDE_PLUGIN_ROOT}/../<script>"`.
|
|
48
|
+
|
|
49
|
+
| Script | Hook | Qué hace |
|
|
50
|
+
| --------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `emit.mjs` | todos | Enriquece con identidad git, sanitiza y postea a `/ingest`; buffer local si el colector no responde |
|
|
52
|
+
| `session-start.mjs` | SessionStart | Handshake + inyecta ranking (`/v1/leaderboard/rank`) y alerta de rama protegida |
|
|
53
|
+
| `check-connection.mjs` | UserPromptSubmit | Avisa si hay desconexión o eventos en cola |
|
|
54
|
+
| `objective-nudge.mjs` | UserPromptSubmit | Recuerda invocar `battlearena:arena-objective` |
|
|
55
|
+
| `execution-coach-nudge.mjs` | UserPromptSubmit | Detecta señales de ejecución subóptima y, con throttle por sesión, sugiere `battlearena:execution-coach` |
|
|
56
|
+
| `pr-review-worktree.mjs` | PostToolUse | Al detectar `gh pr create`, ofrece (opt-in) `battlearena:pr-review-loop`; no lanza nada por sí solo |
|
|
57
|
+
|
|
58
|
+
Helpers que usan las skills: `objective.mjs` (el objetivo del turno) y `plan.mjs` (actividad local de
|
|
59
|
+
git y el PDF de la orden de batalla de `/battlearena:conquer`).
|
|
60
|
+
|
|
61
|
+
### Estructura
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
apps/claude-hooks/
|
|
65
|
+
cli.mjs install.sh *.mjs lib/ el paquete npm y los scripts de los hooks
|
|
66
|
+
.claude-plugin/marketplace.json marketplace local: battlearena → ./plugin
|
|
67
|
+
plugin/
|
|
68
|
+
.claude-plugin/plugin.json { name: "battlearena" }
|
|
69
|
+
hooks/hooks.json los hooks de comando + el módulo del Scorecard
|
|
70
|
+
hooks/register.tsx … el Scorecard (panel, banda, comandos)
|
|
71
|
+
skills/ las skills (las genera `npm run bundle:skill`)
|
|
72
|
+
tests/ pruebas del Scorecard (`claude plugin test plugin`)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Plan de batalla (skill `/battlearena:conquer` + `plan.mjs`, ADR-0042)
|
|
76
|
+
|
|
77
|
+
`/conquer` (o «arma mi plan de mañana») arma tu plan del siguiente día hábil como estratega: revisa
|
|
78
|
+
lo que hiciste hoy, te propone los pendientes con su explicación y su ticket, y por cada tema pide
|
|
79
|
+
contexto en Linear, valor, DoD no ambiguo, duración (≤ 3 h o «divide y vencerás» en subtareas) y
|
|
80
|
+
riesgos. **El de mañana se completa desde las 16:00 (CDMX) de la víspera** (fin de semana libre);
|
|
81
|
+
antes solo guarda el borrador.
|
|
82
|
+
|
|
83
|
+
**¿Hoy o mañana?** De lunes a viernes antes de las 16:00, si aún no tienes plan de hoy, te pregunta
|
|
84
|
+
cuál armar (hoy, recomendado); desde las 16:00 o con el de hoy ya hecho, arma el de mañana. También
|
|
85
|
+
puedes pedirlo directo («arma mi plan de hoy»). El plan de hoy se completa al momento y entra a tu
|
|
86
|
+
reloj del día y a los recordatorios.
|
|
87
|
+
|
|
88
|
+
Cómo habla:
|
|
89
|
+
|
|
90
|
+
- **Conector MCP de Battle Arena** (claude.ai → Settings → Connectors, o `/mcp`): `plan_pending`,
|
|
91
|
+
`plan_my_day`, `plan_get`, `plan_save_draft`, `plan_can_complete`, `plan_complete`,
|
|
92
|
+
`plan_reopen`, `board_add`, `plan_summary`. Identidad = tu cuenta OAuth (sin api-key).
|
|
93
|
+
- **Conector de Linear**, como tú: busca tus tickets y, al completar, crea «tu ticket del día»
|
|
94
|
+
(`conquest:daily`) con el borrador que da `plan_can_complete`. Lo que no tiene ticket NO se crea:
|
|
95
|
+
va al tablero de la PM (`board_add`).
|
|
96
|
+
- **`plan.mjs`, solo local** (sin api-key ni red):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
node plan.mjs local-activity [--repo <ruta>]… # git de hoy SOLO en esas rutas (sin --repo, el cwd):
|
|
100
|
+
# rama, commits con tu email (máx. 10), archivos sin
|
|
101
|
+
# commit (solo nombres, máx. 20) y con `gh` el PR de
|
|
102
|
+
# la rama actual + los tuyos de los últimos 5 hábiles
|
|
103
|
+
node plan.mjs pdf --file summary.json [--reviewer <PM>] [--out <carpeta>] [--no-open]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`pdf` arma la «orden de batalla» (diseño A4 · Primero lo importante) con lo de `plan_summary`: HTML
|
|
107
|
+
con el logo embebido y, si la máquina tiene Chrome/Chromium/Edge (`BATTLE_ORDER_CHROME` para otra
|
|
108
|
+
ruta), el PDF con `--headless --print-to-pdf` en `~/Documents/Battle Arena/`. Sin navegador deja el
|
|
109
|
+
HTML listo para «Imprimir → Guardar como PDF». Es solo para ti: no se sube.
|
|
110
|
+
|
|
111
|
+
Su **fuente única** vive en este paquete: `skill/conquer/SKILL.md` (en el repo,
|
|
112
|
+
`.claude/skills/conquer` es un symlink hacia ella y `bundle:skill` no la toca).
|
|
113
|
+
|
|
114
|
+
### PR review loop (`pr-review-worktree` + skill `battlearena:pr-review-loop`)
|
|
23
115
|
|
|
24
116
|
Al crear un PR con `gh pr create`, el hook `pr-review-worktree` **sugiere** (no lanza) activar la skill
|
|
25
117
|
`pr-review-loop`. Es **opt-in**: la skill primero **pregunta** al usuario si quiere activarlo,
|
|
@@ -74,39 +166,38 @@ que corren las dos suites: `yarn workspace @thebattlearena/battle-arena-hooks te
|
|
|
74
166
|
Tras un envío exitoso con la key nueva, el flag se limpia solo. Lógica compartida en
|
|
75
167
|
`lib/reauth.mjs`.
|
|
76
168
|
|
|
77
|
-
## Instalación
|
|
78
|
-
|
|
79
|
-
La forma recomendada (cualquier SO, sin clonar) es el paquete npm — ver README raíz:
|
|
169
|
+
## Instalación
|
|
80
170
|
|
|
81
171
|
```bash
|
|
82
172
|
npx @thebattlearena/battle-arena-hooks@latest install \
|
|
83
|
-
--url https://api.battlearena.dev --api-key
|
|
173
|
+
--url https://api.battlearena.dev --api-key <tu-llave>
|
|
84
174
|
```
|
|
85
175
|
|
|
86
|
-
|
|
176
|
+
Requiere Claude Code CLI (`claude` en el PATH) y node ≥ 18. El instalador:
|
|
87
177
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
178
|
+
1. Copia los scripts y el plugin a `~/.claude/battle-arena/hooks/` (la misma ruta de antes).
|
|
179
|
+
2. Guarda la url y la llave en `~/.claude/battle-arena/config.json`.
|
|
180
|
+
3. **Limpia la versión anterior:** quita sus hooks de `~/.claude/settings.json` (si no, cada evento se
|
|
181
|
+
mandaría dos veces) y mueve sus skills sueltas de `~/.claude/skills` a
|
|
182
|
+
`~/.claude/battle-arena/legacy-skills/<fecha>/` (respaldo, no las borra).
|
|
183
|
+
4. Registra la carpeta como marketplace local e instala `battlearena@battlearena-local` para tu usuario
|
|
184
|
+
(`claude plugin marketplace add` + `claude plugin install --scope user`). Se lee directo de esa
|
|
185
|
+
carpeta: para actualizar basta volver a correr el instalador y `/reload-plugins`.
|
|
186
|
+
|
|
187
|
+
Desde el clone (contribuidores): `HOOK_STREAM_URL=… HOOK_STREAM_API_KEY=… bash apps/claude-hooks/install.sh`.
|
|
188
|
+
Para probar el plugin sin instalarlo: `npm run bundle:skill` y `claude --plugin-dir apps/claude-hooks/plugin`.
|
|
93
189
|
|
|
94
190
|
## Configuración
|
|
95
191
|
|
|
96
|
-
|
|
97
|
-
`~/.claude/battle-arena/config.json` y registra los hooks en `~/.claude/settings.json` con
|
|
98
|
-
comandos `node "<ruta>"` (sin `VAR=valor` inline, que no existe en Windows). Los hooks leen, en
|
|
99
|
-
orden de prioridad:
|
|
192
|
+
Los hooks y el Scorecard leen, en orden de prioridad:
|
|
100
193
|
|
|
101
194
|
1. **Variables de entorno** `HOOK_STREAM_URL` / `HOOK_STREAM_API_KEY` (si están seteadas).
|
|
102
|
-
2.
|
|
195
|
+
2. **`~/.claude/battle-arena/config.json`** (`{ "url": "...", "apiKey": "..." }`), que escribe el instalador.
|
|
103
196
|
3. Default `http://127.0.0.1:4500`.
|
|
104
197
|
|
|
105
198
|
`HOOK_STREAM_API_KEY` con `ARENA_REQUIRE_AUTH=true` necesita scopes `arena:read`
|
|
106
|
-
(handshake
|
|
107
|
-
|
|
108
|
-
El nudge de objetivos requiere además instalar la skill `arena-objective` en cada repo (o
|
|
109
|
-
global en `~/.claude/skills/`): ver `docs/how-to/install-arena-objective-skill.md`.
|
|
199
|
+
(handshake, ranking, Scorecard), `arena:write` (objetivos) y `arena:hands` (✋ desde el Scorecard);
|
|
200
|
+
`/ingest` acepta cualquier api-key válida.
|
|
110
201
|
|
|
111
202
|
## Cutover desde el colector legacy (speech-analytics/tooling/hook-stream)
|
|
112
203
|
|
package/cli.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// CLI del paquete @thebattlearena/battle-arena-hooks.
|
|
3
|
-
// Instala
|
|
3
|
+
// Instala Battle Arena en Claude Code como el plugin `battlearena` SIN clonar el repo: los hooks,
|
|
4
|
+
// las skills (/battlearena:conquer, …) y el Scorecard de tu día, todo en un solo plugin.
|
|
4
5
|
//
|
|
5
6
|
// npx @thebattlearena/battle-arena-hooks install \
|
|
6
7
|
// --url https://api.battlearena.dev --api-key sk_xxx
|
|
8
|
+
// npx @thebattlearena/battle-arena-hooks uninstall
|
|
7
9
|
//
|
|
8
10
|
// (también lee HOOK_STREAM_URL / HOOK_STREAM_API_KEY del entorno)
|
|
9
11
|
import {
|
|
10
12
|
cpSync,
|
|
11
13
|
existsSync,
|
|
12
14
|
mkdirSync,
|
|
13
|
-
readdirSync,
|
|
14
15
|
readFileSync,
|
|
15
16
|
writeFileSync,
|
|
16
17
|
} from 'node:fs';
|
|
@@ -18,6 +19,15 @@ import { dirname, join } from 'node:path';
|
|
|
18
19
|
import { fileURLToPath } from 'node:url';
|
|
19
20
|
import { execFileSync } from 'node:child_process';
|
|
20
21
|
import os from 'node:os';
|
|
22
|
+
import {
|
|
23
|
+
copyPlugin,
|
|
24
|
+
hasClaudeCli,
|
|
25
|
+
moveLegacySkills,
|
|
26
|
+
PLUGIN_ID,
|
|
27
|
+
registerPlugin,
|
|
28
|
+
unregisterPlugin,
|
|
29
|
+
withoutLegacyHooks,
|
|
30
|
+
} from './lib/plugin-install.mjs';
|
|
21
31
|
|
|
22
32
|
const PKG_DIR = dirname(fileURLToPath(import.meta.url));
|
|
23
33
|
const HOME = os.homedir();
|
|
@@ -35,6 +45,7 @@ const HOOK_FILES = [
|
|
|
35
45
|
'execution-coach-nudge.mjs',
|
|
36
46
|
'pr-review-worktree.mjs',
|
|
37
47
|
'objective.mjs',
|
|
48
|
+
'plan.mjs',
|
|
38
49
|
];
|
|
39
50
|
|
|
40
51
|
function arg(name) {
|
|
@@ -52,97 +63,110 @@ function gitEmail() {
|
|
|
52
63
|
}
|
|
53
64
|
}
|
|
54
65
|
|
|
66
|
+
/** Quita de settings.json los hooks de la versión anterior (ahora los trae el plugin). */
|
|
67
|
+
function cleanLegacyHooks() {
|
|
68
|
+
if (!existsSync(SETTINGS)) return 0;
|
|
69
|
+
const { settings, removed } = withoutLegacyHooks(
|
|
70
|
+
JSON.parse(readFileSync(SETTINGS, 'utf8')),
|
|
71
|
+
);
|
|
72
|
+
if (removed)
|
|
73
|
+
writeFileSync(SETTINGS, JSON.stringify(settings, null, 2) + '\n');
|
|
74
|
+
return removed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function cleanLegacy() {
|
|
78
|
+
const removed = cleanLegacyHooks();
|
|
79
|
+
if (removed)
|
|
80
|
+
console.log(
|
|
81
|
+
`🧹 ${removed} hooks de la versión anterior quitados de ${SETTINGS}`,
|
|
82
|
+
);
|
|
83
|
+
const backup = join(BA_DIR, 'legacy-skills', String(Date.now()));
|
|
84
|
+
const moved = moveLegacySkills(SKILLS_DEST, backup);
|
|
85
|
+
if (moved.length)
|
|
86
|
+
console.log(`🧹 skills sueltas movidas a ${backup}: ${moved.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
function install() {
|
|
56
90
|
const url =
|
|
57
91
|
arg('url') || process.env.HOOK_STREAM_URL || 'https://api.battlearena.dev';
|
|
58
92
|
const key = arg('api-key') || process.env.HOOK_STREAM_API_KEY || '';
|
|
59
93
|
|
|
94
|
+
if (!hasClaudeCli()) {
|
|
95
|
+
console.error(
|
|
96
|
+
'❌ No encontré el comando `claude` (Claude Code CLI). Instálalo y vuelve a correr este instalador:\n' +
|
|
97
|
+
' https://docs.claude.com/en/docs/claude-code/setup',
|
|
98
|
+
);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
60
101
|
if (!gitEmail()) {
|
|
61
102
|
console.log(
|
|
62
103
|
'⚠️ git config user.email vacío — configúralo: es tu identidad en el arena.',
|
|
63
104
|
);
|
|
64
105
|
}
|
|
65
106
|
|
|
66
|
-
// 1)
|
|
107
|
+
// 1) Scripts de los hooks → ~/.claude/battle-arena/hooks (las skills los buscan ahí)
|
|
67
108
|
mkdirSync(HOOKS_DEST, { recursive: true });
|
|
68
109
|
for (const f of HOOK_FILES) cpSync(join(PKG_DIR, f), join(HOOKS_DEST, f));
|
|
69
110
|
cpSync(join(PKG_DIR, 'lib'), join(HOOKS_DEST, 'lib'), { recursive: true });
|
|
70
|
-
console.log(`✅ hooks copiados a ${HOOKS_DEST}`);
|
|
71
|
-
|
|
72
|
-
// 2) Skills del paquete → ~/.claude/skills (cada subcarpeta de skill/*)
|
|
73
|
-
const skillsRoot = join(PKG_DIR, 'skill');
|
|
74
|
-
if (existsSync(skillsRoot)) {
|
|
75
|
-
mkdirSync(SKILLS_DEST, { recursive: true });
|
|
76
|
-
const skillDirs = readdirSync(skillsRoot, { withFileTypes: true }).filter(
|
|
77
|
-
(d) => d.isDirectory(),
|
|
78
|
-
);
|
|
79
|
-
for (const d of skillDirs) {
|
|
80
|
-
cpSync(join(skillsRoot, d.name), join(SKILLS_DEST, d.name), {
|
|
81
|
-
recursive: true,
|
|
82
|
-
});
|
|
83
|
-
console.log(`✅ skill ${d.name} instalada en ${SKILLS_DEST}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
111
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
// 2) El plugin (hooks, skills y Scorecard) y su marketplace local, junto a los scripts
|
|
113
|
+
copyPlugin(PKG_DIR, HOOKS_DEST);
|
|
114
|
+
|
|
115
|
+
// 3) Config (url + api-key) → ~/.claude/battle-arena/config.json. Los hooks y el Scorecard lo leen
|
|
116
|
+
// de ahí; así el comando es solo `node "<ruta>"`, idéntico en los 3 SOs.
|
|
90
117
|
writeFileSync(
|
|
91
118
|
join(BA_DIR, 'config.json'),
|
|
92
119
|
JSON.stringify({ url, apiKey: key || undefined }, null, 2) + '\n',
|
|
93
120
|
);
|
|
94
121
|
|
|
95
|
-
// 4)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
],
|
|
105
|
-
PreToolUse: ['emit.mjs'],
|
|
106
|
-
PostToolUse: ['emit.mjs', 'pr-review-worktree.mjs'],
|
|
107
|
-
Notification: ['emit.mjs'],
|
|
108
|
-
Stop: ['emit.mjs'],
|
|
109
|
-
SubagentStop: ['emit.mjs'],
|
|
110
|
-
PreCompact: ['emit.mjs'],
|
|
111
|
-
SessionEnd: ['emit.mjs'],
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
mkdirSync(CLAUDE, { recursive: true });
|
|
115
|
-
const settings = existsSync(SETTINGS)
|
|
116
|
-
? JSON.parse(readFileSync(SETTINGS, 'utf8'))
|
|
117
|
-
: {};
|
|
118
|
-
settings.hooks = settings.hooks || {};
|
|
119
|
-
for (const [event, scripts] of Object.entries(map)) {
|
|
120
|
-
const groups = (settings.hooks[event] = settings.hooks[event] || []);
|
|
121
|
-
// quita entradas previas del arena (idempotente) y reescribe en su grupo
|
|
122
|
-
for (const g of groups)
|
|
123
|
-
g.hooks = (g.hooks || []).filter(
|
|
124
|
-
(h) =>
|
|
125
|
-
!/battle-arena[/\\]hooks|claude-hooks[/\\]/.test(h.command || ''),
|
|
126
|
-
);
|
|
127
|
-
settings.hooks[event] = groups.filter((g) => (g.hooks || []).length > 0);
|
|
128
|
-
settings.hooks[event].push({
|
|
129
|
-
hooks: scripts.map((f) => ({ type: 'command', command: cmd(f) })),
|
|
130
|
-
});
|
|
122
|
+
// 4) Registrar e instalar el plugin (idempotente). Va ANTES de limpiar lo anterior: si falla,
|
|
123
|
+
// la instalación vieja sigue funcionando intacta.
|
|
124
|
+
try {
|
|
125
|
+
registerPlugin(HOOKS_DEST);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error(
|
|
128
|
+
`❌ No pude instalar el plugin (tu instalación anterior sigue igual): ${String(error.stderr || error.message).trim()}`,
|
|
129
|
+
);
|
|
130
|
+
process.exit(1);
|
|
131
131
|
}
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
|
|
133
|
+
// 5) Ya con el plugin activo, lo que dejó la versión anterior: hooks en settings.json y skills
|
|
134
|
+
// sueltas (no duplicar cada evento)
|
|
135
|
+
cleanLegacy();
|
|
136
|
+
|
|
137
|
+
console.log(
|
|
138
|
+
`✅ plugin ${PLUGIN_ID} instalado (se lee de ${join(HOOKS_DEST, 'plugin')})`,
|
|
139
|
+
);
|
|
134
140
|
console.log(` colector: ${url}${key ? '' : ' (sin api-key)'}`);
|
|
135
141
|
console.log(
|
|
136
|
-
'\nListo. Abre Claude Code
|
|
142
|
+
'\nListo. Abre Claude Code: /battlearena:conquer arma tu plan y /scorecard abre tu día.' +
|
|
143
|
+
'\nSi ya tenías una sesión abierta, corre /reload-plugins.',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function uninstall() {
|
|
148
|
+
const result = hasClaudeCli()
|
|
149
|
+
? unregisterPlugin()
|
|
150
|
+
: { plugin: false, marketplace: false };
|
|
151
|
+
cleanLegacyHooks();
|
|
152
|
+
console.log(
|
|
153
|
+
result.plugin
|
|
154
|
+
? `✅ plugin ${PLUGIN_ID} desinstalado`
|
|
155
|
+
: `ℹ️ el plugin ${PLUGIN_ID} no estaba instalado`,
|
|
156
|
+
);
|
|
157
|
+
console.log(
|
|
158
|
+
` Tu config (${join(BA_DIR, 'config.json')}) se queda; bórrala si ya no usarás Battle Arena.`,
|
|
137
159
|
);
|
|
138
160
|
}
|
|
139
161
|
|
|
162
|
+
const COMMANDS = { install, uninstall };
|
|
140
163
|
const command = process.argv[2];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} else {
|
|
164
|
+
const run = COMMANDS[command];
|
|
165
|
+
if (!run) {
|
|
144
166
|
console.log(
|
|
145
|
-
'Uso: npx @thebattlearena/battle-arena-hooks install [--url <colector>] [--api-key <key>]'
|
|
167
|
+
'Uso: npx @thebattlearena/battle-arena-hooks install [--url <colector>] [--api-key <key>]\n' +
|
|
168
|
+
' npx @thebattlearena/battle-arena-hooks uninstall',
|
|
146
169
|
);
|
|
147
170
|
process.exit(command ? 1 : 0);
|
|
148
171
|
}
|
|
172
|
+
run();
|
|
@@ -36,7 +36,7 @@ const SELF_ASSESS =
|
|
|
36
36
|
'Battle Arena — antes de responder, juzga si el pedido del usuario está claramente vago, ' +
|
|
37
37
|
'subespecificado o confuso: sin objetivo claro, sin criterio de aceptación, o sin saber para ' +
|
|
38
38
|
'quién/para qué es (p. ej. "quiero un código para desplegar el proyecto" sin decir qué app, ' +
|
|
39
|
-
'entorno ni cómo se ve "listo"). Si CLARAMENTE lo está, invoca el skill `execution-coach` para ' +
|
|
39
|
+
'entorno ni cómo se ve "listo"). Si CLARAMENTE lo está, invoca el skill `battlearena:execution-coach` para ' +
|
|
40
40
|
'interceptar con preguntas mayéuticas antes de codear. Sé conservador: ante la duda, o si el ' +
|
|
41
41
|
'pedido ya es concreto, sigue normal sin interrumpir.';
|
|
42
42
|
|
|
@@ -228,7 +228,7 @@ process.stdin.on('end', () => {
|
|
|
228
228
|
let context = SELF_ASSESS;
|
|
229
229
|
if (signals.length) {
|
|
230
230
|
context +=
|
|
231
|
-
'\n\nAdemás, señales deterministas en esta sesión (revísalas con `execution-coach` si ' +
|
|
231
|
+
'\n\nAdemás, señales deterministas en esta sesión (revísalas con `battlearena:execution-coach` si ' +
|
|
232
232
|
'siguen vigentes):\n' +
|
|
233
233
|
signals.map((s) => `- ${s}`).join('\n');
|
|
234
234
|
}
|
|
Binary file
|