navori 0.2.10 → 0.2.11
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/auditor.md +139 -0
- package/dist/assets/core/core-assets/agents/implementer.md +1 -0
- package/dist/assets/core/core-assets/agents/reviewer.md +2 -0
- package/dist/assets/core/core-assets/lib-skills/apollo-client.md +62 -0
- package/dist/assets/core/core-assets/lib-skills/stripe.md +84 -0
- package/dist/assets/core/core-assets/lib-skills/tamagui.md +61 -0
- package/dist/assets/core/core-assets/lib-skills/zustand.md +70 -0
- package/dist/assets/core/core-assets/managed/sdd.md +21 -0
- package/dist/assets/core/core-assets/presets/react-native-expo/managed/stack.md +5 -0
- package/dist/assets/core/core-assets/presets/react-native-expo/skills/expo-runtime.md +46 -0
- package/dist/assets/core/core-assets/presets/react-native-expo/skills/rn-performance.md +50 -0
- package/dist/assets/core/core-assets/presets/react-native-expo.json +28 -0
- package/dist/assets/core/core-assets/skills/spec-bootstrap.md +63 -0
- package/dist/index.js +93 -11
- package/package.json +5 -5
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auditor
|
|
3
|
+
description: Auditoría profunda read-only de código existente. Detecta bugs, problemas de seguridad y performance, violaciones de arquitectura/SOLID, edge cases, duplicación y tests/JSDoc faltantes. Seguridad y performance son ejes obligatorios. Escribe reporte + plan priorizado a disco (y opcionalmente borradores de spec SDD). Nunca edita código de producción. Actívalo cuando el usuario dice "audita X", "auditoría profunda", "deep audit", "encuentra bugs en X", "revisa a fondo X".
|
|
4
|
+
tools: Read, Glob, Grep, Bash, Write, WebFetch, WebSearch
|
|
5
|
+
model: {{models.auditor}}
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Agente Auditor
|
|
9
|
+
|
|
10
|
+
Eres un auditor senior. Tu trabajo es **encontrar problemas reales** en el código y proponer un plan que un humano (o el `leader`) pueda ejecutar. **Nunca editas código de producción**: solo escribes reportes, planes y borradores de spec. La tarea exige razonamiento arquitectural (SOLID, capas, seguridad, performance, edge cases), no es mecánica — configura `models.auditor` a `opus` si tu presupuesto lo permite.
|
|
11
|
+
|
|
12
|
+
## Cuándo activar
|
|
13
|
+
|
|
14
|
+
- El usuario pide auditar un archivo, feature, módulo o el repo completo.
|
|
15
|
+
- Antes de un refactor grande o una migración: mapear deuda y riesgos primero.
|
|
16
|
+
- Revisión de seguridad/performance de un área sensible o crítica del proyecto.
|
|
17
|
+
|
|
18
|
+
## Cuándo NO activar
|
|
19
|
+
|
|
20
|
+
- Revisar un diff acotado antes de mergear → ese es el `reviewer`.
|
|
21
|
+
- Analizar un ticket para descomponerlo → ese es el `ticket-audit`.
|
|
22
|
+
- Bug trivial de 1 archivo conocido → se arregla directo.
|
|
23
|
+
|
|
24
|
+
## Pre-flight
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ls .claude/progress/audit_*.md 2>/dev/null # ¿hay un audit reciente del mismo scope?
|
|
28
|
+
git branch --show-current && git rev-parse --short HEAD
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Si hay un audit reciente del mismo scope y el código no cambió, léelo y actualízalo en vez de re-auditar desde cero.
|
|
32
|
+
|
|
33
|
+
## Protocolo
|
|
34
|
+
|
|
35
|
+
### 1. Arranque
|
|
36
|
+
Lee `CLAUDE.md` (reglas del proyecto + el bloque del orquestador) y la `user-section` de abajo. Fija el scope: **targeted** (1 archivo/feature/módulo) o **full** (todo `src/`).
|
|
37
|
+
|
|
38
|
+
### 2. Recolección de contexto
|
|
39
|
+
Explora **tú mismo** — eres un subagente y no puedes lanzar otros (`Agent` no anida). Para scope amplio: `Glob` la estructura, `Grep` los patrones de riesgo, y lee completos solo los archivos candidatos. No leas artefactos generados/lock/`ui` de librería.
|
|
40
|
+
|
|
41
|
+
### 3. Análisis — clasifica cada hallazgo por severidad
|
|
42
|
+
|
|
43
|
+
Cada hallazgo lleva **causa raíz + `archivo:línea` + fix sugerido**.
|
|
44
|
+
|
|
45
|
+
- **CRÍTICO** — bug real o riesgo de producción: seguridad/auth rota, pérdida/corrupción de datos, crash en happy path.
|
|
46
|
+
- **ALTO** — bug latente o violación seria: edge case sin manejar, invariante rota, contrato incumplido.
|
|
47
|
+
- **MEDIO** — performance, congruencia, tests faltantes en lógica no trivial.
|
|
48
|
+
- **BAJO** — documentación (JSDoc), naming, oportunidades de limpieza.
|
|
49
|
+
|
|
50
|
+
### 3-bis. Ejes obligatorios — Seguridad y Performance
|
|
51
|
+
|
|
52
|
+
Aunque el usuario pida foco "solo X", **siempre** pasas los dos checklists sobre el scope. Si el foco no era seguridad/performance, sus hallazgos van como **NOTA** (causa raíz + 1 línea); si son **CRÍTICOS**, escalan a la sección CRÍTICO igual. El reporte **siempre** incluye las sub-secciones `## Seguridad` y `## Performance`, aunque digan "sin hallazgos en este scope".
|
|
53
|
+
|
|
54
|
+
**Eje SEGURIDAD (genérico — adapta al stack en la user-section):**
|
|
55
|
+
- Secretos hardcoded o en logs: grep `Bearer`, `sk_`, `api_key`, `secret`, `password=`, `.env` committeado.
|
|
56
|
+
- AuthZ/RBAC: check de rol/permiso ausente en el server; guard solo en cliente sin respaldo server-side.
|
|
57
|
+
- Inyección: SQL/NoSQL sin parametrizar, `eval`/`new Function`, `JSON.parse` sin `try`, regex con backtracking (ReDoS).
|
|
58
|
+
- XSS: `dangerouslySetInnerHTML`/`innerHTML` con HTML sin sanitizar.
|
|
59
|
+
- PII/datos sensibles en logs, analytics o breadcrumbs; over-fetch que expone campos que el consumidor no usa.
|
|
60
|
+
- Sesión/tokens: sin `httpOnly`, en `localStorage` o query params; expiración/lockout mal manejados.
|
|
61
|
+
|
|
62
|
+
**Eje PERFORMANCE (genérico):**
|
|
63
|
+
- N+1 o fetch dentro de un loop; falta de paginación; query sin índice.
|
|
64
|
+
- Cómputo caro en render / falta de memoization; re-render por props inestables.
|
|
65
|
+
- Bundle: imports pesados sin code-splitting, barrel imports que arrastran todo.
|
|
66
|
+
- Trabajo síncrono bloqueante; listeners/subscriptions sin cleanup (leaks).
|
|
67
|
+
|
|
68
|
+
En el reporte, cuantifica: `Seguridad: <n CRÍTICOS>/<ALTOS>/<MEDIOS>/<BAJOS>` y lo mismo para Performance.
|
|
69
|
+
|
|
70
|
+
### 4. Antes de proponer extracción de código — regla de 3
|
|
71
|
+
|
|
72
|
+
Es lo que más fácil se hace mal. Aplica el threshold **antes** de recomendar cualquier abstracción:
|
|
73
|
+
- **≥3 ocurrencias** en archivos distintos, misma estructura semántica → proponer extracción compartida.
|
|
74
|
+
- **2 ocurrencias** → marcar "considerar", no prioritario; el humano decide.
|
|
75
|
+
- **1 ocurrencia** → **no** propongas extracción (salvo bloque >80 líneas con responsabilidades mezcladas → extracción **local**).
|
|
76
|
+
|
|
77
|
+
No diseñes para requisitos hipotéticos: si no puedes citar 2 call-sites reales, no propongas la abstracción. Tres líneas repetidas son mejores que una abstracción prematura.
|
|
78
|
+
|
|
79
|
+
### 5. Falsos positivos conocidos
|
|
80
|
+
Antes de marcar algo, contrasta con la tabla de falsos positivos de la `user-section` (patrones que en este repo son correctos por decisión de diseño). Un caso ambiguo nuevo **no se inventa**: va a "Gaps / verificaciones pendientes" para que el humano decida.
|
|
81
|
+
|
|
82
|
+
### 6. No marques bugs de librería sin verificar
|
|
83
|
+
Si el hallazgo depende del comportamiento de una dependencia, **verifica su doc con `WebFetch`/`WebSearch`** antes de reportarlo. "Creo que esta API hace X" sin fuente = hipótesis, no hallazgo.
|
|
84
|
+
|
|
85
|
+
## Outputs (escribes a disco, no devuelves en el chat)
|
|
86
|
+
|
|
87
|
+
1. **Reporte** — `.claude/progress/audit_<scope>.md`:
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
# Auditoría — <scope> — <fecha> — commit <short-sha>
|
|
91
|
+
|
|
92
|
+
## Resumen ejecutivo
|
|
93
|
+
- CRÍTICOS: <n> · ALTOS: <n> · MEDIOS: <n> · BAJOS: <n>
|
|
94
|
+
- Seguridad (eje): <n>/<n>/<n>/<n> · Performance (eje): <n>/<n>/<n>/<n>
|
|
95
|
+
|
|
96
|
+
## Seguridad
|
|
97
|
+
## Performance
|
|
98
|
+
## CRÍTICOS
|
|
99
|
+
### C1 — <título> — `archivo:línea`
|
|
100
|
+
- Causa raíz: … · Fix sugerido: … · Severidad: CRÍTICO
|
|
101
|
+
## ALTOS / MEDIOS / BAJOS
|
|
102
|
+
## Oportunidades de extracción (con justificación del threshold § 4)
|
|
103
|
+
## Tests / JSDoc faltantes
|
|
104
|
+
## Gaps / verificaciones pendientes (humano decide)
|
|
105
|
+
## Cobertura — archivos leídos, grep-eados, regiones NO auditadas
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
2. **Plan priorizado** — `.claude/progress/plan_<scope>.md`: bloqueantes (CRÍTICOS) → quick wins (ALTO/MEDIO de bajo esfuerzo) → features SDD → cleanup (BAJOS). Cada item con severidad, archivos a tocar, esfuerzo y hallazgo de origen.
|
|
109
|
+
|
|
110
|
+
3. **Borradores SDD (opcional)** — para hallazgos CRÍTICO/ALTO que sean SDD-scope (ver bloque **Spec Driven Development** en `CLAUDE.md`), escribe `{{sdd.specsDir}}/<feature>/{requirements,tasks}.md.draft`. El `leader` los refina y les quita el `.draft`.
|
|
111
|
+
|
|
112
|
+
## Reglas duras
|
|
113
|
+
|
|
114
|
+
- ❌ Nunca editas código de producción. Solo reportes/planes/drafts.
|
|
115
|
+
- ❌ Sin `archivo:línea` no es un hallazgo, es una hipótesis — márcala como tal.
|
|
116
|
+
- ❌ No marques un bug de librería sin verificar su doc.
|
|
117
|
+
- ✅ Los dos ejes (seguridad + performance) se pasan siempre, aunque el foco fuera otro.
|
|
118
|
+
- ✅ Sé concreto y accionable: cada hallazgo con causa raíz y fix.
|
|
119
|
+
|
|
120
|
+
## Comunicación con el líder
|
|
121
|
+
|
|
122
|
+
Una línea:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
done -> .claude/progress/audit_<scope>.md (+ plan_<scope>.md)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
El leader (o el humano) lee el reporte y el plan del disco y ejecuta desde ahí.
|
|
129
|
+
|
|
130
|
+
<!-- navori:user-section -->
|
|
131
|
+
## Reglas del proyecto
|
|
132
|
+
|
|
133
|
+
<!-- user: agrega aquí lo específico de tu stack. Sugerencias:
|
|
134
|
+
- Checklist de seguridad del stack (ej. RBAC server-side, CORS, contratos de auth compartidos).
|
|
135
|
+
- Checklist de performance del stack (ej. N+1 del ORM, memoization de tablas, RSC vs client).
|
|
136
|
+
- Áreas críticas que casi siempre requieren audit: {{project.criticalAreas}}.
|
|
137
|
+
- Tabla de FALSOS POSITIVOS conocidos: patrón | ¿falso positivo? | por qué (evita re-reportar decisiones de diseño).
|
|
138
|
+
- Regiones a NO auditar: generados, lock, componentes de librería.
|
|
139
|
+
-->
|
|
@@ -45,6 +45,7 @@ Ejecutas **una sola** tarea desde inicio hasta verificación. No orquestas, no l
|
|
|
45
45
|
- **Sin `console.log`** en código que se va a mergear (guard `import.meta.env.DEV` o equivalente del runtime).
|
|
46
46
|
- **Cero errores nuevos** introducidos por tu código en las herramientas del quality gate (vs. baseline). Si dudas del baseline: `git stash` → re-correr → `git stash pop` → comparar. Devolver con cualquier herramienta en rojo (por tu cambio) es motivo automático de `CHANGES_REQUESTED`.
|
|
47
47
|
- **JSDoc** obligatorio en exports públicos y funciones >15 líneas o con lógica condicional densa.
|
|
48
|
+
- **Trazabilidad SDD** (solo si la feature tiene `{{sdd.specsDir}}/<feature>/tasks.md`, ver bloque SDD en `CLAUDE.md`): cada `R<n>` de tu lote queda cubierto por ≥1 test, y cada test referencia sus requisitos con un comentario `// Covers: R<n>` arriba del caso. Sin trazabilidad completa el `reviewer` rechaza.
|
|
48
49
|
- Si una herramienta falla raro (ej. tsc rompe sin diff aparente), **no improvises workaround**: anota `Estado: BLOCKED` + el motivo en `.claude/progress/impl_<feature>.md` y paras.
|
|
49
50
|
|
|
50
51
|
## Evidence-based completion (gate antes del informe)
|
|
@@ -37,6 +37,7 @@ Eres un revisor estricto. Tu única función es **aprobar o rechazar**. No edita
|
|
|
37
37
|
- ¿Está dentro del scope acordado? (Si tocó archivos fuera del scope del audit/ticket → flag)
|
|
38
38
|
- ¿Falta algo del scope? (Si el ticket pedía A+B y solo hizo A → flag)
|
|
39
39
|
- Si la tarea es bugfix: ¿el `Root cause:` documentado en `impl_<feature>.md` matchea con el fix?
|
|
40
|
+
- **Trazabilidad SDD** (solo si existe `{{sdd.specsDir}}/<feature>/tasks.md`): cada `R<n>` del lote está cubierto por ≥1 test que lo referencia con `// Covers: R<n>`. Un `R<n>` del lote sin test trazable → `SPEC_MISS`.
|
|
40
41
|
- ¿La UI fue validada manualmente (según informe del implementer)? Si NO y el cambio toca pantallas → escalar a humano.
|
|
41
42
|
|
|
42
43
|
**Veredicto parcial:**
|
|
@@ -146,6 +147,7 @@ CHANGES_REQUESTED -> .claude/progress/review_<feature>.md
|
|
|
146
147
|
- ❌ Nunca apruebes si el código nuevo **agrega errores o warnings nuevos** vs baseline.
|
|
147
148
|
- ❌ Nunca apruebes código nuevo con `any` explícito o implícito sin `// any justificado: <razón>` válido.
|
|
148
149
|
- ❌ Nunca apruebes si la UI no fue validada manualmente y el cambio toca pantallas.
|
|
150
|
+
- ❌ En features SDD (con `tasks.md`), nunca apruebes si algún `R<n>` del lote no tiene un test trazable que lo cubra.
|
|
149
151
|
- ❌ Nunca editas el código. Solo señalas qué falla y dónde.
|
|
150
152
|
- ✅ Sé concreto: cita `archivo:línea`. Nada de feedback genérico.
|
|
151
153
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: apollo-client
|
|
3
|
+
description: GraphQL con Apollo Client — hooks, fetchPolicy, normalización de caché y actualización tras mutaciones. Aplica al escribir queries/mutations, configurar la caché o los links.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Apollo Client — el patrón canónico
|
|
8
|
+
|
|
9
|
+
Lecturas declarativas con hooks, caché **normalizada por id**, y la UI se mantiene en sync actualizando la caché tras cada mutación. Los concerns de red/auth viven en los links, no en los componentes.
|
|
10
|
+
|
|
11
|
+
## Cuándo usar este skill
|
|
12
|
+
|
|
13
|
+
Al escribir una query/mutation, configurar `InMemoryCache`/`typePolicies`, o la cadena de `links`.
|
|
14
|
+
|
|
15
|
+
## Hooks y aislamiento
|
|
16
|
+
|
|
17
|
+
`useQuery` (lectura al montar), `useLazyQuery` (bajo demanda, retorna `execute`), `useMutation` (retorna `[mutate, { data, loading, error }]`). Aísla los hooks en una capa (hook + adapter): el componente recibe un **modelo de dominio**, no el shape crudo de GraphQL.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
export function useReport(id: string) {
|
|
21
|
+
const { data, loading, error } = useGetReportQuery({ variables: { id }, fetchPolicy: 'cache-first' });
|
|
22
|
+
return { report: data?.report ? adaptReport(data.report) : null, loading, error };
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## fetchPolicy según el dato
|
|
27
|
+
|
|
28
|
+
- `cache-first` (default) — catálogos/detalles ya traídos por una lista.
|
|
29
|
+
- `cache-and-network` — feeds que cambian seguido (render instantáneo + refresh).
|
|
30
|
+
- `network-only` — sesión/bootstrap, datos críticos.
|
|
31
|
+
- Evita `no-cache` salvo PII estricta que no deba tocar disco.
|
|
32
|
+
|
|
33
|
+
## Normalización de caché
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const cache = new InMemoryCache({ typePolicies: { Report: { keyFields: ['id'] } } });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Con `keyFields`, Apollo identifica entidades por id y deduplica/actualiza solo. Sin normalización, las listas y detalles se desincronizan.
|
|
40
|
+
|
|
41
|
+
## Reglas duras
|
|
42
|
+
|
|
43
|
+
1. **Tras una mutation, actualiza la caché:** `update(cache, { data })` (`cache.modify`/`evict`/`writeQuery`) o `refetchQueries`. Nunca dejes la UI desincronizada.
|
|
44
|
+
2. **`optimisticResponse`** para UI instantánea (resultado temporal con `__typename` + id ficticio); `update` reconcilia al llegar la respuesta real.
|
|
45
|
+
3. **No over-fetch:** pide solo los campos que el componente usa; apóyate en **fragments con colocation** (el fragmento junto al componente que lo consume). Regenera tipos (codegen) tras editar `.graphql`.
|
|
46
|
+
4. **Maneja `loading` y `error` siempre.** Separa error de red (banner genérico, resuelto en un `errorLink`) de error de negocio (`graphQLErrors`, copy según `extensions.code`).
|
|
47
|
+
5. **Paginación** con `fetchMore` + `updateQuery`, o `relayStylePagination`/merge en `typePolicies`.
|
|
48
|
+
6. Red/auth/upload en la cadena de **links** (auth → error → upload), no en cada componente.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const [createReport] = useCreateReportMutation({
|
|
52
|
+
optimisticResponse: { createReport: { __typename: 'Report', id: 'temp', ...fields } },
|
|
53
|
+
update(cache) { cache.evict({ fieldName: 'reports' }); }, // invalida la lista
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Antes de declarar listo
|
|
58
|
+
|
|
59
|
+
- Hooks aislados en capa (hook + adapter); el componente ve el modelo de dominio.
|
|
60
|
+
- Caché normalizada por `keyFields`; mutaciones actualizan/invalidan la caché.
|
|
61
|
+
- `fetchPolicy` elegido por tipo de dato; `loading`/`error` manejados.
|
|
62
|
+
- `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stripe
|
|
3
|
+
description: Integración de pagos con Stripe — selección de API (Checkout/Payment/Setup Intents), claves restringidas, idempotencia y verificación de webhooks. Aplica al crear cobros, checkout, suscripciones o handlers de webhook de Stripe.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Stripe — el patrón canónico
|
|
8
|
+
|
|
9
|
+
Alineado con la skill oficial `stripe-best-practices`. El server manda: la lógica de cobro y las claves `sk_`/`rk_` viven en el backend; el cliente solo toca la publishable key.
|
|
10
|
+
|
|
11
|
+
## Cuándo usar este skill
|
|
12
|
+
|
|
13
|
+
Al crear un cobro, checkout, suscripciones, o un handler de webhook de Stripe.
|
|
14
|
+
|
|
15
|
+
## Qué API usar
|
|
16
|
+
|
|
17
|
+
| Caso | API |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Pago único on-session | **Checkout Session** |
|
|
20
|
+
| Form propio embebido | Checkout Session + **Payment Element** |
|
|
21
|
+
| Guardar método de pago (sin cobrar) | **Setup Intent** |
|
|
22
|
+
| Suscripciones / recurrente | **Billing** + Checkout Session |
|
|
23
|
+
| Marketplace / plataforma | **Accounts v2** (`/v2/core/accounts`) |
|
|
24
|
+
| Impuestos (IVA/VAT/GST) | **Stripe Tax** + Registrations API |
|
|
25
|
+
|
|
26
|
+
**Nunca uses la Charges API** (legacy): si te topas con ella, migra a Checkout Session o Payment Intent — no le agregues features.
|
|
27
|
+
|
|
28
|
+
## Seguridad de claves
|
|
29
|
+
|
|
30
|
+
Tres tipos: **publishable** (`pk_`, cliente), **secret** (`sk_`, server) y **restricted** (`rk_`, server con scope acotado). Prefiere **`rk_` sobre `sk_`**. Las claves van en env vars; la `sk_`/`rk_` **jamás** entra al bundle del cliente.
|
|
31
|
+
|
|
32
|
+
## El patrón (server-side)
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
36
|
+
apiVersion: '2026-06-24.dahlia', // fija la última; no dejes el default de la cuenta
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// idempotencyKey: reintentar la misma request NO crea un segundo cobro.
|
|
40
|
+
const session = await stripe.checkout.sessions.create(
|
|
41
|
+
{
|
|
42
|
+
mode: 'payment',
|
|
43
|
+
line_items: [{ price: priceId, quantity: 1 }],
|
|
44
|
+
success_url: `${base}/ok?session_id={CHECKOUT_SESSION_ID}`,
|
|
45
|
+
cancel_url: `${base}/cancel`,
|
|
46
|
+
// Sin payment_method_types: deja los dynamic payment methods del Dashboard.
|
|
47
|
+
},
|
|
48
|
+
{ idempotencyKey: `checkout:${orderId}` },
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Webhooks
|
|
53
|
+
|
|
54
|
+
El estado real llega por webhook, no por el `success_url`. Verifica **siempre** la firma y hazlo idempotente:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// rawBody = cuerpo crudo, NO el JSON parseado (el body-parser rompe la firma).
|
|
58
|
+
const event = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET!);
|
|
59
|
+
if (await alreadyProcessed(event.id)) return res.sendStatus(200); // el event.id puede repetirse
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Responde `2xx` rápido; el trabajo pesado va a una cola.
|
|
63
|
+
|
|
64
|
+
## Gotchas que muerden
|
|
65
|
+
|
|
66
|
+
- **`payment_method_types` fijo** apaga los dynamic payment methods del Dashboard. Omítelo (única excepción: Terminal → `['card_present']`); para restringir usa `payment_method_configurations` o `excluded_payment_method_types`.
|
|
67
|
+
- **`automatic_tax: { enabled: true }` sin registro fiscal activo** cobra **cero** impuesto pareciendo habilitado — el error más común y silencioso. Verifica el registro antes.
|
|
68
|
+
- **Montos en la unidad mínima y enteros** (centavos: $10.00 → `1000`). Nunca floats.
|
|
69
|
+
|
|
70
|
+
## Reglas duras
|
|
71
|
+
|
|
72
|
+
1. Cobro y claves `sk_`/`rk_` solo en el server (env vars); el cliente solo con `pk_`. Prefiere `rk_` sobre `sk_`.
|
|
73
|
+
2. `apiVersion` fija a la última; nada de default implícito.
|
|
74
|
+
3. Charges API prohibida en código nuevo.
|
|
75
|
+
4. `idempotencyKey` en toda request de creación que mueva dinero.
|
|
76
|
+
5. Webhooks: firma verificada con `rawBody` + handler idempotente por `event.id`.
|
|
77
|
+
6. Sin `payment_method_types` salvo Terminal.
|
|
78
|
+
|
|
79
|
+
## Antes de declarar listo
|
|
80
|
+
|
|
81
|
+
- Ninguna `sk_`/`rk_` quedó en el cliente ni en el repo.
|
|
82
|
+
- Cada cobro lleva `idempotencyKey`; el pago se confirma por webhook (firma + idempotente), no por el redirect.
|
|
83
|
+
- Si hay impuestos: registro fiscal activo verificado.
|
|
84
|
+
- `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tamagui
|
|
3
|
+
description: UI con Tamagui v4 — styled() + variants, tokens de tema y qué mantiene contento al compiler. Aplica al crear componentes con estilo, definir el design system o tocar la config/tema.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Tamagui — el patrón canónico
|
|
8
|
+
|
|
9
|
+
Componentes con `styled()` y **variants**; estilos por **tokens** de tema (`$`), no por valores hardcodeados. El compiler extrae en build lo que sea estático — el trabajo es no romperlo.
|
|
10
|
+
|
|
11
|
+
## Cuándo usar este skill
|
|
12
|
+
|
|
13
|
+
Al crear un componente con estilo, definir tokens/themes del design system, o tocar `createTamagui`.
|
|
14
|
+
|
|
15
|
+
## Config
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { defaultConfig } from '@tamagui/config/v4'
|
|
19
|
+
import { createTamagui, styled, View } from 'tamagui'
|
|
20
|
+
|
|
21
|
+
export const config = createTamagui({
|
|
22
|
+
...defaultConfig,
|
|
23
|
+
settings: { ...defaultConfig.settings, styleCompat: 'react-native' },
|
|
24
|
+
})
|
|
25
|
+
declare module 'tamagui' { interface TamaguiCustomConfig extends typeof config {} }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
El `declare module` da props tipadas y autocompletado en todo el proyecto. Elige **un solo paquete de import** (`tamagui` o `@tamagui/core`), no ambos.
|
|
29
|
+
|
|
30
|
+
## styled() + variants (no condicionales inline)
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const Box = styled(View, {
|
|
34
|
+
variants: {
|
|
35
|
+
tone: { danger: { bg: '$red10' }, ok: { bg: '$green10' } },
|
|
36
|
+
} as const,
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Prefiere variants sobre `bg={isError ? '$red10' : '$green10'}`: los valores runtime rompen el flattening del compiler. Usa `as const` en `variants`.
|
|
41
|
+
|
|
42
|
+
## Reglas duras
|
|
43
|
+
|
|
44
|
+
1. **Tokens en props** (`bg="$blue10"`, `p="$4"`, `color="$color"`), nunca `style={{...}}` con variables ni `StyleSheet` de RN (no resuelven tokens).
|
|
45
|
+
2. **No rompas el compiler:** evita valores runtime (`width={w*0.5}`), funciones inline y spreads no deterministas en props de estilo. Muévelos a variants.
|
|
46
|
+
3. **Themes semánticos** (`success`/`warning`/`error`) vía `createThemes`; colorea por contexto con `<Theme name="...">`, no con hex hardcodeado. Al definir el theme, sin `$`; al consumir, con `$`.
|
|
47
|
+
4. **Orden de props = prioridad:** lo que va después de un `{...spread}` gana; en `variants`, la primera listada gana.
|
|
48
|
+
5. **Animaciones:** driver `react-native-reanimated` en native; anima con `enterStyle`/`exitStyle`, `pressStyle`/`hoverStyle` y `AnimatePresence` para salidas.
|
|
49
|
+
6. **Ramifica plataforma con `Adapt`** (Dialog/Sheet), no con `Platform.OS`.
|
|
50
|
+
7. Al envolver un `styled`, usa `.styleable()` para preservar variantes.
|
|
51
|
+
|
|
52
|
+
## Dev vs prod
|
|
53
|
+
|
|
54
|
+
`disableExtraction: true` en dev (HMR más rápido); extracción completa en prod. Ignora `.tamagui/` en git.
|
|
55
|
+
|
|
56
|
+
## Antes de declarar listo
|
|
57
|
+
|
|
58
|
+
- Estilos por tokens `$`, no hardcodeados ni `style` inline con variables.
|
|
59
|
+
- Lógica condicional de estilo en `variants`, no inline (compiler-friendly).
|
|
60
|
+
- Un solo paquete de import; `declare module` registrado.
|
|
61
|
+
- `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zustand
|
|
3
|
+
description: Estado global con Zustand v5 — selectores para evitar re-renders, acciones en el store, slices. Aplica al crear un store, leer estado en un componente o mover estado compartido fuera de Context.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Zustand — el patrón canónico
|
|
8
|
+
|
|
9
|
+
Un store con estado y acciones juntos; los componentes se suscriben con un **selector**, no al store entero. Así solo re-renderizan cuando cambia lo que leen.
|
|
10
|
+
|
|
11
|
+
## Cuándo usar este skill
|
|
12
|
+
|
|
13
|
+
Al crear un store, leer estado en un componente, o mover estado compartido/mutable frecuente fuera de Context (que re-renderiza todo el árbol del Provider).
|
|
14
|
+
|
|
15
|
+
## El patrón
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { create } from "zustand";
|
|
19
|
+
import { useShallow } from "zustand/react/shallow";
|
|
20
|
+
|
|
21
|
+
const useUserStore = create<UserStore>((set) => ({
|
|
22
|
+
user: null,
|
|
23
|
+
loading: false,
|
|
24
|
+
fetchUser: async (id) => {
|
|
25
|
+
set({ loading: true });
|
|
26
|
+
try {
|
|
27
|
+
const user = await api.getUser(id);
|
|
28
|
+
set({ user, loading: false });
|
|
29
|
+
} catch {
|
|
30
|
+
set({ error: "fetch failed", loading: false });
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const name = useUserStore((s) => s.user?.name); // un campo → sin re-render de más
|
|
36
|
+
const { user, loading } = useUserStore( // varios campos → useShallow
|
|
37
|
+
useShallow((s) => ({ user: s.user, loading: s.loading })),
|
|
38
|
+
);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Reglas duras
|
|
42
|
+
|
|
43
|
+
1. **Siempre un selector.** `const s = useStore()` (sin selector) re-renderiza ante cualquier cambio del store. Selecciona el campo que usas.
|
|
44
|
+
2. **Varios campos → `useShallow`.** Devolver un objeto/array nuevo sin `useShallow` re-renderiza en cada render por identidad nueva.
|
|
45
|
+
3. **Acciones dentro del store**, no en el componente; usa `set((state) => ...)` cuando el update depende del valor actual.
|
|
46
|
+
4. **Estado async con su `loading`/`error` en el store**, no suelto en el componente.
|
|
47
|
+
5. **Nada de un único store global gigante.** Separa por dominio (user, cart, settings) con el **slices pattern**: cada slice es una factory `(set) => ({...})` y el store se compone por spread; el tipo es la intersección.
|
|
48
|
+
6. Fuera de React: `useStore.getState()` / `setState()` / `subscribe()` — no hooks.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const useStore = create<UserSlice & CartSlice>()((...a) => ({
|
|
52
|
+
...createUserSlice(...a),
|
|
53
|
+
...createCartSlice(...a),
|
|
54
|
+
}));
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Middlewares
|
|
58
|
+
|
|
59
|
+
`persist` (hidratar desde storage, con `name`), `immer` (updates anidados mutando "directo"), `devtools` (`{ name }`). Se anidan envolviendo el creador.
|
|
60
|
+
|
|
61
|
+
## Zustand vs Context
|
|
62
|
+
|
|
63
|
+
Context es para inyección **estable** (theme, config, i18n). Para estado compartido que cambia seguido, Zustand — evita el re-render masivo del Provider.
|
|
64
|
+
|
|
65
|
+
## Antes de declarar listo
|
|
66
|
+
|
|
67
|
+
- Cada componente lee con selector; multi-campo con `useShallow`.
|
|
68
|
+
- Acciones y estado async (con loading/error) viven en el store.
|
|
69
|
+
- Stores separados por dominio; nada de un mega-store global.
|
|
70
|
+
- `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## Spec Driven Development (SDD)
|
|
2
|
+
|
|
3
|
+
Para trabajo de alcance real —una feature nueva completa, cambios en auth/seguridad/permisos, adapters o modelos con datos sensibles, o scope > ~2 días— el trabajo arranca de un spec, no de código. Bugfixes de UI, un campo nuevo en un form existente, refactors aislados o ajustes de copy NO usan SDD: se trabajan directo.
|
|
4
|
+
|
|
5
|
+
**Estructura:** `{{sdd.specsDir}}/<feature>/{requirements.md, design.md, tasks.md}`.
|
|
6
|
+
- `requirements.md` — qué debe hacer, en formato EARS (ver abajo), cada requisito con id `R<n>`.
|
|
7
|
+
- `design.md` — cómo: arquitectura, componentes afectados, decisiones y trade-offs.
|
|
8
|
+
- `tasks.md` — ejecución en lotes chicos (1-3 tasks); cada task declara los `R<n>` que cubre.
|
|
9
|
+
|
|
10
|
+
**EARS (Easy Approach to Requirements Syntax)** — siempre `DEBE`/`NO DEBE`, una acción por requisito, id `R<n>`:
|
|
11
|
+
- Ubicuo: `El sistema DEBE <X>.`
|
|
12
|
+
- Evento: `CUANDO <Y>, el sistema DEBE <X>.`
|
|
13
|
+
- Estado: `MIENTRAS <Y>, el sistema DEBE <X>.`
|
|
14
|
+
- Opcional: `DONDE <Y>, el sistema DEBE <X>.`
|
|
15
|
+
- No deseado: `SI <Y> ENTONCES el sistema DEBE <X>.`
|
|
16
|
+
|
|
17
|
+
**Trazabilidad obligatoria (`R<n>` ↔ test):** cada `R<n>` se cubre con ≥1 test, y cada test SDD referencia sus requisitos con un comentario `// Covers: R<n>, R<m>` arriba del caso. Sin trazabilidad completa la feature no está done. Habilita verificación inversa (test → requisito) al refactorizar.
|
|
18
|
+
|
|
19
|
+
**El tracking vive en el spec, no en el harness:** cuando existe `tasks.md`, ese es el tablero. NO uses `TaskCreate` para esas tasks — duplicar el seguimiento en la TaskList produce drift entre el spec y las tasks. Ignorar el reminder de `TaskCreate` en sesiones SDD es lo esperado.
|
|
20
|
+
|
|
21
|
+
**Flujo con agentes:** el `leader` descompone `tasks.md` en lotes; el `implementer` ejecuta un lote y escribe los tests trazables a `R<n>`; el `reviewer` aprueba/rechaza verificando la trazabilidad `R<n>↔test` como gate, no como sugerencia. Cada agente escribe su resultado a disco y devuelve solo la referencia (regla anti-teléfono-descompuesto).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## Stack — React Native + Expo
|
|
2
|
+
|
|
3
|
+
App móvil con Expo (SDK actual) + React Native. Navegación con `expo-router`/`native-stack`; estilos y capas nativas por `expo-*` (no equivalentes bare). El trabajo pesado —listas, animaciones, gestos— corre en el **UI thread**, no en el JS thread.
|
|
4
|
+
|
|
5
|
+
Regla de oro: cada render de una lista o pantalla toca el puente JS↔nativo. Mantén las referencias estables y el trabajo fuera del render. Aplica `rn-performance` para listas/animaciones/re-renders y `expo-runtime` para safe-area, teclado, edge-to-edge y EAS/prebuild.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: expo-runtime
|
|
3
|
+
description: Runtime de Expo — safe areas, teclado, edge-to-edge, prebuild y EAS. Aplica al maquetar pantallas, tocar config nativa (app.config/plugins) o configurar builds.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Expo — runtime nativo
|
|
8
|
+
|
|
9
|
+
La config nativa se declara, no se edita a mano. Las pantallas respetan las zonas seguras del dispositivo, no valores hardcodeados.
|
|
10
|
+
|
|
11
|
+
## Safe areas
|
|
12
|
+
|
|
13
|
+
- **`react-native-safe-area-context`** con `useSafeAreaInsets()`. Nunca hardcodees `paddingTop: 44`. Centraliza en un componente `Screen` en vez de rodar tu propio `SafeAreaView` por pantalla.
|
|
14
|
+
- **Insets asimétricos por plataforma:** en Android respeta `bottom` (barra de gestos), en iOS lo maneja el OS → típico `["top","bottom"]` en Android, `["top"]` en iOS.
|
|
15
|
+
- En un scroll raíz, `contentInsetAdjustmentBehavior="automatic"` deja que iOS maneje los insets nativamente (contenido detrás del status bar).
|
|
16
|
+
|
|
17
|
+
## Teclado
|
|
18
|
+
|
|
19
|
+
- **`behavior` por plataforma:** `KeyboardAvoidingView behavior={isIos ? "padding" : "height"}`.
|
|
20
|
+
- Con **`react-native-keyboard-controller`** (`KeyboardAwareScrollView`), NO lo envuelvas además en `KeyboardAvoidingView` — doble offset. Un solo mecanismo por pantalla.
|
|
21
|
+
|
|
22
|
+
## Edge-to-edge (Android 15+)
|
|
23
|
+
|
|
24
|
+
- `edgeToEdgeEnabled: true` + **`react-native-edge-to-edge`**. Estila las barras con su `SystemBars`, NO con `expo-status-bar` cuando edge-to-edge está activo.
|
|
25
|
+
|
|
26
|
+
## Config nativa: declarar, no editar
|
|
27
|
+
|
|
28
|
+
- **Prebuild** (`ios/` commiteado, `android/` generado) o managed. En cualquier caso **no edites `ios/`/`android/`, `Info.plist` ni `AndroidManifest.xml` a mano** — se regeneran. Cambia vía `app.json`/`app.config.ts` o un config plugin en `plugins/`.
|
|
29
|
+
- Tras tocar `app.config`/plugins, corre `expo prebuild --clean` (o el script del repo).
|
|
30
|
+
- Permisos: `ios.infoPlist` / `android.permissions`, o deja que el plugin del módulo (`expo-image-picker`, `expo-location`) inyecte su purpose string — no lo dupliques.
|
|
31
|
+
- Instala capas nativas con **`expo install`** (pinea la versión del SDK); `expo install --fix` re-pinea. Prefiere módulos `expo-*` sobre equivalentes bare.
|
|
32
|
+
|
|
33
|
+
## EAS y env
|
|
34
|
+
|
|
35
|
+
- Perfiles en `eas.json` (`development`, `preview`, `production`). Las env vars requeridas por builds nativos van en cada perfil.
|
|
36
|
+
- **Runtime:** `process.env.EXPO_PUBLIC_*` para lo público; secrets (`google-services.json`, APNs `.p8`) por **EAS secrets**, nunca commiteados.
|
|
37
|
+
|
|
38
|
+
## Gotcha de build
|
|
39
|
+
|
|
40
|
+
- **Reanimated/Worklets:** `react-native-worklets/plugin` debe ser el **último** plugin de `babel.config.js`.
|
|
41
|
+
|
|
42
|
+
## Antes de declarar listo
|
|
43
|
+
|
|
44
|
+
- Insets vía `useSafeAreaInsets`, no valores fijos; un solo mecanismo de teclado por pantalla.
|
|
45
|
+
- Cambios nativos vía `app.config`/plugin (no en `ios/`/`android/`); `expo prebuild --clean` corrido si tocaste config.
|
|
46
|
+
- Ningún secret commiteado. `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rn-performance
|
|
3
|
+
description: Performance de React Native — listas virtualizadas, re-renders, animaciones en UI thread. Aplica al crear listas, animaciones/gestos, o al optimizar pantallas que se sienten lentas.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React Native — performance
|
|
8
|
+
|
|
9
|
+
El costo real vive en el puente JS↔nativo y en el JS thread. La meta: menos renders, referencias estables, y el trabajo pesado en el UI thread.
|
|
10
|
+
|
|
11
|
+
## Listas (lo más crítico)
|
|
12
|
+
|
|
13
|
+
- **Virtualiza siempre.** Nunca `ScrollView` + `.map()` para datos: monta todo. Usa `FlashList`/`FlatList` con `keyExtractor` y `estimatedItemSize`/`getItemLayout`. Con layouts heterogéneos, `getItemType` para pools de reciclaje separados.
|
|
14
|
+
- **`data` estable.** Nada de `.map()`/`.filter()` sobre `data` en cada render: crea referencias nuevas y re-renderiza toda la lista visible en cada keystroke. Pasa el array estable y transforma dentro del ítem.
|
|
15
|
+
- **`renderItem` sin inline.** `item={{...}}` o `style={{...}}` rompen el `memo()`. Pasa primitivos o estilos hoisteados a módulo. Hoistea también los callbacks (una instancia que reciba el `id`), no uno nuevo por ítem.
|
|
16
|
+
- **Ítem ligero y memoizado.** Sin `useQuery` ni cómputo caro dentro; fetch en el padre. `memo()` + solo los campos que usa (`name`, no el objeto entero).
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
const renderItem = ({ item }: { item: Row }) => <RowItem row={item} />;
|
|
20
|
+
<FlashList data={rows} renderItem={renderItem} keyExtractor={(r) => r.id}
|
|
21
|
+
getItemType={(r) => r.type} estimatedItemSize={80} />
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Re-renders
|
|
25
|
+
|
|
26
|
+
- **Minimiza estado, deriva el resto** en render (no `useState`+`useEffect`).
|
|
27
|
+
- **Selectores de store** (`useStore(s => s.has(id))`) sobre `useContext` (Context re-renderiza ante cualquier cambio).
|
|
28
|
+
- Con **React Compiler** ON, `memo`/`useCallback` manuales sobran — pero la estabilidad de referencias de objetos sigue importando.
|
|
29
|
+
|
|
30
|
+
## Animaciones y gestos
|
|
31
|
+
|
|
32
|
+
- **Anima solo `transform` y `opacity`** (GPU). Nunca `width/height/top/margin`: recalculan layout por frame. Colapsar = `scaleY`, no `height`.
|
|
33
|
+
- **Gestos en UI thread** con Reanimated worklets (`useSharedValue`/`useAnimatedStyle`, `GestureDetector`), no `onPressIn/onPressOut` con round-trip al JS thread. `runOnJS` para saltar a JS.
|
|
34
|
+
- **Scroll con `useAnimatedScrollHandler`** + shared value, jamás en `useState` (render thrashing).
|
|
35
|
+
|
|
36
|
+
## Imágenes y misc
|
|
37
|
+
|
|
38
|
+
- **`expo-image`** para todo (caché memoria/disco, `contentFit`, `recyclingKey`). En listas, pide al CDN el tamaño real (`?w=200` a 2x), no full-res para un thumbnail.
|
|
39
|
+
- Hoistea formatters `Intl` a nivel de módulo. Difiere trabajo pesado con `InteractionManager.runAfterInteractions`.
|
|
40
|
+
|
|
41
|
+
## Evita crashes de render
|
|
42
|
+
|
|
43
|
+
- **Nunca `{value && <C/>}` con falsy** (`""`/`0` crashea en release): usa `!!value &&` o ternario `? : null`.
|
|
44
|
+
- Todo string va dentro de `<Text>`.
|
|
45
|
+
|
|
46
|
+
## Antes de declarar listo
|
|
47
|
+
|
|
48
|
+
- Listas virtualizadas con `keyExtractor` + `estimatedItemSize`; `renderItem` y sus props estables.
|
|
49
|
+
- Animaciones/gestos en UI thread; nada de layout animado por frame.
|
|
50
|
+
- `{cond && …}` sin falsy crudo. `{{qualityGate.fast}}` en verde.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://navori.dev/schema/navori.preset.v1.json",
|
|
3
|
+
"id": "react-native-expo",
|
|
4
|
+
"displayName": "React Native + Expo app",
|
|
5
|
+
"extends": "core",
|
|
6
|
+
"extras": {
|
|
7
|
+
"managed": [
|
|
8
|
+
{
|
|
9
|
+
"id": "stack-react-native-expo",
|
|
10
|
+
"relPath": "presets/react-native-expo/managed/stack.md"
|
|
11
|
+
}
|
|
12
|
+
],
|
|
13
|
+
"agents": [],
|
|
14
|
+
"skills": [
|
|
15
|
+
{
|
|
16
|
+
"id": "rn-performance",
|
|
17
|
+
"relPath": "presets/react-native-expo/skills/rn-performance.md",
|
|
18
|
+
"destRelPath": ".claude/skills/rn-performance.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "expo-runtime",
|
|
22
|
+
"relPath": "presets/react-native-expo/skills/expo-runtime.md",
|
|
23
|
+
"destRelPath": ".claude/skills/expo-runtime.md"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"hooks": []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-bootstrap
|
|
3
|
+
description: Scaffolda un spec SDD completo (requirements/design/tasks) con EARS y trazabilidad R<n>↔test. Usar al arrancar una feature de alcance real antes de escribir código.
|
|
4
|
+
type: reference
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# spec-bootstrap — arranque de un spec SDD
|
|
8
|
+
|
|
9
|
+
## Cuándo usar este skill
|
|
10
|
+
|
|
11
|
+
Al iniciar trabajo SDD-scope (feature nueva completa, cambios en auth/seguridad/datos sensibles, scope > ~2 días — ver el bloque **Spec Driven Development** en `CLAUDE.md`). No lo uses para bugfixes, ajustes de UI o refactors aislados: esos van directo.
|
|
12
|
+
|
|
13
|
+
Produce `{{sdd.specsDir}}/<feature>/{requirements.md, design.md, tasks.md}` listos para que el `leader` los descomponga. El scaffolding lo hace el agente principal (o el `researcher`), no un subagente que nestee.
|
|
14
|
+
|
|
15
|
+
## Orden
|
|
16
|
+
|
|
17
|
+
1. **requirements.md primero.** Sin requisitos claros no hay diseño. Deriva del ticket/pedido; cada requisito es EARS con id `R<n>`.
|
|
18
|
+
2. **design.md** — cómo cumplir esos `R<n>`: componentes afectados, contratos, decisiones y trade-offs. Referencia los `R<n>` que cada decisión satisface.
|
|
19
|
+
3. **tasks.md** — lotes de 1-3 tasks; cada task lista los `R<n>` que cubre y su(s) test(s).
|
|
20
|
+
|
|
21
|
+
## Plantillas
|
|
22
|
+
|
|
23
|
+
`requirements.md`:
|
|
24
|
+
```md
|
|
25
|
+
# <Feature> — Requirements
|
|
26
|
+
|
|
27
|
+
## Contexto
|
|
28
|
+
<1-2 líneas: qué problema resuelve y para quién.>
|
|
29
|
+
|
|
30
|
+
## Requisitos (EARS)
|
|
31
|
+
- **R1** — El sistema DEBE <acción observable>.
|
|
32
|
+
- **R2** — CUANDO <evento>, el sistema DEBE <acción>.
|
|
33
|
+
- **R3** — SI <condición no deseada> ENTONCES el sistema DEBE <acción de contención>.
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`design.md`:
|
|
37
|
+
```md
|
|
38
|
+
# <Feature> — Design
|
|
39
|
+
|
|
40
|
+
## Enfoque
|
|
41
|
+
<Arquitectura elegida y por qué. Trade-offs descartados.>
|
|
42
|
+
|
|
43
|
+
## Componentes
|
|
44
|
+
- <archivo/módulo> — <responsabilidad> — cubre R<n>.
|
|
45
|
+
|
|
46
|
+
## Decisiones
|
|
47
|
+
- <decisión no obvia> — <razón>.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`tasks.md`:
|
|
51
|
+
```md
|
|
52
|
+
# <Feature> — Tasks
|
|
53
|
+
|
|
54
|
+
- [ ] **T1** (R1, R2) — <qué se implementa> · test: <archivo>::<caso> con `// Covers: R1, R2`
|
|
55
|
+
- [ ] **T2** (R3) — <qué se implementa> · test: <archivo>::<caso> con `// Covers: R3`
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Reglas duras
|
|
59
|
+
|
|
60
|
+
- **Cero placeholders sin resolver.** No dejes `<...>` en el spec final; si no sabes un dato, es una pregunta al usuario, no un hueco.
|
|
61
|
+
- **Todo `R<n>` termina en ≥1 task y ≥1 test.** Un requisito sin task ni test no es trazable → no entra al spec.
|
|
62
|
+
- **El tracking vive en `tasks.md`, no en `TaskCreate`.** Ver el bloque SDD.
|
|
63
|
+
- **Self-review antes de cerrar el scaffolding:** ¿cada `R<n>` es una sola acción testeable? ¿cada task apunta a `R<n>` reales? ¿el design cubre todos los `R<n>`? Si algo falla, corrígelo antes de pasar el spec al `leader`.
|
package/dist/index.js
CHANGED
|
@@ -149,7 +149,8 @@ var HarnessSchema = z2.object({
|
|
|
149
149
|
researcher: z2.boolean().default(true),
|
|
150
150
|
ticketAudit: z2.boolean().default(true),
|
|
151
151
|
commitPrPilot: z2.boolean().default(true),
|
|
152
|
-
explorer: z2.boolean().default(true)
|
|
152
|
+
explorer: z2.boolean().default(true),
|
|
153
|
+
auditor: z2.boolean().default(true)
|
|
153
154
|
});
|
|
154
155
|
var ModelsSchema = z2.object({
|
|
155
156
|
leader: z2.enum(MODELS).optional(),
|
|
@@ -158,7 +159,8 @@ var ModelsSchema = z2.object({
|
|
|
158
159
|
researcher: z2.enum(MODELS).optional(),
|
|
159
160
|
ticketAudit: z2.enum(MODELS).optional(),
|
|
160
161
|
commitPrPilot: z2.enum(MODELS).optional(),
|
|
161
|
-
explorer: z2.enum(MODELS).optional()
|
|
162
|
+
explorer: z2.enum(MODELS).optional(),
|
|
163
|
+
auditor: z2.enum(MODELS).optional()
|
|
162
164
|
});
|
|
163
165
|
var PluginEntrySchema = z2.object({
|
|
164
166
|
enabled: z2.boolean()
|
|
@@ -170,7 +172,8 @@ var AGENT_ROLES_FOR_SCHEMA = [
|
|
|
170
172
|
"researcher",
|
|
171
173
|
"ticket-audit",
|
|
172
174
|
"commit-pr-pilot",
|
|
173
|
-
"explorer"
|
|
175
|
+
"explorer",
|
|
176
|
+
"auditor"
|
|
174
177
|
];
|
|
175
178
|
var AgentAssignmentsSchema = z2.record(z2.string(), z2.enum(AGENT_ROLES_FOR_SCHEMA));
|
|
176
179
|
var SkillsSchema = z2.object({
|
|
@@ -278,7 +281,14 @@ function effectiveConfig(config) {
|
|
|
278
281
|
return {
|
|
279
282
|
...config,
|
|
280
283
|
prTarget: config.prTarget ?? config.branchBase,
|
|
281
|
-
project: { ...config.project ?? {}, typedLanguage }
|
|
284
|
+
project: { ...config.project ?? {}, typedLanguage },
|
|
285
|
+
sdd: {
|
|
286
|
+
...config.sdd ?? {},
|
|
287
|
+
enabled: config.sdd?.enabled ?? true,
|
|
288
|
+
specsDir: config.sdd?.specsDir ?? "specs",
|
|
289
|
+
applyWhen: config.sdd?.applyWhen ?? [],
|
|
290
|
+
doesNotApplyTo: config.sdd?.doesNotApplyTo ?? []
|
|
291
|
+
}
|
|
282
292
|
};
|
|
283
293
|
}
|
|
284
294
|
var ConfigError = class extends NavoriError {
|
|
@@ -633,7 +643,15 @@ var LIBRARY_SKILLS = [
|
|
|
633
643
|
{ id: "react-hook-form", deps: ["react-hook-form"], label: "React Hook Form" },
|
|
634
644
|
{ id: "mongoose", deps: ["mongoose", "@nestjs/mongoose"], label: "Mongoose ODM" },
|
|
635
645
|
{ id: "zod-validation", deps: ["zod"], label: "Zod validation" },
|
|
636
|
-
{ id: "winston-logging", deps: ["winston"], label: "Winston logging" }
|
|
646
|
+
{ id: "winston-logging", deps: ["winston"], label: "Winston logging" },
|
|
647
|
+
{
|
|
648
|
+
id: "stripe",
|
|
649
|
+
deps: ["stripe", "@stripe/stripe-js", "@stripe/react-stripe-js"],
|
|
650
|
+
label: "Stripe payments"
|
|
651
|
+
},
|
|
652
|
+
{ id: "apollo-client", deps: ["@apollo/client"], label: "Apollo Client" },
|
|
653
|
+
{ id: "zustand", deps: ["zustand"], label: "Zustand" },
|
|
654
|
+
{ id: "tamagui", deps: ["tamagui", "@tamagui/core"], label: "Tamagui" }
|
|
637
655
|
];
|
|
638
656
|
var REMOVED_LIB_SKILLS = ["formik", "joi-validation"];
|
|
639
657
|
var BY_ID = new Map(LIBRARY_SKILLS.map((s) => [s.id, s]));
|
|
@@ -2115,7 +2133,12 @@ var CORE_MANAGED_ASSETS = [
|
|
|
2115
2133
|
{ id: "tipado-fuerte", relPath: "core-assets/managed/tipado-fuerte.md", availableLanguages: ["es"], condition: "project.typedLanguage" },
|
|
2116
2134
|
{ id: "operaciones-seguras", relPath: "core-assets/managed/operaciones-seguras.md", availableLanguages: ["es"], rootOnly: true },
|
|
2117
2135
|
{ id: "arranque-sesion", relPath: "core-assets/managed/arranque-sesion.md", availableLanguages: ["es"], rootOnly: true },
|
|
2118
|
-
{ id: "cierre-sesion", relPath: "core-assets/managed/cierre-sesion.md", availableLanguages: ["es"], rootOnly: true }
|
|
2136
|
+
{ id: "cierre-sesion", relPath: "core-assets/managed/cierre-sesion.md", availableLanguages: ["es"], rootOnly: true },
|
|
2137
|
+
// SDD protocol block. Conditional on `sdd.enabled`, which effectiveConfig
|
|
2138
|
+
// defaults to true (SDD is core to navori's identity) unless a config sets it
|
|
2139
|
+
// to false. Renders the EARS + R<n>↔test convention that SddSchema declared
|
|
2140
|
+
// but nothing emitted before.
|
|
2141
|
+
{ id: "sdd", relPath: "core-assets/managed/sdd.md", availableLanguages: ["es"], rootOnly: true, condition: "sdd.enabled" }
|
|
2119
2142
|
];
|
|
2120
2143
|
var NAVORI_VERSION = readCliVersion();
|
|
2121
2144
|
function resolveAssetPath(asset, language = "es") {
|
|
@@ -2874,10 +2897,11 @@ var CORE_AGENTS = [
|
|
|
2874
2897
|
{ id: "researcher", harnessKey: "researcher" },
|
|
2875
2898
|
{ id: "ticket-audit", harnessKey: "ticketAudit" },
|
|
2876
2899
|
{ id: "commit-pr-pilot", harnessKey: "commitPrPilot" },
|
|
2877
|
-
{ id: "explorer", harnessKey: "explorer" }
|
|
2900
|
+
{ id: "explorer", harnessKey: "explorer" },
|
|
2901
|
+
{ id: "auditor", harnessKey: "auditor" }
|
|
2878
2902
|
];
|
|
2879
2903
|
var CORE_SKILLS = ["verify-before-done", "loop-back-debug", "review-diff"];
|
|
2880
|
-
var WORKFLOW_SKILLS = ["ticket-intake", "pr-create"];
|
|
2904
|
+
var WORKFLOW_SKILLS = ["ticket-intake", "pr-create", "spec-bootstrap"];
|
|
2881
2905
|
var NAVORI_VERSION2 = readCliVersion();
|
|
2882
2906
|
var CORE_META = { source: "@navori/core", version: NAVORI_VERSION2 };
|
|
2883
2907
|
var SKILLS_INDEX_ID = "skills-index";
|
|
@@ -2935,7 +2959,8 @@ var AGENT_WHEN = {
|
|
|
2935
2959
|
researcher: "Responde una pregunta concreta del repo (\xBFpasa Y? \xBFqu\xE9 consume X?) con evidencia citada.",
|
|
2936
2960
|
explorer: "Mapea un \xE1rea o m\xF3dulo amplio: estructura, entry points, dependencias.",
|
|
2937
2961
|
"ticket-audit": "Analiza a fondo un ticket complejo (bug cr\xEDtico, migraci\xF3n, feature multi-capa) antes de descomponer.",
|
|
2938
|
-
"commit-pr-pilot": "Redacta commits Conventional y abre el PR tras la aprobaci\xF3n del reviewer."
|
|
2962
|
+
"commit-pr-pilot": "Redacta commits Conventional y abre el PR tras la aprobaci\xF3n del reviewer.",
|
|
2963
|
+
auditor: "Auditor\xEDa read-only a fondo de c\xF3digo existente (seguridad, performance, SOLID, edge cases). Escribe reporte + plan priorizado a disco."
|
|
2939
2964
|
};
|
|
2940
2965
|
function buildAgentsIndexBody(config) {
|
|
2941
2966
|
const rows = [];
|
|
@@ -5548,6 +5573,42 @@ function hasBinary(name) {
|
|
|
5548
5573
|
// src/lib/health.ts
|
|
5549
5574
|
import { existsSync as existsSync18, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "fs";
|
|
5550
5575
|
import { join as join14 } from "path";
|
|
5576
|
+
|
|
5577
|
+
// src/lib/legacy-agents.ts
|
|
5578
|
+
var LEGACY_AGENT_ALIASES = {
|
|
5579
|
+
"sdd-leader": "leader",
|
|
5580
|
+
"sdd-implementer": "implementer",
|
|
5581
|
+
"sdd-reviewer": "reviewer",
|
|
5582
|
+
"sdd-explorer": "explorer",
|
|
5583
|
+
"sdd-researcher": "researcher",
|
|
5584
|
+
"sdd-ticket-audit": "ticket-audit",
|
|
5585
|
+
"deep-auditor": "auditor"
|
|
5586
|
+
};
|
|
5587
|
+
var CANONICAL_HARNESS_KEY = {
|
|
5588
|
+
leader: "leader",
|
|
5589
|
+
implementer: "implementer",
|
|
5590
|
+
reviewer: "reviewer",
|
|
5591
|
+
researcher: "researcher",
|
|
5592
|
+
"ticket-audit": "ticketAudit",
|
|
5593
|
+
"commit-pr-pilot": "commitPrPilot",
|
|
5594
|
+
explorer: "explorer",
|
|
5595
|
+
auditor: "auditor"
|
|
5596
|
+
};
|
|
5597
|
+
function detectLegacyAgents(agentFiles, config) {
|
|
5598
|
+
const harness = config.harness;
|
|
5599
|
+
const out = [];
|
|
5600
|
+
for (const file of agentFiles) {
|
|
5601
|
+
const legacyName = file.replace(/\.md$/, "");
|
|
5602
|
+
const canonical = LEGACY_AGENT_ALIASES[legacyName];
|
|
5603
|
+
if (!canonical) continue;
|
|
5604
|
+
const key = CANONICAL_HARNESS_KEY[canonical];
|
|
5605
|
+
const enabled2 = !harness || !key || harness[key] !== false;
|
|
5606
|
+
if (enabled2) out.push({ legacyName, canonical });
|
|
5607
|
+
}
|
|
5608
|
+
return out;
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
// src/lib/health.ts
|
|
5551
5612
|
function listMarkers(filePath) {
|
|
5552
5613
|
if (!existsSync18(filePath)) return [];
|
|
5553
5614
|
const content = readFileSync14(filePath, "utf-8");
|
|
@@ -5708,6 +5769,9 @@ function scanMalformedMarkers(cwd) {
|
|
|
5708
5769
|
}
|
|
5709
5770
|
return out;
|
|
5710
5771
|
}
|
|
5772
|
+
function scanLegacyAgents(cwd, config) {
|
|
5773
|
+
return detectLegacyAgents(detectClaudeInfra(cwd).agentFiles, config);
|
|
5774
|
+
}
|
|
5711
5775
|
function suggestNextSteps(state) {
|
|
5712
5776
|
const steps = [];
|
|
5713
5777
|
if (!state.claudeMdExists) {
|
|
@@ -5734,6 +5798,12 @@ function suggestNextSteps(state) {
|
|
|
5734
5798
|
`Mueve el texto que tienes entre bloques managed de CLAUDE.md arriba del primer bloque o abajo del \xFAltimo${lead}; luego corre 'navori render --apply' para reordenarlos.`
|
|
5735
5799
|
);
|
|
5736
5800
|
}
|
|
5801
|
+
if (state.legacyAgents && state.legacyAgents.length > 0) {
|
|
5802
|
+
const names = state.legacyAgents.map((l) => l.legacyName).join(", ");
|
|
5803
|
+
steps.push(
|
|
5804
|
+
`Archiva o borra ${state.legacyAgents.length} agente(s) legacy (${names}); navori ya provee sus equivalentes can\xF3nicos.`
|
|
5805
|
+
);
|
|
5806
|
+
}
|
|
5737
5807
|
if (steps.length === 0) {
|
|
5738
5808
|
steps.push("Todo al d\xEDa \u2014 sin acciones pendientes.");
|
|
5739
5809
|
}
|
|
@@ -5804,6 +5874,7 @@ var doctorCommand = defineCommand3({
|
|
|
5804
5874
|
const missingExternalTools = scanMissingExternalTools(config);
|
|
5805
5875
|
const monorepoDrift = scanMonorepoDrift(cwd, config);
|
|
5806
5876
|
const workspaceLink = scanWorkspaceLink(cwd, config);
|
|
5877
|
+
const legacyAgents = scanLegacyAgents(cwd, config);
|
|
5807
5878
|
const resolvedPreset = config.preset !== "custom" ? resolvePreset(config.preset, cwd) : null;
|
|
5808
5879
|
const missingPreset = config.preset !== "custom" && resolvedPreset === null ? config.preset : null;
|
|
5809
5880
|
const presetOverride = resolvedPreset?.source === "local" && presetExists(config.preset) ? config.preset : null;
|
|
@@ -5836,7 +5907,8 @@ var doctorCommand = defineCommand3({
|
|
|
5836
5907
|
missingPreset,
|
|
5837
5908
|
presetOverride,
|
|
5838
5909
|
missingPresetFiles,
|
|
5839
|
-
placeholderName
|
|
5910
|
+
placeholderName,
|
|
5911
|
+
legacyAgents
|
|
5840
5912
|
};
|
|
5841
5913
|
if (args.json) {
|
|
5842
5914
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -5960,6 +6032,15 @@ ${lines.join("\n")}`
|
|
|
5960
6032
|
);
|
|
5961
6033
|
p4.log.warn(
|
|
5962
6034
|
`Markers managed malformados (${malformedMarkers.length}) \u2014 a esta(s) l\xEDnea(s) les falta el cierre '-->', as\xED que navori ya no las reconoce; el pr\xF3ximo render appendear\xEDa un bloque duplicado y dejar\xEDa la l\xEDnea rota. Restaura el '-->' (o borra la l\xEDnea) a mano:
|
|
6035
|
+
${lines.join("\n")}`
|
|
6036
|
+
);
|
|
6037
|
+
}
|
|
6038
|
+
if (legacyAgents.length > 0) {
|
|
6039
|
+
const lines = legacyAgents.map(
|
|
6040
|
+
(l) => ` ${color.yellow(sym.update)} ${accent(`.claude/agents/${l.legacyName}.md`)} ${dim(`\u2192 superado por '${l.canonical}'`)}`
|
|
6041
|
+
);
|
|
6042
|
+
p4.log.warn(
|
|
6043
|
+
`Agentes legacy (${legacyAgents.length}) \u2014 de un harness previo; navori ya provee sus equivalentes can\xF3nicos. No los toco (son tuyos), pero conviene archivarlos o borrarlos para no quedar con dos rosters en paralelo:
|
|
5963
6044
|
${lines.join("\n")}`
|
|
5964
6045
|
);
|
|
5965
6046
|
}
|
|
@@ -6015,7 +6096,8 @@ ${lines.join("\n")}`);
|
|
|
6015
6096
|
claudeMdExists: report.checks.claudeMdExists,
|
|
6016
6097
|
missingPlugins,
|
|
6017
6098
|
drifts,
|
|
6018
|
-
orderReport
|
|
6099
|
+
orderReport,
|
|
6100
|
+
legacyAgents
|
|
6019
6101
|
});
|
|
6020
6102
|
p4.note(
|
|
6021
6103
|
nextSteps.map((s) => ` ${color.cyan(sym.bullet)} ${s}`).join("\n"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "navori",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Multi-agent harness + SDD scaffolder for Claude Code and other AI engines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"features": {
|
|
23
23
|
"plugins": 6,
|
|
24
|
-
"presets":
|
|
25
|
-
"coreAgents":
|
|
26
|
-
"coreSkills":
|
|
27
|
-
"librarySkills":
|
|
24
|
+
"presets": 10,
|
|
25
|
+
"coreAgents": 8,
|
|
26
|
+
"coreSkills": 6,
|
|
27
|
+
"librarySkills": 11
|
|
28
28
|
},
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"scripts": {
|