navori 0.2.0 → 0.2.2

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.
Files changed (24) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -17
  3. package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +15 -9
  4. package/dist/assets/core/core-assets/lib-skills/formik.md +57 -0
  5. package/dist/assets/core/core-assets/lib-skills/joi-validation.md +73 -0
  6. package/dist/assets/core/core-assets/{presets/express-mongoose/skills → lib-skills}/mongoose.md +3 -2
  7. package/dist/assets/core/core-assets/lib-skills/redux-toolkit.md +59 -0
  8. package/dist/assets/core/core-assets/lib-skills/socketio.md +56 -0
  9. package/dist/assets/core/core-assets/lib-skills/tanstack-query.md +58 -0
  10. package/dist/assets/core/core-assets/managed/cierre-sesion.md +1 -1
  11. package/dist/assets/core/core-assets/managed/idioma-rol.md +1 -1
  12. package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -0
  13. package/dist/assets/core/core-assets/presets/background-worker/managed/stack.md +18 -0
  14. package/dist/assets/core/core-assets/presets/background-worker/skills/job-scheduling.md +54 -0
  15. package/dist/assets/core/core-assets/presets/background-worker/skills/queue-consumers.md +55 -0
  16. package/dist/assets/core/core-assets/presets/background-worker/skills/worker-lifecycle.md +57 -0
  17. package/dist/assets/core/core-assets/presets/background-worker.json +39 -0
  18. package/dist/assets/core/core-assets/presets/express-mongoose/managed/stack.md +2 -2
  19. package/dist/assets/core/core-assets/presets/express-mongoose/skills/pr-create.md +18 -18
  20. package/dist/assets/core/core-assets/presets/express-mongoose.json +0 -12
  21. package/dist/assets/core/core-assets/settings/settings-base.json +22 -1
  22. package/dist/index.js +797 -346
  23. package/package.json +5 -4
  24. /package/dist/assets/core/core-assets/{presets/express-mongoose/skills → lib-skills}/zod-validation.md +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ulises Ciprés
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -25,8 +25,8 @@ navori init
25
25
 
26
26
  El `init` detecta automáticamente del repo:
27
27
  - **Nombre** del proyecto (de `package.json`, `pyproject.toml`, `Cargo.toml`, git remote o basename)
28
- - **Stack**: framework (Next.js, Vite, NestJS, Expo, etc.), UI, forms, state, test
29
- - **Preset sugerido** basado en el stack (ej. `vite-react-ts-mantine`, `nextjs-apollo`, `bun-keystone`)
28
+ - **Stack**: framework (Next.js, Vite, NestJS, Express, Astro, etc.), UI, forms, state, test
29
+ - **Preset sugerido** según el stack (ej. `vite-react-ts-mantine`, `nextjs`, `express-mongoose`)
30
30
  - **Quality gate** compuesto de los scripts del `package.json`
31
31
  - **Branch base** del git (`origin/HEAD`, fallback main/master/develop)
32
32
  - **Infraestructura Claude existente** (`.claude/`, `CLAUDE.md`, `AGENTS.md`, agents, skills) — ofrece coexistir o reemplazar con backup
@@ -34,20 +34,56 @@ El `init` detecta automáticamente del repo:
34
34
  Y genera:
35
35
  - `navori.config.json` — fuente de verdad del repo
36
36
  - `CLAUDE.md` con managed blocks que el CLI mantiene sincronizados
37
+ - `.claude/` con agentes, skills, hooks y settings
37
38
 
38
39
  ## Comandos
39
40
 
40
41
  | Comando | Qué hace |
41
42
  |---|---|
42
- | `init` | Bootstrap del repo con detección automática + wizard |
43
- | `add <plugin>` | Activa un plugin + opcionalmente instala la tool externa |
43
+ | `init` | Bootstrap del repo con detección automática + wizard (o `--recommended` sin preguntas) |
44
+ | `add <plugin>` | Activa un plugin y opcionalmente instala la tool externa |
44
45
  | `configure <section>` | Ajusta una sección del config sin re-correr el wizard |
45
46
  | `update` | Re-detecta el repo, refresca config y corre sync en un paso |
