diff --git a/CHANGELOG.md b/CHANGELOG.md index 5080d84..673279e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ Formato basado en [Keep a Changelog](https://keepachangelog.com/) y [SemVer](htt ## [No publicado] +## [3.1.0] - 2026-10-26 + +### Added +- **Del diseño al código: `/project-context /design.md`.** Si diseñaste el sistema con [system-design-kit](https://github.com/lacasoft/system-design-kit), `project-context` traslada el diseño al proyecto sin reescribirlo: visión y "fuera de alcance (v1)", atributos de calidad con sus umbrales, modelo de dominio y contratos, arquitectura con las fallas previstas (y sus tests de regresión), supuestos pendientes, y **un ADR por decisión** con la plantilla de la skill `adr`. Lo derivable del repo (stack real, comandos, estructura) sigue saliendo del repo. Anota el diseño de origen y su fecha, así que al reimportar solo actualiza lo que vino de ahí; un ADR aceptado se reemplaza, no se edita. Mapeo completo en `skills/project-context/desde-design.md`. Probado en una sesión real: 3 ADRs desde las decisiones y 9 pendientes señalados, uno de ellos un hueco del propio diseño (validación de `long_url` frente a SSRF). +- **Regla §6.1.7: sin costo ni ataduras.** Nada de pago, que exija API key o cuenta de un tercero, o con licencia propietaria o modificada. Está en la baseline y en la checklist que el hook `pre-bash` muestra al pedir confirmación de una instalación. + +### Changed +- **Se retiran las recomendaciones que no cumplen §6.1.7** y pasan a `components.json` → `discarded` con su motivo: los plugins `security-guidance` (una llamada pagada a Opus por turno y por commit) y `claude-security` (licencia propietaria); el LSP de PHP `php-lsp`, que depende de Intelephense (licencia propietaria); y `react-doctor` (MIT modificada con restricciones, y además sugerido como `@latest`). En los overlays de React y React Native lo sustituyen `eslint-plugin-react-hooks` y los profilers de React DevTools y React Native DevTools. + +### Fixed +- **El agente no podía leer `.env.example`.** `settings.json` denegaba `Read(./.env.*)`, y ese patrón atrapa también los ejemplos, que son justo la documentación versionada de las variables (y `project-context` la necesita). Las reglas de permisos no admiten excepciones, así que ahora hay denies explícitos para los `.env` reales comunes y un hook `pre-read` que bloquea cualquier `.env*` y `.envrc`, en cualquier carpeta, salvo `.example`, `.sample` y `.template`. `--update` retira el `Read(./.env.*)` viejo de los proyectos y lo informa, igual que `Bash(npx react-doctor*)`. +- **La baseline indicaba un `.gitignore` que no funciona.** `/docs` con `!/docs/adr` no reincluye los ADRs: git no puede reincluir nada dentro de un directorio excluido. Ahora indica `/docs/*` con `!/docs/adr/`. +- 12 tests nuevos (112 en total). + ## [3.0.2] - 2026-10-18 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bad64e5..febb6f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,7 +85,7 @@ Borra el archivo de `shared/.claude/` sin más: en `--update`, el instalador ret - SemVer en `package.json` + entrada en `CHANGELOG.md`. - Como se distribuye por `npx github:`, los proyectos toman la rama por defecto: **`master` siempre debe estar verde** (CI en cada PR). Usa ramas `feat/…`, `fix/…` y PRs. -- Para fijar versiones en un proyecto concreto: `npx github:lacasoft/dev-starter-kit#v3.0.2`. +- Para fijar versiones en un proyecto concreto: `npx github:lacasoft/dev-starter-kit#v3.1.0`. ## Probar localmente diff --git a/README.md b/README.md index 63a3268..088ae29 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Dentro de **cualquier proyecto** (nuevo o heredado), sin clonar nada: ```bash npx -y github:lacasoft/dev-starter-kit # interactivo npx -y github:lacasoft/dev-starter-kit --yes --all # desatendido (todo) -npx -y github:lacasoft/dev-starter-kit#v3.0.2 # fijar una versión (recomendado) +npx -y github:lacasoft/dev-starter-kit#v3.1.0 # fijar una versión (recomendado) ``` Toma siempre la última versión de `master` (o el tag indicado). Repo **público (MIT)**. @@ -146,6 +146,29 @@ comandos de build/test/lint antes de documentarlos, rellena lo derivable y te pr tanda lo que no se puede deducir. Mientras siga siendo la plantilla, cada sesión arranca con un aviso que lo sugiere. +### Desde un diseño (system-design-kit) + +Si diseñaste el sistema con [system-design-kit](https://github.com/lacasoft/system-design-kit), +no reescribas nada: en el proyecto de código, pasa la ruta del diseño. + +```bash +claude --add-dir ../acortador-urls # para que pueda leer la carpeta del diseño +> /project-context ../acortador-urls/design.md +``` + +| Del diseño | Al proyecto | +| --- | --- | +| Requisitos y fuera de alcance | *Visión del producto* (con "Fuera de alcance (v1)") | +| Requisitos no funcionales y escala | *Atributos de calidad* con sus umbrales | +| API y modelo de datos | *Modelo / dominio* y contratos en *Arquitectura* | +| Arquitectura inicial y escenarios de falla | *Arquitectura* (con "Fallas previstas") y tests de regresión en *Testing* | +| Decisiones clave y tradeoffs | **Un ADR por decisión** en `docs/adr/`, con la plantilla de la skill `adr` | +| Supuestos sin confirmar | *Pendiente de confirmar* | + +Lo que el repo sí sabe (stack real, comandos, estructura) sigue saliendo del repo. El archivo +anota de qué diseño salió y su fecha, así que si el diseño cambia, volver a correrlo actualiza +solo lo que vino de ahí; un ADR aceptado no se edita, se reemplaza por otro. + ## Memoria y contexto (nativos de Claude Code) | Qué | Dónde | Quién lo escribe | Versionado | @@ -188,6 +211,13 @@ seguridad), scripts `pre/postinstall`, y si algo del repo ya lo cubre. Si un com Como defensa en profundidad, `settings.json` deniega además `sudo`, `publish`, `chmod 777` y la lectura de `~/.ssh` y `~/.aws` por permisos, para el caso de que los hooks estén desactivados. +Los archivos `.env` reales (también `.env.production`, `.env.local`, `.envrc`, en cualquier carpeta) +no se leen: lo impide el hook `pre-read`. Los ejemplos (`.env.example`, `.env.sample`, +`.env.template`) sí, porque documentan las variables sin valores. + +**Regla para cualquier externo** (§6.1.7): nada de pago, que exija API key o cuenta de un tercero, +o con licencia propietaria o modificada. + ## Estándares de seguridad | Estándar | Edición | Dónde se aplica | @@ -225,27 +255,26 @@ cubre lo que usábamos. Si un proyecto lo tenía: **Integrados por stack** (vía `claude-code-templates`, solo los marcados `integrar`): - shared: `code-reviewer`, skills `clean-code`, `senior-security`; marketplace oficial de plugins. -- seguridad (plugins oficiales, **opcionales**, revisados archivo por archivo; el instalador los - lista con su condición): - - `security-guidance`: revisión automática del diff en cada turno y revisión agéntica en cada - commit. Cuesta una llamada a Opus por revisión (`ENABLE_STOP_REVIEW=0` deja solo la de commit) e - instala `claude-agent-sdk` sin versión fijada en `~/.claude/security` al iniciar sesión. - - `claude-security`: escaneo profundo bajo demanda con verificación independiente y SARIF. Pesado - en tokens; úsalo antes de un release grande. Licencia propietaria (se usa, no se destila). - code intelligence (plugins oficiales LSP, se indican por stack): `typescript-lsp` (Nest, Express, - Angular, React, Next, React Native), `pyright-lsp` (FastAPI, Django), `php-lsp`, `jdtls-lsp` - (Spring), `csharp-lsp` (.NET). Sin plugin oficial para Dart ni Solidity. + Angular, React, Next, React Native), `pyright-lsp` (FastAPI, Django), `jdtls-lsp` + (Spring), `csharp-lsp` (.NET). Sin plugin oficial libre para PHP (`php-lsp` usa Intelephense, + de licencia propietaria), Dart ni Solidity. - backend: `database-architect`, `test-engineer`; skill `docker-expert`. - frontend/mobile: `frontend-developer`, `ui-ux-designer`; skills `frontend-design`, `ui-design-system`. -- frontend/react + mobile/react-native: dep npm de animación (`motion` / `react-native-reanimated`) - + `react-doctor` (auditoría on-demand, no se cablea como hook bloqueante). +- frontend/react + mobile/react-native: dep npm de animación (`motion` / `react-native-reanimated`). **Opcionales** (en `components.json` pero *no* se instalan solos; añádelos a mano si los quieres): `architect-review` (backend) y `senior-frontend` (frontend). -**Descartados** (extracto; los 13 con su motivo están en `components.json` → `discarded`): +**Regla para todo externo** (baseline §6.1.7): nada de pago, que exija API key o cuenta de un tercero, +o con licencia propietaria o comercial. + +**Descartados** (extracto; los 17 con su motivo están en `components.json` → `discarded`): - `ruvnet/claude-flow` (Ruflo) — retirado en la 3.x; ver "Orquestación multiagente". - `claude-mem` — duplica la memoria nativa y registra hooks propios. +- `php-lsp` (Intelephense) y `react-doctor` — licencia propietaria y MIT modificada con restricciones. +- `security-guidance` y `claude-security` — el primero hace llamadas pagadas a Opus en cada turno y + commit; el segundo tiene licencia propietaria. Su lugar lo ocupa la skill `owasp-review`. - `semgrep` (Guardian) — envía cada archivo que escribe el agente a la nube de Semgrep, en todos los proyectos, y se autoactualiza con binarios opacos. Alternativa: `semgrep` CLI local o en CI. - `serena` — arranca con `uvx` desde el último commit de GitHub, sin versión fijada; los plugins LSP @@ -307,7 +336,7 @@ recibir la copia vieja. La solución buena es la misma que exige la baseline §6 versión** — y de paso hace la instalación reproducible: ```bash -npx -y github:lacasoft/dev-starter-kit#v3.0.2 --update --yes # por tag +npx -y github:lacasoft/dev-starter-kit#v3.1.0 --update --yes # por tag npx -y github:lacasoft/dev-starter-kit# --update --yes # por commit (el SHA del tag) ``` @@ -328,4 +357,4 @@ Repo **público (MIT)** distribuido por `npx github:` → la rama **`master` deb (dry-run) y en un monorepo multi-stack, en cada PR. La lista sale de `scripts/stacks.cjs`, que la deriva de `install.js`. - **Cómo añadir** agentes, skills, stacks o externos: ver [CONTRIBUTING.md](CONTRIBUTING.md). - **Versionado**: SemVer en `package.json` + `CHANGELOG.md`, con tag anotado por versión - (`v3.0.2`). Instala siempre pineando (`#v3.0.2`): es reproducible y evita la cache de npx. + (`v3.1.0`). Instala siempre pineando (`#v3.1.0`): es reproducible y evita la cache de npx. diff --git a/components.json b/components.json index d2b99d5..e45d564 100644 --- a/components.json +++ b/components.json @@ -18,8 +18,6 @@ ], "pluginsOptional": [ { "id": "code-review@claude-plugins-official", "why": "review automatizado de PRs" }, - { "id": "security-guidance@claude-plugins-official", "rec": "opcional", "short": "revisión de seguridad automática en cada turno y commit; cuesta tokens (Opus)", "why": "Revisado (v2.0.14, Apache-2.0, Anthropic). Tres capas: avisos por patrones al editar, revisión del diff con un LLM al terminar cada turno y revisión agéntica en cada commit/push que sigue el flujo de datos entre archivos (IDOR, bypass de auth, SSRF entre archivos). Complementa a owasp-review (que es bajo demanda). Condiciones: 1) cada revisión es una llamada a Opus aparte de tu sesión (su README estima ~0.19 USD por revisión del Stop): para gastar menos, ENABLE_STOP_REVIEW=0 deja solo las de commit/push, o SECURITY_REVIEW_MODEL apunta a un modelo más barato; 2) en cada SessionStart instala claude-agent-sdk desde PyPI SIN versión fijada (pip install --upgrade, en un venv en ~/.claude/security), lo que choca con la regla de versión fijada de §6.1; 3) envía el diff y los archivos relacionados al mismo endpoint de modelo que usa Claude Code. Sus hooks viven en el plugin y no tocan nuestro settings.json. Reglas propias del proyecto: .claude/claude-security-guidance.md (versionado; sin secretos, va al prompt)." }, - { "id": "claude-security@claude-plugins-official", "rec": "opcional", "short": "escaneo profundo bajo demanda (/claude-security) con hallazgos verificados y SARIF", "why": "Revisado (v0.12.0, Anthropic, licencia PROPIETARIA: se puede usar, no destilar). Escaneo multiagente bajo demanda del repo o de un diff, con threat model, verificadores independientes que intentan refutar cada hallazgo y salida en Markdown, JSONL y SARIF; puede proponer parches como archivos que tú aplicas. Es el complemento pesado de owasp-review: úsalo antes de un release grande o tras un cambio sensible (scan de cambios a effort high). Sin instalaciones en SessionStart; sus hooks solo muestran un banner, cuentan uso y sugieren escanear tras push/PR (CLAUDE_SECURITY_SCAN_TIP=off lo apaga). Requiere Python 3.9+. Escribe CLAUDE-SECURITY-/ en la raíz del repo, con su propio .gitignore. Consume muchos tokens: pide confirmar el costo antes de cada scan." }, { "id": "context7-plugin@context7-marketplace", "marketplace": "upstash/context7", "rec": "opcional", "why": "MCP remoto (https://mcp.context7.com/mcp) que inyecta docs versionadas de librerías. Sin API key obligatoria (free tier; la key gratis solo sube rate limits). No toca el .mcp.json del proyecto (servidor namespaced 'context7' dentro del plugin). Transversal a todos los stacks. Privacidad: solo viaja el nombre de librería + la pregunta, nunca metas secretos ni código propietario en la query." } ] }, @@ -72,13 +70,12 @@ "backend/express": { "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } }, "backend/fastapi": { "lsp": { "plugin": "pyright-lsp@claude-plugins-official", "binary": "pyright-langserver (npm i -g pyright, o pipx install pyright)" } }, "backend/django": { "lsp": { "plugin": "pyright-lsp@claude-plugins-official", "binary": "pyright-langserver (npm i -g pyright, o pipx install pyright)" } }, - "backend/php": { "lsp": { "plugin": "php-lsp@claude-plugins-official", "binary": "intelephense (npm i -g intelephense)" } }, "backend/spring": { "lsp": { "plugin": "jdtls-lsp@claude-plugins-official", "binary": "jdtls (Eclipse JDT.LS; brew install jdtls o instalación manual)" } }, "backend/dotnet": { "lsp": { "plugin": "csharp-lsp@claude-plugins-official", "binary": "csharp-ls (dotnet tool install --global csharp-ls)" } }, "frontend/angular": { "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } }, - "frontend/react": { "npm": ["motion"], "tools": ["react-doctor"], "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } }, + "frontend/react": { "npm": ["motion"], "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } }, "frontend/nextjs": { "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } }, - "mobile/react-native": { "npm": ["react-native-reanimated"], "tools": ["react-doctor"], "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } } + "mobile/react-native": { "npm": ["react-native-reanimated"], "lsp": { "plugin": "typescript-lsp@claude-plugins-official", "binary": "typescript-language-server (npm i -g typescript-language-server typescript)" } } }, "externalSkillCatalogs": { @@ -132,6 +129,10 @@ "git/git-flow-manager": "Solo si el equipo usa Git Flow (no trunk-based). Dejar a criterio por proyecto.", "web-development/react-best-practices": "Duplica la skill vercel:react-best-practices ya disponible en el harness.", "cct-hooks": "lint-on-save / security-scanner / smart-formatting / tdd-gate: su instalador hace merge shallow por evento y PISA nuestros arrays Pre/PostToolUse. Su intención (format+lint+secret-scan) ya está replicada en .claude/helpers/hook-handler.cjs.", + "php-lsp@claude-plugins-official (Intelephense)": "RETIRADO por la regla §6.1.7: el plugin oficial de PHP depende de Intelephense, cuya licencia es propietaria (EULA personal e intransferible; las funciones premium requieren una clave de pago). Phpactor es libre (MIT) pero no tiene plugin oficial; si se quiere, se configura a mano.", + "react-doctor": "RETIRADO por la regla §6.1.7: licencia 'MIT modificada' con restricciones propias (prohíbe usarlo como entrada de pipelines de IA y venderlo como servicio sin permiso escrito), no es open source estándar. Además el kit lo sugería como @latest. Alternativas libres en los overlays: eslint-plugin-react-hooks y los profilers de React DevTools / React Native DevTools.", + "security-guidance@claude-plugins-official": "Revisado (v2.0.14, Apache-2.0). RETIRADO de los opcionales por la regla §6.1.7 (sin costo): hace una llamada a Opus en cada turno con cambios y otra en cada commit/push, a cargo de tu cuenta, aparte de la sesión. Además instala claude-agent-sdk sin versión fijada en cada SessionStart. Su función la cubren owasp-review (bajo demanda) y el hook de secretos.", + "claude-security@claude-plugins-official": "Revisado (v0.12.0). RETIRADO de los opcionales por la regla §6.1.7: licencia PROPIETARIA de Anthropic (todos los derechos reservados) y escaneos que consumen muchos tokens de tu cuenta. Usa owasp-review.", "semgrep@claude-plugins-official (Semgrep Guardian)": "Revisado (v2.7.0). NO se recomienda por defecto: sus hooks corren en cada Write/Edit/Bash de TODOS los proyectos y envían cada archivo que escribe el agente (y su versión anterior), más lockfiles, a scanner.semgrep.ai; exige cuenta de Semgrep; ejecuta binarios precompilados que se autoactualizan en cada inicio de sesión (con manifiesto firmado, pero opacos para revisión); y el repo del plugin no trae licencia. Si tu organización ya usa Semgrep y acepta ese flujo de datos, es su decisión. La alternativa que propone el kit: semgrep CLI local o en CI (ver ci-cd y owasp-review).", "serena@claude-plugins-official": "Revisado. Plugin comunitario cuyo .mcp.json arranca `uvx --from git+https://github.com/oraios/serena` SIN versión fijada: cada sesión ejecuta el último commit de GitHub, lo contrario de §6.1. Lo que aporta (navegación semántica por LSP) ya lo cubren los plugins LSP oficiales que el kit indica por stack, y su propio sistema de memorias del proyecto duplicaría la memoria nativa. Si se quisiera, habría que fijarlo a un tag a mano.", "framer-motion (como skill)": "Es dependencia npm de proyecto, no componente .claude. Se sugiere instalar en el proyecto, no en .claude." diff --git a/install.js b/install.js index ad6c910..a1b2684 100644 --- a/install.js +++ b/install.js @@ -458,6 +458,10 @@ function stackRules(stacks) { const isKitHook = (g) => Array.isArray(g && g.hooks) && g.hooks.length > 0 && g.hooks.every((h) => /hook-handler\.cjs|auto-memory-hook\.mjs/.test((h && h.command) || "")); const RUFLO = /claude[-_]?flow|ruflo|ruv-swarm|flow-nexus/i; +// Permisos que versiones anteriores del kit escribían y que ya no se usan. La unión de permisos +// los arrastraría para siempre. Read(./.env.*) bloqueaba también .env.example: lo sustituyen +// denies explícitos y el hook pre-read. +const RETIRED_PERMISSIONS = new Set(["Read(./.env.*)", "Bash(npx react-doctor*)"]); const isRuflo = (v) => RUFLO.test(typeof v === "string" ? v : JSON.stringify(v)); function mergeSettings(kitPath, destPath, retired = []) { @@ -502,6 +506,10 @@ function mergeSettings(kitPath, destPath, retired = []) { // allow/deny/ask son listas (se unen); defaultMode y similares son escalares (gana el tuyo). if (Array.isArray(a) || Array.isArray(b)) { perms[k] = [...new Set([...(a || []), ...(b || [])])].filter((p) => { + if (RETIRED_PERMISSIONS.has(p)) { + retired.push(`permiso ${p} (el kit ya no lo usa)`); + return false; + } if (!isRuflo(p)) return true; retired.push(`permiso ${p}`); return false; @@ -569,7 +577,7 @@ function reportLayer(ctx) { if (l.length) console.log(` 📋 ${l.join(" · ")}`); if (ctx.retiredSettings.length) { const dry = FLAGS.dryRun ? "(dry-run) " : ""; - console.log(`\n ${dry}🧹 Retirado de settings.json por apuntar a Ruflo (claude-flow): ${[...new Set(ctx.retiredSettings)].join(" · ")}`); + console.log(`\n ${dry}🧹 Retirado de settings.json (Ruflo o permisos que el kit ya no usa): ${[...new Set(ctx.retiredSettings)].join(" · ")}`); if (!FLAGS.dryRun) console.log(" Si lo necesitabas, está en el backup .claude.backup.* de esta ejecución."); } if (ctx.conflicts.length) { @@ -1234,9 +1242,6 @@ async function main() { run(`npm install ${npmDeps.join(" ")}`); } } - if (stacks.some((st) => manifest.byStackId?.[st.id]?.tools?.includes("react-doctor"))) { - console.log("\n🩺 react-doctor disponible: `npx react-doctor@latest .` para auditar (state/perf/a11y). No se cablea como hook bloqueante."); - } // 5b) tooling de git: husky + lint-staged + commitlint + plantilla de PR (la baseline los exige) await maybeSetupHusky(); diff --git a/package.json b/package.json index c4ae11f..73f38e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lacasoft/dev-starter-kit", - "version": "3.0.2", + "version": "3.1.0", "private": true, "description": "Base .claude coherente (agentes, skills, helpers, orquestación multiagente nativa) instalable en cualquier proyecto lacasoft (backend/frontend/mobile/blockchain).", "bin": { diff --git a/shared/.claude/CLAUDE.base.md b/shared/.claude/CLAUDE.base.md index 7affca7..39f47d2 100644 --- a/shared/.claude/CLAUDE.base.md +++ b/shared/.claude/CLAUDE.base.md @@ -37,7 +37,7 @@ | `/scripts` | utilidades | | `/examples` | ejemplos | -Nunca en la raíz. En `.gitignore`: `/docs` (salvo `/docs/adr`, que SÍ se versiona), `.claude/agent-memory-local/`, `CLAUDE.local.md`, `*.local.*`, `.env*`. `.claude/agent-memory/` **sí** se versiona (es memoria del equipo). Los ADRs son historia del proyecto: deben sobrevivir un clone y verse para todos. +Nunca en la raíz. En `.gitignore`: `/docs/*` con `!/docs/adr/` después (no `/docs` a secas: git no puede reincluir nada dentro de un directorio excluido), `.claude/agent-memory-local/`, `CLAUDE.local.md`, `*.local.*`, `.env*`. `.claude/agent-memory/` **sí** se versiona (es memoria del equipo). Los ADRs son historia del proyecto: deben sobrevivir un clone y verse para todos. ## 3. Coordinación de agentes (nativa de Claude Code) @@ -120,6 +120,7 @@ Antes de aceptar una instalación, verifica y **di en voz alta** el resultado: 4. **Antigüedad**: no adoptes una versión publicada hace menos de 7 días salvo que sea un fix de seguridad; los paquetes comprometidos suelen retirarse en ese plazo. 5. **Qué ejecuta al instalarse**: revisa scripts `postinstall`/`preinstall`; ante un script opaco, no instales. 6. **¿Hace falta?**: si algo en el repo ya lo cubre, no añadas superficie de ataque. +7. **Sin costo ni ataduras**: nada de pago, que exija API key o cuenta de un tercero, o con licencia propietaria o comercial. Si la única opción lo requiere, no lo instales: dilo y propón una alternativa libre. Los **servidores MCP, plugins, skills y agentes de terceros** son código de terceros con tus permisos: misma checklist, versión fijada y solo los permisos que necesiten. diff --git a/shared/.claude/helpers/hook-handler.cjs b/shared/.claude/helpers/hook-handler.cjs index a55b234..e39b7bf 100644 --- a/shared/.claude/helpers/hook-handler.cjs +++ b/shared/.claude/helpers/hook-handler.cjs @@ -1,7 +1,7 @@ #!/usr/bin/env node /** * Entry-point único de hooks. Claude Code pasa el payload del hook como JSON por STDIN. - * Acciones (1ª arg): pre-bash, pre-edit, post-edit, session-start, memory-only. + * Acciones (1ª arg): pre-bash, pre-edit, pre-read, post-edit, session-start, memory-only. * Una acción desconocida (p. ej. de un settings.json antiguo) no hace nada y sale con 0. * * Diseño: solo interviene en casos explícitos. Con exit 2, los destructivos clásicos @@ -189,7 +189,7 @@ function findKitNew(dir, out = [], budget = { n: 500 }) { const CHECKLIST = "Valida antes de aceptar (baseline §6.1): 1) nombre exacto, ¿typosquat? 2) publisher oficial y mantenimiento vivo " + "3) versión pineada, nunca @latest/@alpha 4) publicada hace 7+ días (salvo fix de seguridad) 5) scripts pre/postinstall " + - "6) ¿algo del repo ya lo cubre?"; + "6) ¿algo del repo ya lo cubre? 7) gratis, sin API key ni cuenta de terceros, y con licencia open source (no propietaria ni modificada)"; // Ejecutores que descargan y corren un paquete en un solo paso, como `npx`. const REMOTE_RUNNERS = [ @@ -272,6 +272,20 @@ switch (action) { break; } + case "pre-read": { + // Archivos de entorno reales: nunca se leen. Los ejemplos sí, porque son la documentación + // versionada de las variables (y la skill project-context los necesita). Va en un hook y no + // en permissions.deny porque las reglas de permisos no admiten excepciones: Read(./.env.*) + // bloqueaba también .env.example. + const base = path.basename(toolInput.file_path || ""); + if (/^\.env(rc|\..+)?$/.test(base) && !/\.(example|sample|template)$/.test(base)) { + denyPreTool( + `Lectura bloqueada: ${base} puede contener secretos reales. Usa .env.example para conocer las variables (solo nombres); si necesitas un valor concreto, pídeselo a la persona.` + ); + } + break; + } + case "post-edit": { const file = toolInput.file_path; if (!file || !fs.existsSync(file)) break; diff --git a/shared/.claude/settings.json b/shared/.claude/settings.json index 431624c..ccd296b 100644 --- a/shared/.claude/settings.json +++ b/shared/.claude/settings.json @@ -13,6 +13,12 @@ "hooks": [ { "type": "command", "command": "sh -c 'exec node \"${CLAUDE_PROJECT_DIR:-.}/.claude/helpers/hook-handler.cjs\" pre-edit'", "timeout": 5 } ] + }, + { + "matcher": "Read", + "hooks": [ + { "type": "command", "command": "sh -c 'exec node \"${CLAUDE_PROJECT_DIR:-.}/.claude/helpers/hook-handler.cjs\" pre-read'", "timeout": 5 } + ] } ], "PostToolUse": [ @@ -42,7 +48,6 @@ "Bash(npm test*)", "Bash(npm ci)", "Bash(npm install)", - "Bash(npx react-doctor*)", "Bash(pnpm run *)", "Bash(pnpm test*)", "Bash(pnpm install)", @@ -72,7 +77,12 @@ ], "deny": [ "Read(./.env)", - "Read(./.env.*)", + "Read(./.env.local)", + "Read(./.env.*.local)", + "Read(./.env.development)", + "Read(./.env.production)", + "Read(./.env.staging)", + "Read(./.env.test)", "Read(~/.ssh/**)", "Read(~/.aws/**)", "Bash(sudo *)", diff --git a/shared/.claude/skills/adr/SKILL.md b/shared/.claude/skills/adr/SKILL.md index 4bc37ab..f8d67ab 100644 --- a/shared/.claude/skills/adr/SKILL.md +++ b/shared/.claude/skills/adr/SKILL.md @@ -12,6 +12,8 @@ Eres quien cuida la memoria de decisiones del proyecto. Un ADR captura **por qu - **Sí**: decisiones caras de revertir, que afectan a varios módulos o equipos, que eligen entre alternativas razonables, o que imponen una restricción (seguridad, compliance, costo). - **No**: detalles de implementación locales, preferencias de estilo (eso va a lint o a `CLAUDE.project.md`), o lo que ya decide el framework. +Si las decisiones vienen de un `design.md` del system-design-kit, la skill `project-context` las convierte en ADRs con esta misma plantilla (ver su `desde-design.md`). + ## Procedimiento 1. **Busca antes de escribir**: lista `docs/adr/`. Si ya hay un ADR sobre el tema, el nuevo lo **reemplaza** (y el viejo se marca `Reemplazado por ADR-NNNN`); no edites el contenido de uno aceptado. 2. **Numera**: siguiente número libre con 4 dígitos y título en kebab-case: `docs/adr/0007-usar-outbox-para-eventos.md`. diff --git a/shared/.claude/skills/owasp-review/SKILL.md b/shared/.claude/skills/owasp-review/SKILL.md index 77bdce9..4d15f4e 100644 --- a/shared/.claude/skills/owasp-review/SKILL.md +++ b/shared/.claude/skills/owasp-review/SKILL.md @@ -33,8 +33,6 @@ Si el código llama a un LLM, revisa contra el **OWASP Top 10 for LLM Applicatio ## 6. Herramientas Corre las que el repo **ya tenga** (`npx --no-install semgrep`, `gitleaks`, `osv-scanner`, `npm audit`, `pip-audit`, `trivy`, `bandit`, `gosec`…). Si falta una y la quieres, instalarla pasa por la baseline §6.1: propónla, no la instales por tu cuenta. Una herramienta ausente no tumba la revisión. -Si el plugin `claude-security` está instalado y el cambio es grande o sensible, propón además un escaneo de cambios con él (es más profundo y caro; pide confirmar el costo). Sus hallazgos se integran a tu informe con su CWE; no los copies sin verificarlos. - ## 7. Autocrítica antes de entregar Relee tus hallazgos una vez buscando dos cosas: **falsos positivos** (¿de verdad llega input del atacante a ese sink?) y **huecos** (¿qué categoría o capítulo no miraste y por qué?). Corrige y declara lo que quedó fuera. diff --git a/shared/.claude/skills/project-context/SKILL.md b/shared/.claude/skills/project-context/SKILL.md index 1f182e4..9b94961 100644 --- a/shared/.claude/skills/project-context/SKILL.md +++ b/shared/.claude/skills/project-context/SKILL.md @@ -1,7 +1,7 @@ --- name: project-context -description: Úsalo cuando CLAUDE.project.md falte, siga siendo la plantilla o se haya quedado viejo (cambió el stack, los comandos, la estructura o el despliegue), al llegar a un repo heredado, o cuando el agente se equivoque una y otra vez por desconocer cómo funciona este proyecto. -argument-hint: "[área o paquete a profundizar]" +description: Úsalo cuando CLAUDE.project.md falte, siga siendo la plantilla o se haya quedado viejo (cambió el stack, los comandos, la estructura o el despliegue), al llegar a un repo heredado, cuando el agente se equivoque una y otra vez por desconocer cómo funciona este proyecto, o cuando exista un design.md de System Design (system-design-kit) del que partir para no reescribir requisitos ni decisiones. +argument-hint: "[área o paquete a profundizar | ruta a design.md]" --- # Contexto del proyecto — CLAUDE.project.md a partir del repo real @@ -14,6 +14,9 @@ Eres quien hace el onboarding de un agente en un repo que no conoce. Sabes que e - **Corto**: apunta a menos de 150 líneas. Todo se carga en cada sesión y la adherencia baja con el tamaño. Si un área necesita más detalle, propón una regla con `paths:` en `.claude/rules/` (se carga solo al tocar esos archivos), fuera de `.claude/rules/kit/`, que gestiona el instalador. - **Cero secretos**: nombres de variables de entorno sí; valores nunca. +## 0. Si partes de un diseño (design.md) +Si el argumento es una ruta a un `design.md` del **system-design-kit** (front matter con `language` y marcas ``), ese documento es la fuente de **lo que el repo no puede decir**: visión, requisitos, escala, decisiones y fallas. La persona ya lo pensó y lo decidió; tu trabajo es trasladarlo sin reescribirlo ni reinterpretarlo. Lee [desde-design.md](desde-design.md) y sigue su mapeo. Lo derivable del repo (stack real, comandos, estructura) sigue saliendo de los pasos 1-3: el diseño no sustituye al código. + ## 1. Lee lo que hay `CLAUDE.project.md` actual (si existe, **actualízalo**, no lo reescribas: conserva lo que escribió el equipo), `README`, manifiestos y lockfiles, configuración de lint/format/tests, `Makefile`/scripts, Dockerfile y compose, workflows de CI, `.env.example`, `docs/adr/`, y la estructura de primer y segundo nivel. Si el argumento nombra un área, profundiza ahí. @@ -24,7 +27,7 @@ Los comandos de build, test y lint del proyecto se **corren** antes de escribirl Siguiendo las secciones de la plantilla: stack con versiones y el linter/formatter **real**, estructura (en monorepo, cada paquete con su stack y su responsabilidad), arquitectura observada (capas, módulos, flujos principales con su ruta), comandos confirmados, despliegue y CI, variables de entorno críticas (de `.env.example` y de la capa de config), testing (framework, dónde viven, cobertura que se mide). ## 4. Pregunta lo que no se puede derivar -En **una sola tanda** y priorizado, a la persona: visión y usuarios del producto, invariantes de negocio que no se pueden violar, requisitos de seguridad y compliance, **nivel ASVS** objetivo (propón uno: L1, o L2 si hay auth, datos personales o pagos), atributos de calidad con umbral (latencia, disponibilidad), integraciones y sus reglas. Lo que no te respondan queda marcado como pendiente en el archivo, no inventado. +Si partiste de un `design.md`, pregunta solo lo que el diseño no responde. Si no, en **una sola tanda** y priorizado, a la persona: visión y usuarios del producto, invariantes de negocio que no se pueden violar, requisitos de seguridad y compliance, **nivel ASVS** objetivo (propón uno: L1, o L2 si hay auth, datos personales o pagos), atributos de calidad con umbral (latencia, disponibilidad), integraciones y sus reglas. Lo que no te respondan queda marcado como pendiente en el archivo, no inventado. ## 5. Escribe y cierra - Elimina el comentario de plantilla y las secciones que no aplican; deja las preguntas abiertas en una sección final **Pendiente de confirmar**. diff --git a/shared/.claude/skills/project-context/desde-design.md b/shared/.claude/skills/project-context/desde-design.md new file mode 100644 index 0000000..c07f122 --- /dev/null +++ b/shared/.claude/skills/project-context/desde-design.md @@ -0,0 +1,72 @@ +# Del diseño al proyecto: design.md → CLAUDE.project.md + ADRs + +Referencia de la skill `project-context` cuando parte de un `design.md` del +[system-design-kit](https://github.com/lacasoft/system-design-kit). El objetivo es que nadie +reescriba requisitos ni decisiones: el diseño se **traslada**, resumido y con la fuente a la vista. + +## Cómo reconocer el documento +- Front matter con `title`, `language` (`es` o `en`) y `updated`. +- Una marca `` bajo cada título. Guíate por las marcas, no por los títulos + (pueden estar en inglés o retocados). Ids: `requirements`, `out-of-scope`, `scale`, + `api-data-model`, `architecture`, `decisions`, `failures`, `ai-review`. +- Una sección que solo tiene `_⏳ Pendiente_` / `_⏳ Pending_` está vacía: no hay nada que trasladar. +- Si la ruta está fuera del proyecto, Claude Code pedirá permiso para leerla (o arranca con + `claude --add-dir `). + +## Mapeo a CLAUDE.project.md + +| De design.md | A CLAUDE.project.md | Cómo | +|---|---|---| +| título + `requirements` (funcionales) | **Visión del producto** | 2-4 líneas: qué es, para quién, los casos de uso principales | +| `out-of-scope` | **Visión del producto** → línea "Fuera de alcance (v1)" | Lista corta. Es lo que más evita que el agente construya de más | +| `api-data-model` (entidades, términos) | **Modelo / dominio** | Glosario de entidades y su clave de acceso | +| `api-data-model` (endpoints, eventos) | **Arquitectura** → "Contratos" | Solo los de los casos de uso principales, con idempotencia y paginación si el diseño las fija | +| `architecture` | **Arquitectura** | Componentes y flujo principal, síncrono y asíncrono; enlaza el diagrama del diseño en vez de copiarlo | +| `failures` | **Arquitectura** → "Fallas previstas" y **Testing** | Una línea por escenario (escenario → mitigación). En *Testing*, los que merecen test de regresión | +| `requirements` (no funcionales) + `scale` | **Atributos de calidad** | Los umbrales con número (latencia, disponibilidad, consistencia) y el orden de magnitud de carga | +| `requirements` (seguridad, privacidad) | **Seguridad y compliance** | Si el diseño fija el nivel ASVS, úsalo; si no, propónlo y márcalo como pendiente | +| `decisions` | **Decisiones de arquitectura** | Un ADR por decisión (abajo) y aquí solo la línea con el enlace | +| `decisions` (tecnologías elegidas) | **Stack tecnológico** | Como "decidido en el diseño"; versiones y herramientas reales salen del repo | +| `architecture` / `decisions` (terceros, nube, región) | **Integraciones externas** y **Despliegue** | Lo que el diseño fije; lo demás, del repo | +| supuestos `(supuesto)` sin confirmar | **Pendiente de confirmar** | Cada supuesto que condiciona la implementación | +| `ai-review` | — | No se traslada: los hallazgos aceptados ya deberían estar en `decisions` o `failures`. Si hay alguno aceptado que no lo esté, señálalo | + +Al principio del archivo, una línea con la fuente: + +```markdown +> Diseño de origen: `/design.md` (actualizado AAAA-MM-DD). Decisiones en `docs/adr/`. +``` + +Sirve para la próxima importación y para que cualquiera sepa de dónde salió el contenido. + +## ADRs desde las decisiones +Por cada decisión de `decisions` que condicione el código (elegir almacenamiento, un patrón de +integración, un límite entre servicios, una política), crea un ADR con la plantilla y la +numeración de la skill `adr`: + +| ADR | De dónde sale | +|---|---| +| Título | La decisión, en positivo ("Usar cola SQS para la analítica de clics") | +| Contexto | El requisito o la escala que la motiva, citando la sección del diseño | +| Decisión | Lo elegido, tal como lo dice el diseño | +| Alternativas consideradas | La alternativa descartada y por qué | +| Consecuencias | El tradeoff: lo que se gana y lo que se paga; las fallas asociadas de `failures` | +| Estado | Las decisiones las tomó la persona al diseñar: **pregunta una vez** si las marcas `Aceptado`. Sin respuesta, `Propuesto` | + +- Una decisión **tachada** en el diseño (`~~…~~`) no genera ADR propio: si la nueva la sustituye, + la nueva lo dice en su contexto. +- No crees ADRs para detalles locales que el diseño menciona de pasada; esos van como línea en + *Arquitectura*. + +## Reimportar (el diseño cambió) +- Compara el `updated` del diseño con el de la línea "Diseño de origen". Si no cambió, no hay + nada que hacer. +- Actualiza solo lo que vino del diseño; lo que el equipo escribió después en `CLAUDE.project.md` + se respeta (regla general de la skill). +- Un ADR `Aceptado` no se edita: si la decisión cambió, crea uno nuevo que lo **reemplace** + y marca el anterior `Reemplazado por ADR-NNNN`, como indica la skill `adr`. + +## Idioma +`CLAUDE.project.md` y los ADRs siguen el idioma del proyecto (el de su `CLAUDE.project.md` si ya +existe; si no, español). Si el diseño está en otro idioma, traduce el contenido sin cambiar el +significado ni los nombres propios de componentes. diff --git a/shared/.claude/skills/ship-gate/SKILL.md b/shared/.claude/skills/ship-gate/SKILL.md index c2f7c22..a32b079 100644 --- a/shared/.claude/skills/ship-gate/SKILL.md +++ b/shared/.claude/skills/ship-gate/SKILL.md @@ -11,7 +11,7 @@ Eres un revisor de release implacable. Antes de que algo salga a producción, es Cuando detectes intención de desplegar ("deploy", "push to prod", "go live", "release"), ejecuta el gate primero. No des el OK hasta que **todos los CRITICAL** pasen. ## Categorías auditadas -1. **Seguridad**: secretos hardcodeados o `.env` commiteado, dependencias con CVEs (`npm audit`/`pip-audit`), authz deny-by-default, validación de input, CORS/headers, TLS. Los controles del **nivel ASVS 5.0** del proyecto (en `CLAUDE.project.md`) verificados para lo que cambia en esta release; sin hallazgos CRITICAL/HIGH abiertos de la skill `owasp-review` (ni del plugin `claude-security`, si está instalado: en un release grande, un escaneo de cambios a effort `high`). +1. **Seguridad**: secretos hardcodeados o `.env` commiteado, dependencias con CVEs (`npm audit`/`pip-audit`), authz deny-by-default, validación de input, CORS/headers, TLS. Los controles del **nivel ASVS 5.0** del proyecto (en `CLAUDE.project.md`) verificados para lo que cambia en esta release; sin hallazgos CRITICAL/HIGH abiertos de la skill `owasp-review`. - **CI/CD (GitHub Actions)**: sin **script injection** — `${{ github.event.* }}` (título/cuerpo de issue/PR, comentarios, `head_ref`) interpolado en un bloque `run:` es RCE; pásalo por `env:` y referencia `$VAR`. Sin `pull_request_target` que haga checkout del código del PR. `permissions:` mínimas a nivel top (`contents: read`), nunca `write-all`. Actions de terceros **pinneadas por SHA** (no `@v1`/`@main`, refs mutables). Secretos nunca en `echo`/`env`/CLI args (quedan en logs); usa OIDC en vez de claves estáticas. 2. **Datos**: migraciones reversibles y aplicadas, sin pérdida de datos, backups verificados, índices en columnas calientes. 3. **Despliegue**: variables de entorno de prod presentes (fail-fast), health/readiness checks, graceful shutdown, rollback definido. diff --git a/shared/templates/PROJECT.template.md b/shared/templates/PROJECT.template.md index 70b0da5..bf1951b 100644 --- a/shared/templates/PROJECT.template.md +++ b/shared/templates/PROJECT.template.md @@ -2,6 +2,8 @@ PLANTILLA DE DEFINICIÓN DEL PROYECTO La forma rápida de rellenarla: abre Claude Code y corre /project-context. Lee el repo, confirma los comandos y te pregunta solo lo que no se puede deducir. + Si diseñaste el sistema con system-design-kit: /project-context /design.md traslada + requisitos, escala, arquitectura y fallas, y crea los ADRs desde las decisiones. Rellena las secciones que apliquen y borra las que no. Esto es lo que da CONTEXTO real al agente (qué es el producto, su dominio, reglas y convenciones). Sin esto, el agente solo conoce las reglas genéricas. El root CLAUDE.md importa este archivo con @CLAUDE.project.md. diff --git a/stacks/frontend/_common/.claude/CLAUDE.md b/stacks/frontend/_common/.claude/CLAUDE.md index cd22b62..27808aa 100644 --- a/stacks/frontend/_common/.claude/CLAUDE.md +++ b/stacks/frontend/_common/.claude/CLAUDE.md @@ -31,4 +31,4 @@ - Sanitiza HTML antes de inyectarlo (XSS); no confíes en datos del backend para `innerHTML`/`[innerHTML]`. ## Agentes/skills recomendados (los instala el kit) -Agentes: `frontend-developer`, `ui-ux-designer`, `code-reviewer`. Skills: `frontend-design`, `ui-design-system`, `senior-frontend`, `clean-code`. Herramienta: `react-doctor` (en React). +Agentes: `frontend-developer`, `ui-ux-designer`, `code-reviewer`. Skills: `frontend-design`, `ui-design-system`, `senior-frontend`, `clean-code`. diff --git a/stacks/frontend/react/.claude/CLAUDE.md b/stacks/frontend/react/.claude/CLAUDE.md index b5775f7..7686148 100644 --- a/stacks/frontend/react/.claude/CLAUDE.md +++ b/stacks/frontend/react/.claude/CLAUDE.md @@ -18,7 +18,7 @@ ## UI y calidad - shadcn/ui + Tailwind. Animaciones con `motion` (framer-motion) cuando aporten, no decorativas. -- `npx react-doctor .` para auditar (state/effects/perf/a11y/bundle) antes de cerrar features grandes o en CI; no como hook bloqueante. +- Auditoría con herramientas libres: `eslint-plugin-react-hooks` (reglas de hooks y efectos) en el lint, el Profiler de React DevTools para renders de más, y el analizador de bundle del bundler antes de cerrar features grandes. ## Comandos - `npm run dev` · `build` · `preview` · `lint` diff --git a/stacks/mobile/react-native/.claude/CLAUDE.md b/stacks/mobile/react-native/.claude/CLAUDE.md index f1a9b39..0648795 100644 --- a/stacks/mobile/react-native/.claude/CLAUDE.md +++ b/stacks/mobile/react-native/.claude/CLAUDE.md @@ -19,7 +19,7 @@ - Permisos con los módulos de Expo; maneja el caso "denegado". ## Calidad -- `npx react-doctor .` incluye reglas específicas de React Native (perf, listas, efectos). +- `eslint-plugin-react-hooks` en el lint y React Native DevTools (profiler) para listas, renders y efectos. ## Comandos - `npx expo start` · `npm run android` · `npm run ios` · `npm run lint` diff --git a/test/hook-handler.test.cjs b/test/hook-handler.test.cjs index ed066b2..744083d 100644 --- a/test/hook-handler.test.cjs +++ b/test/hook-handler.test.cjs @@ -93,6 +93,8 @@ test("ASK al registrar un servidor MCP", () => assert.equal(decisionDe("claude mcp add github -- npx -y @modelcontextprotocol/server-github"), "ask")); test("el motivo del ASK incluye la antigüedad mínima", () => assert.match(bash("npm install express@5.1.0").permissionDecisionReason, /7\+ días/)); +test("el motivo del ASK recuerda: nada de pago, API key ni licencia propietaria", () => + assert.match(bash("npm install express@5.1.0").permissionDecisionReason, /sin API key.*open source/)); // ---- pre-edit: escáner de secretos ---- // Fixtures tipo-secreto construidos por concatenación para no dejar literales @@ -173,3 +175,16 @@ test("memory-only PERMITE la memoria local", () => test("memory-only DENIEGA escribir código", () => assert.equal(memoryOnly("src/app.ts"), "deny")); test("memory-only DENIEGA un prefijo engañoso", () => assert.equal(memoryOnly(".claude/agent-memory-evil/x.md"), "deny")); + +// ---- pre-read: .env reales no; ejemplos sí ---- +// Read(./.env.*) en permissions bloqueaba también .env.example (las reglas no admiten excepciones). +function lectura(file) { + const { out } = run("pre-read", { tool_input: { file_path: file } }); + return out.trim() ? JSON.parse(out).hookSpecificOutput.permissionDecision : null; +} +for (const f of [".env", ".env.production", ".env.prod", "/repo/apps/api/.env.local", ".envrc"]) { + test(`pre-read DENIEGA ${f}`, () => assert.equal(lectura(f), "deny")); +} +for (const f of [".env.example", "/repo/.env.sample", ".env.template", "src/env.ts", "environment.md"]) { + test(`pre-read PERMITE ${f}`, () => assert.equal(lectura(f), null)); +} diff --git a/test/install-update.test.cjs b/test/install-update.test.cjs index 796f74d..01b7af3 100644 --- a/test/install-update.test.cjs +++ b/test/install-update.test.cjs @@ -411,6 +411,24 @@ test("--stack explícito gana sobre el stack recordado", () => { fs.rmSync(dir, { recursive: true, force: true }); }); +test("--update retira Read(./.env.*), que bloqueaba .env.example, y conserva tus denies", () => { + const dir = proyecto(); + const sp = path.join(dir, ".claude", "settings.json"); + const s = leerJSON(sp); + s.permissions.deny.push("Read(./.env.*)", "Read(./secretos/**)"); + fs.writeFileSync(sp, JSON.stringify(s, null, 2)); + + const salida = instalar(dir, ["--update"]); + + const deny = leerJSON(sp).permissions.deny; + assert.ok(!deny.includes("Read(./.env.*)"), "el patrón viejo se retira"); + assert.ok(deny.includes("Read(./.env.production)"), "lo sustituyen denies explícitos"); + assert.ok(deny.includes("Read(./secretos/**)"), "tus denies siguen"); + assert.match(JSON.stringify(leerJSON(sp).hooks.PreToolUse), /pre-read/, "y el hook pre-read"); + assert.match(salida, /Read\(\.\/\.env\.\*\) \(el kit ya no lo usa\)/); + fs.rmSync(dir, { recursive: true, force: true }); +}); + test("--update nunca toca la memoria ni CLAUDE.project.md", () => { const dir = proyecto(); const mem = path.join(dir, ".claude", "memory", "notas.md");