46
- | `render` | Genera CLAUDE.md con los managed blocks |
47
- | `sync` | Refresca managed blocks con conflict resolution + backups |
48
- | `doctor` | Inspecciona config + reporta procedencia de cada managed block |
49
- | `workspace <sub>` | Gestiona workspaces cross-repo (init, ls, show, add-repo) |
50
- | `ticket <sub>` | Gestiona tickets-as-files en un workspace (new, list, show) |
47
+ | `render` | Genera CLAUDE.md y `.claude/` desde el config (preview por default; `--apply` escribe) |
48
+ | `sync` | Refresca los managed blocks con conflict resolution + backups |
49
+ | `preset init <id>` | Scaffoldea un preset local en `.navori/presets/<id>/` |
50
+ | `scan` | Detecta workspaces nuevos en monorepos (`pnpm-workspace.yaml` / `package.json#workspaces`) |
51
+ | `doctor` | Audita el config + reporta procedencia y drift de cada managed block (`--strict` para CI) |
52
+ | `status` | Snapshot rápido: config, plugins activos, conteo de drift y próximos pasos |
53
+ | `bench` | Corre `render` en dry-run N veces y reporta latencias (detecta regresiones locales) |
54
+ | `workspace <sub>` | Gestiona workspaces cross-repo (`init`, `ls`, `show`, `rm`) |
55
+ | `ticket <sub>` | Gestiona tickets-as-files en un workspace (`new`, `list`, `show`, `archive`, `delete`) |
56
+ | `backup <sub>` | Lista y restaura backups de `~/.navori/backups/` |
57
+ | `migrations <sub>` | Lista y restaura migraciones de `~/.navori/migrations/` |
58
+
59
+ ## Presets
60
+
61
+ Un preset aporta skills y reglas específicas del stack además del core. El `init` te sugiere uno según lo que detecta.
62
+
63
+ **Presets oficiales (incluidos):**
64
+
65
+ | Preset | Stack |
66
+ |---|---|
67
+ | `vite-react-ts-mantine` | Vite + React + TS + Mantine (SPA) |
68
+ | `nextjs` | Next.js (App Router) |
69
+ | `nestjs` | NestJS (backend) |
70
+ | `express-mongoose` | Express + Mongoose (backend) |
71
+ | `background-worker` | Worker de fondo (jobs + colas: agenda / bullmq / amqplib) |
72
+ | `astro` | Astro (static / SSR) |
73
+ | `medusa` | Medusa.js v2 (backend) |
74
+
75
+ **¿Tu stack no tiene preset oficial?** No pasa nada. El `init` instala el harness completo (agentes, gates, protocolo, SDD) y funciona desde ya — solo te quedas sin los skills específicos del stack. El init te avisa, te deja en el baseline (`preset: custom`) y te sugiere cubrir el gap con un preset local.
76
+
77
+ **Presets locales** — crea uno checked-in al repo bajo `.navori/presets/<id>/`:
78
+
79
+ ```bash
80
+ navori preset init sveltekit
81
+ # ✓ .navori/presets/sveltekit/ (manifest + managed/stack.md + skills/)
82
+ # ✓ navori.config.json → preset: sveltekit
83
+ # → edita las plantillas y corre 'navori render --apply'
84
+ ```
85
+
86
+ La resolución es **local → bundled**: si tienes un preset local con el mismo id que uno oficial, gana el local. Así puedes override un preset incluido sin tocar el paquete.
51
87
 
52
88
  ## Plugins disponibles
53
89
 
@@ -66,6 +102,15 @@ navori add engram # te ofrece instalar la tool externa si falta
66
102
  navori add engram --skip-install # solo registra el plugin
67
103
  ```
68
104
 
105
+ ## Harness defensivo (read-only por default)
106
+
107
+ El harness que genera `navori` trae permisos seguros desde el arranque, para que tengas menos prompts en lo cotidiano sin bajar la guardia en lo peligroso:
108
+
109
+ - **Las lecturas no piden confirmación**: `git status/diff/log/show`, `ls`, `cat`, `grep`, `Read`/`Glob`/`Grep`, etc. corren sin interrumpirte.
110
+ - **Lo destructivo pide confirmación** (`ask`): `rm -rf`, `git push --force`, `git reset --hard`, `git clean -f`, `chmod -R`, …
111
+ - **Lo catastrófico se rechaza** (`deny`): `rm -rf /`, `sudo rm`, `mkfs`, …
112
+ - Un hook `guard-destructive` actúa como backstop adicional.
113
+
69
114
  ## Workspace + tickets cross-repo
70
115
 
71
116
  Si un ticket toca varios repos (frontend + backend + microservicio), el workspace te da un punto único:
@@ -81,7 +126,7 @@ navori workspace add-repo bonum --name backend --path ~/dev/bonum/nexus --stack
81
126
  # Crear ticket
82
127
  navori ticket new bonum BNM-123 --title "Checkout flow rebuild"
83
128
 
84
- # En cada repo que tocás el ticket, agregá una referencia:
129
+ # En cada repo que toca el ticket, agrega una referencia:
85
130
  # echo "ticket: BNM-123" >> progress/current.md
86
131
 
87
132
  # Ver el ticket + en qué repos aparece
@@ -91,6 +136,18 @@ navori ticket show bonum BNM-123
91
136
  El workspace también guarda defaults heredables:
92
137
  ```bash
93
138
  navori init --workspace bonum # hereda engines, plugins, branchBase, etc.
139
+
140
+ # Ajustar un default sin editar el manifest a mano
141
+ navori workspace set-default bonum branchBase main
142
+ navori workspace set-default bonum prTarget develop # PRs van a develop, no a main
143
+ navori workspace set-default bonum engines claude,cursor
144
+ navori workspace set-default bonum plugins.engram.enabled true
145
+ ```
146
+
147
+ Y re-renderizar todos los repos del workspace de una vez:
148
+ ```bash
149
+ navori workspace render bonum # preview (no toca disco)
150
+ navori workspace render bonum --apply # escribe en cada repo
94
151
  ```
95
152
 
96
153
  Storage: `~/.navori/workspaces/<name>/` (manifest + tickets/ + backups/).
@@ -105,17 +162,17 @@ contenido sincronizado
105
162
  <!-- /navori:managed id="idioma-rol" -->
106
163
  ```
107
164
 
108
- - **`hash`**: detecta si vos editaste el bloque (sync te avisa antes de pisar)
165
+ - **`hash`**: detecta si editaste el bloque (sync te avisa antes de pisarlo)
109
166
  - **`version`**: cuando se publica una nueva versión de `@navori/core` o un plugin, `sync` reporta "update available"
110
167
  - **`source`**: qué paquete es dueño del bloque (`doctor` te muestra la procedencia de cada uno)
111
168
 
112
- Si modificás un managed block a mano y después corrés `sync`, vas a ver:
169
+ Si modificas un managed block a mano y después corres `sync`, vas a ver:
113
170
  ```
114
171
  Conflict in 'idioma-rol':
115
172
  - tu versión
116
173
  + versión del Core
117
174
  ```
118
- Y elegís: `skip-conflicts` (mantener tu edit), `apply-all` (pisar) o `abort`.
175
+ Y eliges: `skip-conflicts` (mantener tu edit), `apply-all` (pisar) o `abort`.
119
176
 
120
177
  Backups automáticos en `~/.navori/backups/<timestamp>/` antes de cada `sync` (retención 30 días).
121
178
 
@@ -128,17 +185,20 @@ navori configure plugins # multiselect de plugins activos
128
185
  navori configure quality-gate # nuevo comando de quality gate
129
186
  navori configure language en # switch a inglés (fallback a es)
130
187
  navori configure engines # multiselect: claude / agents-md / cursor / copilot
188
+ navori configure branch-base main # punto de fork / rama protegida
189
+ navori configure pr-target develop # rama destino del PR (gh pr create --base)
131
190
  navori configure workspace bonum # asociar a un workspace
132
191
  ```
133
192
 
134
193
  ## Filosofía
135
194
 
136
- - **Cero opinión sobre tu proceso**. El CLI detecta y propone; vos decidís.
137
- - **Coexiste con harness existente**. Modo `coexist` no toca nada que ya tenías.
195
+ - **Cero opinión sobre tu proceso**. El CLI detecta y propone; decides.
196
+ - **Coexiste con harness existente**. El modo `coexist` no toca nada que ya tenías.
138
197
  - **Nunca pisa silenciosamente**. Hash en el marker + backups antes de cada write.
198
+ - **Read-only por default**. Las lecturas no piden permiso; lo destructivo sí.
139
199
  - **Output legible siempre**. Texto + `--json` para piping en CI.
140
- - **Bilingüe ready**. Schema soporta `language: es | en`. Hoy solo `es` está full; `en` cae en fallback honesto.
200
+ - **Bilingüe ready**. El schema soporta `language: es | en`. Hoy `es` está full; `en` cae en fallback honesto.
141
201
 
142
202
  ## Licencia
143
203
 
144
- ISC.
204
+ MIT.
@@ -18,20 +18,22 @@ Te encargas del **cierre del ciclo**: commits Conventional bien estructurados y
18
18
  ## Cuándo NO activar
19
19
 
20
20
  - Working tree con cambios sin commitear cuando el usuario solo pidió "abre el PR" → primero commiteas o pides permiso.
21
- - Estás en `{{branchBase}}` u otra rama protegida → abort + pedir branch.
21
+ - Estás en `{{branchBase}}` o `{{prTarget}}` u otra rama protegida → abort + pedir branch.
22
22
  - Harness activo y `.claude/progress/review_*.md` reciente contiene `CHANGES_REQUESTED` → no se crea PR.
23
23
  - Quality gate en rojo en este turno.
24
24
 
25
+ > **Dos ramas, dos roles:** `{{branchBase}}` es el punto de fork (de dónde ramificaste). `{{prTarget}}` es la rama destino del PR (`gh pr create --base`). Suelen coincidir; cuando difieren, el PR y su diff se calculan contra `{{prTarget}}`.
26
+
25
27
  ## Pre-flight obligatorio
26
28
 
27
29
  Corre estos chequeos antes de redactar nada. Si algo falla, paras y reportas.
28
30
 
29
31
  ```bash
30
32
  git status --porcelain # qué falta commitear
31
- git rev-parse --abbrev-ref HEAD # no puede ser {{branchBase}}
32
- git fetch origin {{branchBase}} --quiet
33
- git log origin/{{branchBase}}..HEAD --oneline # debe haber ≥1 commit (o cambios para commitear)
34
- git diff origin/{{branchBase}}...HEAD --stat # scope del PR
33
+ git rev-parse --abbrev-ref HEAD # no puede ser {{branchBase}} ni {{prTarget}}
34
+ git fetch origin {{prTarget}} --quiet
35
+ git log origin/{{prTarget}}..HEAD --oneline # debe haber ≥1 commit (o cambios para commitear)
36
+ git diff origin/{{prTarget}}...HEAD --stat # scope REAL del PR (contra el target)
35
37
  gh auth status # gh autenticado
36
38
  ```
37
39
 
@@ -59,10 +61,11 @@ Sin `APPROVED` y con harness activo → abort, dile al usuario que falta review.
59
61
 
60
62
  ## Flujo de PR
61
63
 
62
- 1. **Recopilar contexto** (curado, no volcar todo el repo):
63
- - `git log origin/{{branchBase}}..HEAD --oneline` — commits incluidos.
64
- - `git diff origin/{{branchBase}}...HEAD --stat` — siempre.
65
- - `git diff origin/{{branchBase}}...HEAD` — solo si el diff < 500 líneas. Si es mayor, usa solo el stat + lista de archivos + los hunks de los 2–3 archivos más relevantes.
64
+ 1. **Recopilar contexto** (curado, no volcar todo el repo). El diff del PR es contra `{{prTarget}}` (lo que GitHub mostrará):
65
+ - `git log origin/{{prTarget}}..HEAD --oneline` — commits incluidos.
66
+ - `git diff origin/{{prTarget}}...HEAD --stat` — siempre.
67
+ - `git diff origin/{{prTarget}}...HEAD` — solo si el diff < 500 líneas. Si es mayor, usa solo el stat + lista de archivos + los hunks de los 2–3 archivos más relevantes.
68
+ - **Arrastre de commits** (solo si `{{branchBase}}` ≠ `{{prTarget}}`): `git fetch origin {{branchBase}} --quiet` y `git rev-list --count origin/{{prTarget}}..origin/{{branchBase}}`. Si es > 0, `{{branchBase}}` va adelantado de `{{prTarget}}` y tu PR arrastra esos commits ajenos: avisa al usuario y sugiere rebasar sobre `{{prTarget}}` antes de abrir.
66
69
  - Ticket si aplica: nombre del branch (ej. `BT-1234-fix-x` → `BT-1234`) o referencia en el primer commit.
67
70
  - `.claude/progress/impl_<feature>.md` si existe — decisiones no obvias.
68
71
 
@@ -79,6 +82,7 @@ Sin `APPROVED` y con harness activo → abort, dile al usuario que falta review.
79
82
 
80
83
  ```bash
81
84
  gh pr create \
85
+ --base {{prTarget}} \
82
86
  --title "<title validado>" \
83
87
  --body "$(cat <<'EOF'
84
88
  <body validado>
@@ -86,6 +90,8 @@ Sin `APPROVED` y con harness activo → abort, dile al usuario que falta review.
86
90
  )"
87
91
  ```
88
92
 
93
+ Siempre pasa `--base {{prTarget}}` explícito — no dejes que `gh` use la rama default del repo. Si el target cambió, ajústalo con `navori configure pr-target`.
94
+
89
95
  5. **Output al usuario**: solo la URL del PR + 1 línea con el título. Nada más.
90
96
 
91
97
  ## Template del body (default genérico)
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: formik
3
+ description: Patrones de Formik en React+TS — schema de validación, estado de form, submit, errores por campo. Aplica al crear o tocar formularios con Formik.
4
+ type: reference
5
+ ---
6
+
7
+ # Formik — convenciones
8
+
9
+ ## Cuándo usar este skill
10
+
11
+ Al crear o tocar un formulario con Formik: definir initial values, validación, submit, o pintar errores. Formik es la fuente de verdad del estado del form — no dupliques sus valores en `useState` paralelos.
12
+
13
+ ## El patrón
14
+
15
+ Validación declarativa con un schema (Yup/Zod), no a mano en `validate`:
16
+
17
+ ```ts
18
+ const formik = useFormik({
19
+ initialValues: { email: '', role: 'coachee' },
20
+ validationSchema: toFormikValidationSchema(loginSchema), // zod, o un Yup schema
21
+ onSubmit: async (values, { setSubmitting, setStatus }) => {
22
+ try {
23
+ await api.login(values);
24
+ } catch (err) {
25
+ setStatus(toFormError(err)); // error de servidor, no de campo
26
+ } finally {
27
+ setSubmitting(false);
28
+ }
29
+ },
30
+ });
31
+ ```
32
+
33
+ En el JSX: `formik.getFieldProps('email')` cablea value/onChange/onBlur; el error se muestra solo si el campo fue tocado: `formik.touched.email && formik.errors.email`.
34
+
35
+ ## Gotchas que muerden
36
+
37
+ - **Una sola fuente de verdad.** Los valores viven en Formik; nada de `useState` espejo que se desincroniza.
38
+ - **`touched` antes de mostrar error** — pintar errores antes del primer blur frustra al usuario; usa `touched.<campo> && errors.<campo>`.
39
+ - **`isSubmitting`** deshabilita el botón y evita doble submit; resetéalo siempre en `finally`.
40
+ - **Errores de campo vs de servidor**: un fallo de validación va a `errors` (vía schema); un fallo de API va a `setStatus`/`setFieldError`, no se inventa como error de campo.
41
+ - **Forms grandes** re-renderizan todo en cada tecla; aísla con `<Field>`/componentes memoizados si pesa.
42
+ - **`enableReinitialize`** cuando los initial values llegan async (editar un recurso cargado), o el form arranca vacío.
43
+
44
+ ## Reglas duras
45
+
46
+ 1. Validación en un schema (Yup/Zod), nunca lógica suelta en `validate` inline.
47
+ 2. Estado del form solo en Formik; sin `useState` paralelos a sus valores.
48
+ 3. Error de campo solo tras `touched`; error de servidor vía `setStatus`/`setFieldError`.
49
+ 4. `isSubmitting` controla el botón y se limpia en `finally`.
50
+ 5. `enableReinitialize` para formularios de edición con carga async.
51
+
52
+ ## Antes de declarar listo
53
+
54
+ - El form valida contra un schema y muestra errores solo tras blur.
55
+ - El submit deshabilita el botón y maneja el error de servidor aparte.
56
+ - No hay estado del form duplicado fuera de Formik.
57
+ - `{{qualityGate.fast}}` en verde.
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: joi-validation
3
+ description: Validación de input con Joi (@hapi/joi) en Express — schemas por recurso, middleware validate genérico, DTOs explícitos. Aplica al crear schemas o tocar input validation de body/query/params.
4
+ type: reference
5
+ ---
6
+
7
+ # Joi Validation — el patrón canónico
8
+
9
+ Un schema por recurso (`<resource>.schema.ts`), validado por un middleware genérico que reemplaza `req[target]` con el valor convertido. Como Joi no infiere tipos, el DTO es una `interface` explícita junto al schema.
10
+
11
+ ## Cuándo usar este skill
12
+
13
+ Al crear un schema, agregar validación a un endpoint, definir un DTO, o tocar input de body/query/params.
14
+
15
+ ## El patrón
16
+
17
+ El middleware vive en `helpers/validate.ts`: corre `schema.validate(req[target], { abortEarly: true, convert: true })`, y al fallar lanza `BadRequestError(\`${detail.path.join('.')}: ${detail.message}\`)` con el primer issue. En éxito, reasigna `req[target] = value` (el valor ya convertido). Schema y DTO:
18
+
19
+ ```ts
20
+ const objectId = Joi.string().pattern(/^[a-f\d]{24}$/i).message('Invalid ObjectId');
21
+
22
+ export const createResourceSchema = Joi.object({
23
+ owner: objectId.required(),
24
+ resourceType: Joi.string().valid(...Object.values(ResourceTypeEnum)).required(),
25
+ page: Joi.number().integer().positive().default(1),
26
+ });
27
+ export const updateResourceSchema = createResourceSchema.fork(
28
+ Object.keys(createResourceSchema.describe().keys),
29
+ (s) => s.optional(),
30
+ );
31
+
32
+ export interface CreateResourceDto {
33
+ owner: string;
34
+ resourceType: ResourceTypeEnum;
35
+ page: number;
36
+ }
37
+ ```
38
+
39
+ En la route: `router.post('/', validate(createResourceSchema, 'body'), ...)`. En el controller el cast `req.body as CreateResourceDto` es seguro porque el middleware ya validó y convirtió.
40
+
41
+ ## Gotchas que muerden
42
+
43
+ - **ObjectId pelado** (`Joi.string()`) deja pasar `"abc"`; Mongoose lanza CastError 500 en vez de 400 limpio. Usa siempre el helper `objectId`.
44
+ - **`convert: true` es obligatorio** para query/params: las query strings llegan como string y Joi sin `convert` rechaza `Joi.number()`. Con `convert` las castea a number/date/boolean.
45
+ - **`abortEarly`**: con `false` juntas todos los errores; con `true` (default recomendado aquí) cortas en el primero — sé consistente con lo que el middleware reporta.
46
+
47
+ ## Reglas duras
48
+
49
+ 1. Toda validación en el schema, nunca inline en el controller.
50
+ 2. El schema vive en `<resource>.schema.ts`, nunca en las routes.
51
+ 3. DTO como `interface` explícita al lado del schema — mantenlos en sync (Joi no infiere tipos).
52
+ 4. Nada de `Joi.any()`: equivale a `any`, prohibido en código nuevo.
53
+ 5. Un solo validador por endpoint — no mezcles Joi + Zod (al migrar entre ambos, migra el endpoint completo).
54
+ 6. ObjectId con el helper `objectId`; query/params siempre con `convert: true`.
55
+
56
+ ## Tabla rápida
57
+
58
+ | Necesito validar | Helper |
59
+ |---|---|
60
+ | ObjectId | `objectId` (pattern `/^[a-f\d]{24}$/i`) |
61
+ | String no vacío | `Joi.string().trim().min(1)` |
62
+ | Number desde query | `Joi.number().integer().positive()` (+ `convert: true`) |
63
+ | Date | `Joi.date()` (+ `convert: true`) |
64
+ | Enum TS / literal | `Joi.string().valid(...Object.values(MyEnum))` |
65
+ | Update parcial | `schema.fork(keys, (s) => s.optional())` |
66
+ | Validación cruzada | `.custom((v, helpers) => ...)` o `Joi.object().and('a', 'b')` |
67
+
68
+ ## Antes de declarar listo
69
+
70
+ - El schema vive en `<resource>.schema.ts` y el DTO es una `interface` explícita.
71
+ - El endpoint usa `validate(schema, target)`; sin validación inline en el controller.
72
+ - Campos ObjectId con el helper `objectId`; el middleware corre con `convert: true`.
73
+ - `{{qualityGate.fast}}` en verde.
@@ -10,6 +10,8 @@ type: reference
10
10
 
11
11
  Cuando la tarea toca `domain/models` o ejecuta operaciones de Mongoose en los controllers. Mongoose 6+ sobre MongoDB. El repo no usa repository wrappers: los controllers tocan los Models directo, así que null-guards, casts de ObjectId y `.lean()` viven en cada controller method.
12
12
 
13
+ En **NestJS** (`@nestjs/mongoose`) el Model no se importa directo: se inyecta con `@InjectModel(Resource.name) private resourceModel: Model<ResourceDocument>` en el constructor del service. El resto de patrones (`.lean()`, `new Types.ObjectId`, null-guards, soft delete) aplican igual.
14
+
13
15
  ## Patrón canónico
14
16
 
15
17
  ```ts
@@ -40,7 +42,7 @@ Valida el formato en el schema (`z.string().regex(/^[a-f\d]{24}$/i, ...)`); si n
40
42
 
41
43
  - **N+1**: `populate` ejecuta queries extra. En paginate sobre datasets grandes usa `$lookup` en vez de `populate`.
42
44
  - **`.lean()`**: el resultado no tiene `.save()`, `.delete()` ni virtuals. Si necesitas mutar, no lo uses.
43
- - **Soft delete**: con `mongoose-delete`, `find` ya excluye `deleted: true`; borra con `doc.delete()` (no `findByIdAndDelete`, que es hard delete) y restaura con `doc.restore()`. Plugin sin tipos → `@ts-expect-error` puntual.
45
+ - **Soft delete**: con `mongoose-delete`, `find` ya excluye `deleted: true`; borra con `doc.delete()` (no `findByIdAndDelete`) y restaura con `doc.restore()`.
44
46
 
45
47
  ## Reglas duras
46
48
 
@@ -49,7 +51,6 @@ Valida el formato en el schema (`z.string().regex(/^[a-f\d]{24}$/i, ...)`); si n
49
51
  3. **`.lean()` cuando no necesitas mutar** — evita el overhead de documentos Mongoose.
50
52
  4. **Comparar ObjectId con `.equals()`** / `.toString()`, nunca `==`.
51
53
  5. **Respeta el soft delete del repo** — no hard delete en modelos con `mongoose-delete`.
52
- 6. **Cast explícito solo cuando hace falta** — `new Types.ObjectId(id)` para aggregations/queries complejas; nada de casts innecesarios.
53
54
 
54
55
  ## Tabla rápida
55
56
 
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: redux-toolkit
3
+ description: Patrones de Redux Toolkit en React+TS — slices, store tipado, hooks tipados, async thunks, selectores. Aplica al tocar estado global, slices o el store.
4
+ type: reference
5
+ ---
6
+
7
+ # Redux Toolkit — convenciones
8
+
9
+ ## Cuándo usar este skill
10
+
11
+ Al crear o tocar un slice, el store, async thunks, o leer/escribir estado global. RTK es el estándar — nada de `createStore` pelado, action types a mano, ni `connect`. Estado de servidor (fetch/cache) NO va aquí: eso es TanStack Query. Redux es para estado de cliente compartido (sesión, UI cross-página, carrito).
12
+
13
+ ## El patrón
14
+
15
+ ```ts
16
+ const slice = createSlice({
17
+ name: 'session',
18
+ initialState,
19
+ reducers: {
20
+ setActive(state, action: PayloadAction<Session>) {
21
+ state.active = action.payload; // Immer: "mutas" un draft, no el real
22
+ },
23
+ },
24
+ extraReducers: (b) => {
25
+ b.addCase(loadSession.fulfilled, (s, a) => { s.active = a.payload; });
26
+ },
27
+ });
28
+ export const { setActive } = slice.actions;
29
+ ```
30
+
31
+ Store + hooks tipados una sola vez, y se usan en toda la app:
32
+
33
+ ```ts
34
+ export const useAppDispatch: () => AppDispatch = useDispatch;
35
+ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
36
+ ```
37
+
38
+ ## Gotchas que muerden
39
+
40
+ - **Immer solo dentro de `createSlice`.** Ahí "mutas" el draft; fuera de un reducer, mutar el state es un bug. No retornes Y mutes en el mismo reducer.
41
+ - **Selectores memoizados** con `createSelector` cuando derivan/transforman — un selector que crea un array/objeto nuevo en cada llamada re-renderiza siempre.
42
+ - **`useSelector` devuelve la referencia**: selecciona lo mínimo, no el slice entero.
43
+ - **Async**: `createAsyncThunk` para casos simples; si es data de API que cacheas/invalidas, evalúa RTK Query en vez de thunks + slice manual.
44
+ - **No-serializables** (Date, Map, funciones) fuera del store; rompen devtools y persistencia.
45
+
46
+ ## Reglas duras
47
+
48
+ 1. Estado global solo vía slices de RTK; nada de Context improvisado para lo mismo.
49
+ 2. Hooks `useAppDispatch`/`useAppSelector` tipados, nunca los crudos sin tipo.
50
+ 3. Selecciona lo mínimo y memoiza los derivados con `createSelector`.
51
+ 4. Estado de servidor no vive en Redux — eso es cache de queries.
52
+ 5. Solo valores serializables en el store.
53
+
54
+ ## Antes de declarar listo
55
+
56
+ - El slice nuevo expone acciones tipadas y se consume con los hooks tipados.
57
+ - Los selectores derivados están memoizados; los componentes seleccionan lo mínimo.
58
+ - Nada de data de API duplicada en el store si ya hay capa de queries.
59
+ - `{{qualityGate.fast}}` en verde.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: socketio
3
+ description: Patrones de Socket.IO en un servicio Node — namespaces, rooms, auth en el handshake, eventos tipados, cleanup. Aplica al tocar realtime, gateways o handlers de socket.
4
+ type: reference
5
+ ---
6
+
7
+ # Socket.IO — convenciones del servicio
8
+
9
+ ## Cuándo usar este skill
10
+
11
+ Al agregar o tocar realtime: un namespace, un evento, autenticación de conexión, o broadcast a un room. Socket.IO sobre el HTTP server de Express. La regla base: el handler de socket es una capa de transporte, no de negocio — delega al mismo service/controller que usan las rutas HTTP.
12
+
13
+ ## El patrón
14
+
15
+ ```ts
16
+ io.of('/sessions').use(authSocket).on('connection', (socket) => {
17
+ socket.join(`session:${socket.data.sessionId}`);
18
+
19
+ socket.on('message:send', async (dto, ack) => {
20
+ try {
21
+ const saved = await messageService.create(socket.data.userId, dto);
22
+ io.to(`session:${dto.sessionId}`).emit('message:new', saved);
23
+ ack?.({ ok: true, id: saved._id });
24
+ } catch (err) {
25
+ ack?.({ ok: false, error: toClientError(err) });
26
+ }
27
+ });
28
+
29
+ socket.on('disconnect', () => { /* cleanup timers/subscriptions */ });
30
+ });
31
+ ```
32
+
33
+ `authSocket` valida el token en `socket.handshake.auth.token` y rellena `socket.data` (userId/sessionId). Nunca confíes en un id que venga en el payload del evento sin cruzarlo contra `socket.data`.
34
+
35
+ ## Gotchas que muerden
36
+
37
+ - **Rooms, no broadcast global.** `io.emit` manda a todos los conectados; usa `io.to(room)` / `socket.to(room)` para no filtrar datos entre sesiones/tenants.
38
+ - **`socket.emit` vs `io.to(...).emit`.** `socket.emit` responde solo al emisor; para incluirte y al resto del room usa `io.to(room)`, para excluirte usa `socket.to(room)`.
39
+ - **Listeners colgados.** Toda suscripción/intervalo creado en `connection` se limpia en `disconnect`, o se filtra memoria.
40
+ - **Errores.** Un throw dentro de un handler no llega al cliente: reporta vía el callback `ack` o un evento `error:*`, nunca dejes la promesa sin catch.
41
+ - **Auth en el handshake**, no por evento — rechaza en el middleware `.use()` antes de `connection`.
42
+
43
+ ## Reglas duras
44
+
45
+ 1. El handler delega al service; nada de queries ni lógica de negocio inline.
46
+ 2. Identidad desde `socket.data` (poblado en auth), nunca desde el payload.
47
+ 3. Emite a un room específico; `io.emit` global solo para health/system.
48
+ 4. Cada `on(...)` con efectos secundarios tiene su cleanup en `disconnect`.
49
+ 5. Errores al cliente vía `ack`/evento `error`, con el mismo `ApiError` mapeado que HTTP.
50
+
51
+ ## Antes de declarar listo
52
+
53
+ - Los eventos nuevos validan su input igual que un endpoint HTTP.
54
+ - Ningún `io.emit` global salvo señales de sistema; el resto va por room.
55
+ - Auth resuelta en el middleware del namespace, no dentro de los handlers.
56
+ - `{{qualityGate.fast}}` en verde.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: tanstack-query
3
+ description: Patrones de TanStack Query (React Query) — query keys, mutations, invalidación, staleTime. Aplica al tocar fetching, cache de servidor o mutaciones de datos remotos.
4
+ type: reference
5
+ ---
6
+
7
+ # TanStack Query — convenciones
8
+
9
+ ## Cuándo usar este skill
10
+
11
+ Al leer/escribir datos del servidor: un `useQuery`, un `useMutation`, invalidar cache, o paginar. TanStack Query es la fuente de verdad del **estado de servidor** (fetch + cache + revalidación). No lo uses para estado de cliente puro (eso es useState/Redux), ni dupliques su data en otro store.
12
+
13
+ ## El patrón
14
+
15
+ Query keys estructuradas y centralizadas para invalidar sin strings sueltos:
16
+
17
+ ```ts
18
+ const sessionKeys = {
19
+ all: ['sessions'] as const,
20
+ detail: (id: string) => [...sessionKeys.all, id] as const,
21
+ };
22
+
23
+ const { data, isPending, error } = useQuery({
24
+ queryKey: sessionKeys.detail(id),
25
+ queryFn: () => api.getSession(id),
26
+ staleTime: 30_000,
27
+ enabled: Boolean(id),
28
+ });
29
+
30
+ const mutation = useMutation({
31
+ mutationFn: api.updateSession,
32
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: sessionKeys.all }),
33
+ });
34
+ ```
35
+
36
+ ## Gotchas que muerden
37
+
38
+ - **Query keys como datos, no strings.** Una factory (`sessionKeys`) evita typos y permite invalidar por prefijo (`sessionKeys.all` invalida todos los detalles).
39
+ - **`staleTime` vs `gcTime`.** `staleTime` decide cuándo refetchea; con `0` (default) refetchea agresivo. Súbelo para data estable y evita parpadeos/llamadas extra.
40
+ - **No espejes la data en useState/Redux.** Lee de `data` directo; copiarla a otro estado crea dos verdades que se desincronizan.
41
+ - **`enabled`** para queries dependientes — no dispares con el id aún `undefined`.
42
+ - **Invalidación tras mutar**, no edición manual del cache, salvo update optimista deliberado con rollback en `onError`.
43
+ - **`isPending` vs `isFetching`**: `isPending` es la primera carga sin data; `isFetching` es cualquier fetch en curso (incluye revalidación).
44
+
45
+ ## Reglas duras
46
+
47
+ 1. Estado de servidor vive en Query; no se copia a otro store.
48
+ 2. Query keys desde una factory tipada, nunca arrays literales dispersos.
49
+ 3. Tras una mutation, invalida las keys afectadas.
50
+ 4. `enabled` en queries dependientes; nada de queries con params inválidos.
51
+ 5. `staleTime` explícito cuando la data no cambia cada segundo.
52
+
53
+ ## Antes de declarar listo
54
+
55
+ - Las keys nuevas salen de la factory y se invalidan tras mutar.
56
+ - Ningún dato de query duplicado en useState/Redux.
57
+ - Las queries dependientes usan `enabled`.
58
+ - `{{qualityGate.fast}}` en verde.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Antes de cerrar la sesión:
4
4
 
5
- 1. **Quality gate**: corre `{{qualityGate.full}}` y confirma que pasa (o documenta deuda en `progress/current.md`).
5
+ 1. **Quality gate**: {{qualityGate.full}} confirma que pasa (o documenta deuda en `progress/current.md`).
6
6
  2. **History**: agrega entrada en `progress/history.md` con `## YYYY-MM-DD HH:MM <agente> — <resumen>` + cambios + estado del gate.
7
7
  3. **Vaciar current**: deja `progress/current.md` en estado `idle` o con el siguiente paso explícito.
8
8
  4. **Sin temporales**: borra scratch files, no dejes `console.log`, `debugger`, ni código comentado.
@@ -1,4 +1,4 @@
1
1
  ## Idioma y rol
2
2
 
3
- - Código/JSDoc: inglés. Chat: español MX.
3
+ - Código y comentarios (JSDoc/docstrings): inglés. Chat: español MX.
4
4
  - Rol Tech Lead Senior. Antes de codear: ¿lo más simple? ¿legible en 6 meses? ¿mantiene patrón existente? Simplicidad > cleverness.
@@ -4,5 +4,6 @@ Read-only por default. Antes de mutar datos, esquema o infraestructura (DB, stor
4
4
 
5
5
  - **DB / queries**: por default solo lectura (`SELECT`, `EXPLAIN`, flags tipo `onlyRead`). `INSERT/UPDATE/DELETE/DROP/ALTER/TRUNCATE` requieren que el usuario lo pida de forma explícita.
6
6
  - **Comandos de shell**: inspeccionar es libre (`ls`, `cat`, `git status/diff/log`). Los destructivos (`rm -rf`, `git reset --hard`, force-push, `chmod -R`) los manda el harness a `ask`/`deny` y un hook los bloquea — no intentes evadir esa capa.
7
+ - **Búsqueda de código**: usa las tools nativas `Glob` (archivos por nombre/patrón) y `Grep` (contenido). Son read-only, más rápidas (ripgrep por debajo) y ya saltan `node_modules`/`.git`, así que no piden permiso. Reserva `find`/`grep` por shell para lo que las tools no cubren — búsqueda por metadata del FS (`-size`, `-mtime`, permisos) — y úsalo solo cuando sea críticamente necesario. `find` no está pre-aprobado a propósito: con `-exec`/`-delete` no es read-only puro, así que pedir permiso ahí es la red de seguridad correcta, no un estorbo.
7
8
  - **Si una mutación destructiva es legítima y necesaria**: explica qué hace y por qué, y deja que el usuario la confirme o la corra. Nunca la disfraces con variables, subshells o `--no-verify` para saltarte el gate.
8
9
  - **Datos sensibles**: no vuelques secretos, PII ni dumps completos a logs, chat o archivos del repo.
@@ -0,0 +1,18 @@
1
+ ## Stack — Background worker (jobs + queues)
2
+
3
+ Proceso de fondo en Node/TS cuyo trabajo es **procesar jobs y mensajes**, no servir HTTP. El flujo típico: un scheduler (`agenda` / `bullmq` / `node-cron`) dispara jobs en el tiempo, y/o un consumidor de cola (`amqplib` / `bullmq` / `kafkajs`) reacciona a mensajes. Cada handler hace su trabajo (mandar email, push, recalcular, sincronizar) y reporta éxito/fallo a la infraestructura de jobs.
4
+
5
+ Aunque el repo tenga `express` en deps, **no expone endpoints de negocio** — a lo sumo un `/health` para el orquestador. Si te piden agregar una "ruta", confirma: casi siempre es un job o un consumer nuevo, no un endpoint.
6
+
7
+ Reglas de oro:
8
+ - **Idempotencia**: un job/mensaje puede entregarse más de una vez. Todo handler debe ser seguro de re-ejecutar (claves de deduplicación, upserts, chequear estado antes de actuar).
9
+ - **Graceful shutdown**: en `SIGTERM`/`SIGINT`, deja de tomar trabajo nuevo, espera a que los jobs en vuelo terminen (con timeout) y cierra conexiones (DB, broker) antes de salir. Nunca mates un job a la mitad sin re-encolar.
10
+ - **Errores explícitos**: un fallo se reintenta con backoff o va a una dead-letter; nunca se traga en silencio. El logging va por el `Logger` estructurado, nunca `console.log`.
11
+ - **Nada de `process.env`** fuera del módulo de config.
12
+
13
+ Aplica las skills según la capa que toques:
14
+ - `worker-lifecycle` — bootstrap, graceful shutdown, healthcheck, no servir HTTP de negocio.
15
+ - `job-scheduling` — definir/agendar jobs (agenda/bullmq), idempotencia, reintentos con backoff.
16
+ - `queue-consumers` — consumir mensajes (amqplib/bullmq), `ack`/`nack`, dead-letter, backpressure.
17
+
18
+ El logging y el flujo de tickets/PR los cubre el harness base (agentes `leader`, `implementer`, `reviewer`, `commit-pr-pilot` y las skills core).