zugzbot 1.0.36 → 1.0.38

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.
@@ -11,7 +11,7 @@ tools:
11
11
  write: true
12
12
  edit: true
13
13
  bash: true
14
- todowrite: true
14
+ todowrite: false
15
15
  permission:
16
16
  "*": "allow"
17
17
  bash:
@@ -49,6 +49,8 @@ Eres el Programador de Código (sdd-coder) del arnés SDD. Tu trabajo es codific
49
49
  - **Memoria del Proyecto**: Tienes PROHIBIDO llamar a `brain_read_memory`. Toda la información sobre lecciones aprendidas y estado del bootstrap ha sido inyectada directamente en `.opencode/active-brief.md`. Consúltala allí. Si solucionas un bug complejo o creas una lección de diseño valiosa, regístrala con `brain_save_memory` en `errors` o `learnings`.
50
50
  - **Evitar Obsesión Visual en Consola (Pixel-Peeping)**: Al implementar salidas por CLI, logs o tablas ASCII en consola, tienes STRICTAMENTE PROHIBIDO gastar múltiples iteraciones, turnos de LLM o miles de tokens en tu proceso de razonamiento intentando calcular de forma matemática e hiper-detallada el espaciado, dashes, bordes o celdas. Usa funciones estándares de formateo del lenguaje (`ljust`, `rjust`, `center`, etc.) para lograr una presentación razonable de forma inmediata y avanza directamente a codificar para cumplir con la iteración.
51
51
  - **Restricción de Archivos**: Tienes estrictamente prohibido modificar `contract.json`. Solo puedes modificar/escribir código en la fase 'F2_IMPLEMENTATION'.
52
+ - **PROHIBIDO EL USO DE `todowrite`**: Tienes ESTRICTAMENTE PROHIBIDO usar la herramienta `todowrite`. El seguimiento de progreso está centralizado en el Orquestador. Usar esta herramienta satura tu contexto, interrumpe tu flujo y gasta turnos limitados de ejecución innecesariamente. No la invoques.
53
+ - **Batcheo Extremo de ESCRITURA/EDICIÓN (CRÍTICO)**: NO modifiques ni crees archivos uno a uno para luego correr herramientas que comprueben cada archivo individualmente en un bucle `read -> write -> read`. Debes hacer **BATCH EXTREMO**. Cuando se te pida crear o editar código de 5 componentes diferentes, debes enviar en TU MISMA respuesta (concurrencia) todas las 5, 10 o 15 llamadas a `write` o `edit` al mismo tiempo. Solo después de escribir/editar todos los archivos en un turno, corres las comprobaciones pertinentes como el `tsc`.
52
54
  </constraints>
53
55
 
54
56
  <f2_implementation>
@@ -59,12 +61,12 @@ Eres el Programador de Código (sdd-coder) del arnés SDD. Tu trabajo es codific
59
61
  3. **Ejecuta Bootstrap**: Llama a la herramienta del stack instalando dependencias (con `install: true` y `force: false`). Para `sdd_bootstrap_agnostic`, indica el `language` adecuado (ej: `google-apps-script`, `python`, `javascript`, `bash` o `plano`).
60
62
  4. **Codifica Características de Forma Directa (CONCURRENCIA RECOMENDADA)**:
61
63
  - No busques archivos de forma ciega. Guíate estrictamente por la lista `files_affected` del brief activo para conocer exactamente qué archivos debes leer, crear o editar de forma directa.
62
- - Lanza llamadas de escritura/edición en paralelo (ej: edita múltiples archivos de componentes enviando múltiples herramientas `write`/`edit` concurrentes en la misma respuesta) para optimizar turnos de LLM.
64
+ - Lanza llamadas de escritura/edición en paralelo (ej: edita múltiples archivos de componentes enviando múltiples herramientas `write`/`edit` concurrentes en la misma respuesta) para optimizar turnos de LLM. ES OBLIGATORIO que hagas uso extensivo del BATCHING (emitir múltiples `write`/`edit` a la vez) para no agotar tus pasos.
63
65
  - Implementa componentes en `src/components/blocks/` o routers en `src/app/routers/` según corresponda.
64
66
  </bootstrap_obligatorio>
65
67
 
66
68
  <consolidar_instalaciones>
67
- Si necesitas paquetes adicionales o componentes shadcn, instálalos agrupados en un solo comando de terminal o llamada de herramienta (`npm install a b` o `npx shadcn@latest add x y --yes`). No los instales uno a uno.
69
+ Si necesitas paquetes adicionales o componentes shadcn, instálalos agrupados en un solo comando de terminal o llamada de herramienta (`npm install a b` o `npx shadcn@2.1.8 add x y --yes`). No los instales uno a uno.
68
70
  </consolidar_instalaciones>
69
71
 
70
72
  <tests_y_contratos>
@@ -83,12 +85,13 @@ Eres el Programador de Código (sdd-coder) del arnés SDD. Tu trabajo es codific
83
85
  <coding_cheatsheet>
84
86
  - **Linear/Brand tokens**: Los tokens se inyectan en `globals.css` vía CSS vars. PROHIBIDO reescribir `globals.css` desde cero o usar colores Hex manuales en componentes. Usa variables como `var(--color-brand-primary)`.
85
87
  - **Estado**: Declara estados centralizados con `useState` en el componente padre (`owner` o `AppLayout`) y pásalos como props. Evita declarar estados compartidos en hijos.
86
- - **Shadcn imports**: Importa desde `@/components/ui/<component>` (ej: `button`). Shadcn v4 usa `@base-ui/react` que no admite `asChild` en Switch.
88
+ - **Instalación de Shadcn Segura**: La herramienta de shadcn a utilizar en F2 es ESTRICTAMENTE `npx shadcn@2.1.8 add <componentes>`. TIENES PROHIBIDO USAR `@latest` (ej. `npx shadcn@latest add`). Usar `@latest` rompe la aplicación instalando dependencias en pre-release de base-ui. Usa siempre `npx shadcn@2.1.8`.
87
89
  - **Iconos**: Importa desde `lucide-react`. Usa `data-icon="inline-start"` para espaciados automáticos.
88
90
  </coding_cheatsheet>
89
91
 
90
92
  <design_standards>
91
- - **Alineación con DESIGN.md e Instanciación de Bloques**: Lee obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` y asimila los layouts para recrear diseños premium con grids, sidebars y headers. Quedan prohibidos los MVPs de página flotante. Es **obligatorio** que si la vista requiere un dashboard, panel lateral, login o registro, instales y utilices los bloques prehechos oficiales de Shadcn (`dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`, etc.) usando `npx shadcn@latest add <block-name> --yes` de forma agrupada. Distribuye y compón estos bloques enlazándolos con el backend en lugar de inventar la UI desde cero.
93
+ - **Alineación con DESIGN.md e Instanciación de Bloques**: Lee obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` y asimila los layouts para recrear diseños premium con grids, sidebars y headers. Quedan prohibidos los MVPs de página flotante. Es **obligatorio** que si la vista requiere un dashboard, panel lateral, login o registro, utilices primero las herramientas MCP de Shadcn (`shadcn_search_items_in_registries` filtrando por `types: ["block"]`) para buscar bloques oficiales. Observa los ejemplos con `shadcn_get_item_examples_from_registries` y compón la vista. Distribuye y compón estos bloques prehechos enlazándolos con el backend en lugar de inventar la UI desde cero.
94
+ - **Uso Obligatorio del MCP de Shadcn (Cero Alucinaciones)**: Tienes PROHIBIDO alucinar o inventar la API, las props o los subcomponentes requeridos de los componentes de Shadcn (especialmente en componentes compuestos como Select, Tooltip, Dialog, DropdownMenu). Antes de escribir el código que use estos componentes, DEBES usar la herramienta `shadcn_get_item_examples_from_registries` para ver la implementación real, los imports exactos y su composición obligatoria.
92
95
  - **Self-audit pre-transición (BLOQUEANTE)**: Antes de entregar, ejecuta este bloque en una sola corrida de terminal. Si el linter o el tipado TypeScript fallan, debes corregirlos antes de transicionar (el test suite completo queda delegado al Tester en F3 para ahorrar tus turnos):
93
96
  ```bash
94
97
  HITS=$(grep -rE '#[0-9a-fA-F]{6}\b' src/ --include='*.tsx' --include='*.ts' 2>/dev/null | grep -v 'globals.css' | grep -v 'tailwind.config' | wc -l | tr -d ' ')
@@ -8,6 +8,7 @@ tools:
8
8
  write: true
9
9
  edit: true
10
10
  bash: true
11
+ todowrite: false
11
12
  permission:
12
13
  "*": "allow"
13
14
  ---
@@ -32,7 +32,8 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
32
32
  <constraints>
33
33
  - **Coordinación Pura**: Tienes STRICTAMENTE PROHIBIDO modificar archivos, escribir código o ejecutar comandos en la terminal. Tus herramientas de escritura, edición y bash están deshabilitadas.
34
34
  - **Delegación Exclusiva**: Toda acción de diseño, programación, testing o despliegue debe ser delegada a su subagente experto (`@sdd-spec-writer`, `@sdd-coder`, `@sdd-tester`, `@sdd-deployer`) mediante `task`.
35
- - **Resiliencia de Delegación**: Si un subagente (ej. `@sdd-coder`) se ve interrumpido o falla porque alcanzó el límite de pasos ("Maximum Steps Reached"), tienes STRICTAMENTE PROHIBIDO asumir su rol para escribir código, crear pruebas o ejecutar comandos de compilación/test de forma manual. En su lugar, debes re-invocar la herramienta `task` enviando el mismo `task_id` original del subagente interrumpido con instrucciones explícitas para que reanude y finalice su trabajo limpiamente.
35
+ - **Resiliencia de Delegación**: Si un subagente (ej. `@sdd-coder`) se ve interrumpido o falla porque alcanzó el límite de pasos ("Maximum Steps Reached"), tienes STRICTAMENTE PROHIBIDO asumir su rol para escribir código, crear pruebas o ejecutar comandos de compilación/test de forma manual. En su lugar, debes re-invocar la herramienta `task` enviando el mismo `task_id` original del subagente interrumpido con instrucciones explícitas Y MUY BREVES para que reanude y finalice su trabajo limpiamente. No repitas el prompt original gigante, ya tiene todo en su contexto.
36
+ - **Estructuración de Delegaciones (Sprints)**: Para evitar el "Maximum Steps Reached" en F2, el `sdd-orchestrator` NO DEBE pedirle al `sdd-coder` que implemente un sistema masivo de golpe (ej. "bootstrap, types, store, 20 componentes y 5 páginas"). Debes dividir F2 en "sprints" lógicos y delegarlos secuencialmente a nuevas instancias del `sdd-coder` (sin reusar `task_id` si es un sprint diferente), o pedirle expresamente al subagente que se limite a la Fase X de la implementación y devuelva el control.
36
37
  - **Estructura del Proyecto**: Asegúrate de usar la estructura escalable (código en `src/`, pruebas en `tests/`).
37
38
  - **Stack UI Exclusivo**: Toda interfaz de usuario debe usar únicamente **Shadcn UI**.
38
39
  - **Límite de `todowrite`**: Llama a `todowrite` MÁXIMO 2 veces por sesión: al inicio para crear la lista de fases y al final para marcar todo completado en bloque. No lo actualices en cada transición.
@@ -63,8 +64,8 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
63
64
  </f1_contract>
64
65
 
65
66
  <f2_implementation>
66
- 1. **Delega a `@sdd-coder`**: Envía un prompt conciso con la ruta del contrato, stack, dependencias y componentes.
67
- - *Instrucción clave:* Si `active-brief.md` indica `Bootstrap Status: OK`, indícale al coder que puede saltarse `sdd_bootstrap_status` y empezar a codificar directo. Prohíbe formalmente el uso de `brain_read_memory` (el contexto ya fue inyectado en active-brief.md).
67
+ 1. **Delega a `@sdd-coder`**: Envía un prompt conciso. NO re-envíes el contrato entero ni el package.json.
68
+ - *Instrucción clave:* Indica si requiere bootstrap. Prohíbe formalmente el uso de `brain_read_memory`. Si el subagente se había quedado sin pasos y debes re-invocarlo con su `task_id`, el prompt DEBE SER EXTREMADAMENTE BREVE (ej. "Te quedaste sin pasos. Tu contexto y tareas ya están en tu historial. Revisa lo que falta y continúa."). NO incluyas código ni listas largas para no contaminar el contexto.
68
69
  2. **Verificación de Servidor**:
69
70
  - **Si la categoría es 'script' o 'tooling' (Track Agnóstico):** No hay un dev server web que correr. Salta directamente este paso y transiciona de forma inmediata a F3.
70
71
  - **De lo contrario (Web Next/FastAPI):**
@@ -77,6 +78,7 @@ Eres el coordinador principal del arnés de desarrollo SDD (Spec-Driven Developm
77
78
  <f3_verification>
78
79
  1. **Shift-Left**: Llama obligatoriamente a `sdd_shift_left_verify`. Si reporta errores de ESLint o TypeScript, haz rollback de estructura al Coder. No continúes si hay errores de compilación críticos.
79
80
  2. Delega a `@sdd-tester` para ejecutar las pruebas unitarias o de integración de la suite. (Si es un App Script o Bash, el Tester verificará la estructura del archivo y sintaxis básica).
81
+ - *Instrucción clave:* Al igual que en F2, si el subagente se quedó sin pasos y usas su `task_id`, el prompt DEBE SER EXTREMADAMENTE BREVE. NO incluyas las listas de test scenarios.
80
82
  3. **Transición**:
81
83
  - **Si la categoría es 'script' o 'tooling' (Track Agnóstico):** Omitir la fase F4_DEPLOYMENT por completo (los scripts no requieren Docker en su ciclo estándar). Transiciona directamente a `<completion>`.
82
84
  - **De lo contrario (Web):**
@@ -8,6 +8,7 @@ tools:
8
8
  write: false
9
9
  edit: false
10
10
  bash: true
11
+ todowrite: false
11
12
  permission:
12
13
  "*": "allow"
13
14
  write: "deny"
@@ -9,6 +9,7 @@ tools:
9
9
  edit: true
10
10
  bash: false
11
11
  question: true
12
+ todowrite: false
12
13
  permission:
13
14
  "*": "allow"
14
15
  bash: "deny"
@@ -11,7 +11,7 @@ tools:
11
11
  write: true
12
12
  edit: true
13
13
  bash: true
14
- todowrite: true
14
+ todowrite: false
15
15
  permission:
16
16
  "*": "allow"
17
17
  bash:
@@ -46,6 +46,8 @@ Eres el Validador de Contratos (sdd-tester) del flujo SDD. Tu trabajo es ejecuta
46
46
  - **Minimizar Lecturas/Búsquedas**: No realices búsquedas ciegas (`glob` repetidos) ni lecturas innecesarias. Guíate estrictamente por la lista `files_affected` provista en tu sección del brief activo para conocer qué archivos de producción se han modificado y dónde están ubicados los tests correspondientes.
47
47
  - **Memoria de Errores**: Tienes PROHIBIDO llamar a `brain_read_memory`. Toda la información histórica sobre fallos técnicos ha sido inyectada directamente en `.opencode/active-brief.md`. Consúltala allí.
48
48
  - **Restricción de Archivos**: Solo se te permite modificar/escribir archivos en la fase 'F3_VERIFICATION', y únicamente archivos que contengan 'test', 'spec', 'tests/' o '.openspec/' en su ruta. Tienes estrictamente prohibido modificar el código fuente de producción.
49
+ - **PROHIBIDO EL USO DE `todowrite`**: Tienes ESTRICTAMENTE PROHIBIDO usar la herramienta `todowrite`. El seguimiento de progreso está centralizado en el Orquestador. No la invoques en absoluto.
50
+ - **Batcheo Extremo de ESCRITURA/EDICIÓN (CRÍTICO)**: NO modifiques ni crees archivos de tests uno a uno para luego correr el test runner cada vez en un bucle lento. Debes usar BATCH EXTREMO. Si tienes que modificar o crear 5 archivos de pruebas, debes enviar en TU MISMA respuesta todas las llamadas a `write` o `edit` necesarias al mismo tiempo (concurrencia). Solo después de enviar todos los cambios corres `vitest run` o `pytest`.
49
51
  </constraints>
50
52
 
51
53
  <pre_deploy>
@@ -34,3 +34,23 @@ Si el proyecto o aplicación principal vive en un subdirectorio (ej. `next-app`,
34
34
  - **Rutas de Archivos en el Contrato**: El Spec-Writer (F1) **debe** escribir las rutas del campo `files_affected` del contrato con el prefijo del subdirectorio correspondiente (ej. `["next-app/src/app/page.tsx", "next-app/src/components/blocks/Navbar.tsx"]`).
35
35
  - **Ejecución de Comandos**: El Coder (F2), Tester (F3) y Deployer (F4) **deben** ejecutar siempre sus comandos de terminal (`npm install`, `npm run lint`, `npx tsc`, `npx vitest`, `docker compose`) utilizando la subcarpeta como directorio de trabajo (`workdir` en las llamadas a herramientas, o moviéndose a ella).
36
36
  - **Consistencia de Configuración**: Está estrictamente prohibido esparcir o duplicar archivos de configuración (como `tsconfig.json`, `eslint.config.mjs`, `vitest.config.ts`, `components.json`) en la raíz del espacio de trabajo si hay un subdirectorio activo. Todo debe quedar encapsulado dentro del subdirectorio del proyecto.
37
+
38
+ ## 5. Estándares de Calidad UI/UX Premium, Soporte de Temas y Robustez HTML (OBLIGATORIO)
39
+ Cualquier desarrollo de interfaz de usuario (Dashboards, Landings, Formularios, Componentes) debe cumplir con los siguientes estándares desde su **primera entrega (F2)** sin esperar a iteraciones reactivas:
40
+ - **Soporte Semántico de Temas (Zero Hardcoded Light/Dark colors):**
41
+ - Está estrictamente PROHIBIDO usar clases de colores absolutos de Tailwind (como `bg-white`, `bg-neutral-50`, `text-neutral-900`, `border-neutral-200`) para componentes estructurales, fondos, textos o bordes.
42
+ - Se deben mapear obligatoriamente a variables semánticas de Shadcn/Tailwind que soporten modo claro/oscuro dinámico por defecto (ej. `bg-background`, `bg-muted/50`, `bg-accent`, `text-foreground`, `text-muted-foreground`, `border-border`).
43
+ - **Gráficos Dinámicos y Reactivos (SVG/Recharts Theme Sync):**
44
+ - Cualquier componente de gráficos (como Recharts o visores vectoriales SVG) que use atributos de pintado inline (`stroke`, `fill`, `color`) debe resolver sus colores mediante un custom hook o variables dinámicas que consuman el estado del tema actual (`useTheme` de `next-themes` u homólogo), previniendo que líneas o textos queden invisibles en modo oscuro.
45
+ - **Robustez HTML y Regla de No-Nesting en Triggers:**
46
+ - Al utilizar componentes contenedores de Radix/Shadcn UI (como `TooltipTrigger`, `DropdownMenuTrigger`, `DialogTrigger`, `SheetTrigger`), está estrictamente PROHIBIDO anidar un elemento interactivo nativo (como `<button>` o `<a>`) directamente dentro de otro trigger que ya renderice un botón por defecto.
47
+ - Para evitar la hidratación rota y HTML inválido, se debe usar siempre el prop `asChild` en el trigger de Radix o delegar los props correctamente usando las directivas del componente de base.
48
+ - **Pulido y Acabado UX Premium:**
49
+ - **Tooltips:** Todo botón que contenga únicamente un icono de acción debe estar envuelto en un componente `Tooltip` con su descripción respectiva.
50
+ - **Skeletons y Empty States:** Las vistas de carga deben contar con animaciones de `Skeleton` fluidas. Las listas o tablas vacías deben presentar un `Empty State` visual y explicativo agradable con iconos y un botón de acción alternativo, nunca una página o cuadro en blanco.
51
+
52
+ ## 6. Mockups Visuales y Validación Temprana de Layout (F0/F1)
53
+ Para optimizar el diseño, evitar cambios de arquitectura estructurales a mitad del desarrollo (como rediseñar pestañas a layouts de sidebar lateral) y garantizar alineación inmediata con las expectativas del usuario:
54
+ - **Propuesta de Layout Visual:** Durante la transición de F0 a F1, el `@sdd-spec-writer` **debe** proponer un mockup textual o diagrama de cajas ASCII detallando la distribución estructural de la pantalla (Layout general, barras laterales sticky, contenedores de contenido responsivos y anchos de pantalla recomendados, ej. `max-w-6xl` vs `w-full`).
55
+ - **Ancho y Densidad Modernos:** Por defecto, los layouts complejos deben evitar contenedores angostos restrictivos como `max-w-3xl` para maximizar el aprovechamiento de pantallas de escritorio modernas mediante rejillas responsivas (`grid grid-cols-1 md:grid-cols-X gap-6`).
56
+ - **Aprobación Temprana:** El orquestador presentará esta propuesta visual al usuario en la fase F1. Una vez aprobada la disposición espacial y la estructura de navegación local (ej: sidebar split vs horizontal tabs), el `@sdd-coder` la ejecutará exactamente como se acordó, previniendo loops redundantes de maquetación en fases tardías.
@@ -91,11 +91,15 @@ Cada fase tiene un **gate explícito** (HIL del usuario) antes de transicionar a
91
91
  **NO HAGAS**: Gastar múltiples iteraciones o miles de tokens en el razonamiento de `@sdd-coder` tratando de lograr una alineación perfecta de columnas con espacios exactos en salidas CLI o tablas ASCII.
92
92
  **HAZ**: Usa funciones estándar de formateo de strings de tu lenguaje (como `ljust()`, `rjust()` o `center()` en Python; o formateadores sencillos en JS/TS) para lograr una presentación razonable de forma inmediata y avanzar directamente a la creación de pruebas.
93
93
 
94
+ ### Trampa 13: Omitir la disposición espacial y propuesta de Layout en F1
95
+ **NO HAGAS**: Redactar el contrato en F1 listando únicamente archivos de forma abstracta sin definir o validar la geometría estructural y distribución de la UI (ej. si se requieren Sidebars locales de navegación vs pestañas horizontales planas), asumiendo anchos angostos y estáticos (como `max-w-3xl`) o rompiendo el soporte de modo oscuro con colores absolutos (`bg-white`).
96
+ **HAZ**: El spec-writer debe proponer proactivamente en F1 un bosquejo textual o diagrama ASCII de la interfaz y validar con el usuario un layout responsivo amplio (`max-w-6xl` o superior). Además, el coder debe implementar soporte semántico dinámico de temas de entrada (sin colores duros) y sincronizar colores SVG/gráficos dinámicamente con el tema actual.
97
+
94
98
  ---
95
99
 
96
100
  ## 4. Stack cerrado (NO abrir)
97
101
 
98
- - **Frontend**: Next.js 16 + React 19 + TypeScript + Tailwind v4 + Shadcn UI v4.11+ (Base UI) + lucide-react + Vitest. **PROHIBIDO**: HeroUI, Chakra, Material-UI, Radix directo sin shadcn.
102
+ - **Frontend**: Next.js 15/16 + React 19 + TypeScript + Tailwind v4 + Shadcn UI v2.1.8 (Radix UI) + lucide-react + Vitest. **PROHIBIDO**: Shadcn v4/Base-UI (por inestabilidad y falta de soporte actual), HeroUI, Chakra, Material-UI.
99
103
  - **Backend**: Python 3.11+ + FastAPI + Pydantic v2 + Uvicorn + Pytest + Ruff. **PROHIBIDO**: Django, Flask, sync SQLAlchemy.
100
104
  - **Persistencia**: localStorage (frontend-only) o PostgreSQL con pgvector. **PROHIBIDO**: MongoDB (excepto si se justifica), Redis como primary store.
101
105
  - **Containerización**: Docker multi-stage con `node:20-alpine` (Next) o `python:3.11-slim` (FastAPI). Usuario no-root. Healthcheck via `node -e` (no wget en alpine).
@@ -159,6 +159,7 @@ Copia y adapta los IDs y descripciones que apliquen. Borra los que no apliquen.
159
159
  - **NO dupliques los tokens de `.openspec/design-assets/<brandId>/DESIGN.md` línea por línea.** Usa el tool `oh-my-design_get_design_md` y pega el bloque `tokens` completo UNA vez.
160
160
  - **NO escribas más de 5-6 `test_scenarios`** para modo `console`. Más de eso no acelera la entrega, solo retrasa F2.
161
161
  - **NO incluyas escenarios `visual` o `e2e` si `verificationMode` es `console`.** El tester los rechazará.
162
+ - **NO omitas la disposición espacial y propuesta de Layout**: El spec-writer DEBE proponer un bosquejo textual o diagrama ASCII de la disposición espacial de la UI en F1 (ej. layouts con sidebar lateral para configuración o paneles complejos en lugar de simples pestañas tradicionales planas) y acordar anchos de pantalla generosos (`max-w-6xl` o superior, evitando el limitante `max-w-3xl`) con el usuario antes de transicionar a F2. Esto previene reestructuraciones estéticas costosas y asegura entregas de alta gama a la primera.
162
163
 
163
164
  ---
164
165
 
@@ -9,19 +9,19 @@ compatibility: opencode
9
9
 
10
10
  A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
11
11
 
12
- > **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
12
+ > **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@2.1.8`, `pnpm dlx shadcn@2.1.8`, or `bunx --bun shadcn@2.1.8` — based on the project's `packageManager`. Examples below use `npx shadcn@2.1.8` but substitute the correct runner for the project.
13
13
 
14
14
  ## Current Project Context
15
15
 
16
16
  ```json
17
- !`npx shadcn@latest info --json`
17
+ !`npx shadcn@2.1.8 info --json`
18
18
  ```
19
19
 
20
- The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
20
+ The JSON above contains the project config and installed components. Use `npx shadcn@2.1.8 docs <component>` to get documentation and example URLs for any component.
21
21
 
22
22
  ## Principles
23
23
 
24
- 1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
24
+ 1. **Use existing components first.** Use `npx shadcn@2.1.8 search` to check registries before writing custom UI. Check community registries too.
25
25
  2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
26
26
  3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
27
27
  4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
@@ -52,7 +52,7 @@ These rules are **always enforced**. Each links to a file with Incorrect/Correct
52
52
  ### Component Structure → [composition.md](../../docs/shadcn/rules/composition.md)
53
53
 
54
54
  - **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
55
- - **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](../../docs/shadcn/rules/base-vs-radix.md)
55
+ - **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@2.1.8 info`. → [base-vs-radix.md](../../docs/shadcn/rules/base-vs-radix.md)
56
56
  - **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
57
57
  - **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
58
58
  - **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
@@ -77,8 +77,8 @@ These rules are **always enforced**. Each links to a file with Incorrect/Correct
77
77
 
78
78
  ### CLI
79
79
 
80
- - **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.
81
- - **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
80
+ - **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@2.1.8 preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@2.1.8 preset resolve`.
81
+ - **Apply preset codes directly with the CLI.** Use `npx shadcn@2.1.8 apply <code>` for existing projects, or `npx shadcn@2.1.8 init --preset <code>` when initializing.
82
82
 
83
83
  ## Key Patterns
84
84
 
@@ -151,45 +151,44 @@ The injected project context contains these key fields:
151
151
  - **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
152
152
  - **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
153
153
  - **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
154
- - **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.
154
+ - **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@2.1.8 preset resolve --json` when you only need preset information.
155
155
 
156
156
  See [cli.md — `info` command](../../docs/shadcn/cli.md) for the full field reference.
157
157
 
158
158
  ## Component Docs, Examples, and Usage
159
159
 
160
- Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
160
+ DO NOT run bash commands to view docs. Instead, explicitly use the built-in MCP Tools provided to you:
161
+ 1. Use `shadcn_search_items_in_registries` to find components and blocks.
162
+ 2. Use `shadcn_get_item_examples_from_registries` to get the full, actual implementation code, imports, and usage examples of any component or block.
163
+ 3. Use `shadcn_view_items_in_registries` to see the internal source code structure of a registry item.
161
164
 
162
- ```bash
163
- npx shadcn@latest docs button dialog select
164
- ```
165
-
166
- **When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
165
+ **When creating, fixing, debugging, or using a component, always run `shadcn_get_item_examples_from_registries` first.** This ensures you're working with the correct API, composition (e.g. SelectItem inside SelectContent), and usage patterns rather than guessing.
167
166
 
168
167
  ## Workflow
169
168
 
170
- 1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
169
+ 1. **Get project context** — already injected above. Run `npx shadcn@2.1.8 info` again if you need to refresh.
171
170
  2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
172
- 3. **Find components** — `npx shadcn@latest search`.
173
- 4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
174
- 5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
175
- 6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
171
+ 3. **Find components** — `npx shadcn@2.1.8 search`.
172
+ 4. **Get docs and examples** — use the MCP tool `shadcn_get_item_examples_from_registries` with query strings like `'button demo'` or `'login block example'` to see exact implementations. To preview changes to installed components, use `npx shadcn@2.1.8 add --diff`.
173
+ 5. **Install or update** — `npx shadcn@2.1.8 add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
174
+ 6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@2.1.8 info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
176
175
  7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
177
176
  8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
178
177
  9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
179
- - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.
180
- - **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
181
- - **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.
182
- - **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
183
- - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
184
- - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
178
+ - **Inspect current preset**: `npx shadcn@2.1.8 preset resolve`. Use `--json` when you need structured values.
179
+ - **Inspect incoming preset**: `npx shadcn@2.1.8 preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
180
+ - **Overwrite**: `npx shadcn@2.1.8 apply <code>`. Overwrites detected components, fonts, and CSS variables.
181
+ - **Partial**: `npx shadcn@2.1.8 apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
182
+ - **Merge**: `npx shadcn@2.1.8 init --preset <code> --force --no-reinstall`, then run `npx shadcn@2.1.8 info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
183
+ - **Skip**: `npx shadcn@2.1.8 init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
185
184
  - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
186
185
 
187
186
  ## Updating Components
188
187
 
189
188
  When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
190
189
 
191
- 1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
192
- 2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
190
+ 1. Run `npx shadcn@2.1.8 add <component> --dry-run` to see all files that would be affected.
191
+ 2. For each file, run `npx shadcn@2.1.8 add <component> --diff <file>` to see what changed upstream vs local.
193
192
  3. Decide per file based on the diff:
194
193
  - No local changes → safe to overwrite.
195
194
  - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
@@ -200,55 +199,55 @@ When the user asks to update a component from upstream while keeping their local
200
199
 
201
200
  ```bash
202
201
  # Create a new project.
203
- npx shadcn@latest init --name my-app --preset base-nova
204
- npx shadcn@latest init --name my-app --preset a2r6bw --template vite
202
+ npx shadcn@2.1.8 init --name my-app --preset base-nova
203
+ npx shadcn@2.1.8 init --name my-app --preset a2r6bw --template vite
205
204
 
206
205
  # Create a monorepo project.
207
- npx shadcn@latest init --name my-app --preset base-nova --monorepo
208
- npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
206
+ npx shadcn@2.1.8 init --name my-app --preset base-nova --monorepo
207
+ npx shadcn@2.1.8 init --name my-app --preset base-nova --template next --monorepo
209
208
 
210
209
  # Initialize existing project.
211
- npx shadcn@latest init --preset base-nova
212
- npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
210
+ npx shadcn@2.1.8 init --preset base-nova
211
+ npx shadcn@2.1.8 init --defaults # shortcut: --template=next --preset=nova (base style implied)
213
212
 
214
213
  # Apply a preset to an existing project.
215
- npx shadcn@latest apply a2r6bw
216
- npx shadcn@latest apply a2r6bw --only theme
217
- npx shadcn@latest apply a2r6bw --only font
218
- npx shadcn@latest apply a2r6bw --only theme,font
214
+ npx shadcn@2.1.8 apply a2r6bw
215
+ npx shadcn@2.1.8 apply a2r6bw --only theme
216
+ npx shadcn@2.1.8 apply a2r6bw --only font
217
+ npx shadcn@2.1.8 apply a2r6bw --only theme,font
219
218
 
220
219
  # Inspect preset codes and project preset state.
221
- npx shadcn@latest preset decode a2r6bw
222
- npx shadcn@latest preset url a2r6bw
223
- npx shadcn@latest preset open a2r6bw
224
- npx shadcn@latest preset resolve
225
- npx shadcn@latest preset resolve --json
220
+ npx shadcn@2.1.8 preset decode a2r6bw
221
+ npx shadcn@2.1.8 preset url a2r6bw
222
+ npx shadcn@2.1.8 preset open a2r6bw
223
+ npx shadcn@2.1.8 preset resolve
224
+ npx shadcn@2.1.8 preset resolve --json
226
225
 
227
226
  # Add components.
228
- npx shadcn@latest add button card dialog
229
- npx shadcn@latest add @magicui/shimmer-button
230
- npx shadcn@latest add owner/repo/item
231
- npx shadcn@latest add --all
227
+ npx shadcn@2.1.8 add button card dialog
228
+ npx shadcn@2.1.8 add @magicui/shimmer-button
229
+ npx shadcn@2.1.8 add owner/repo/item
230
+ npx shadcn@2.1.8 add --all
232
231
 
233
232
  # Preview changes before adding/updating.
234
- npx shadcn@latest add button --dry-run
235
- npx shadcn@latest add button --diff button.tsx
236
- npx shadcn@latest add @acme/form --view button.tsx
237
- npx shadcn@latest add owner/repo/item --dry-run
233
+ npx shadcn@2.1.8 add button --dry-run
234
+ npx shadcn@2.1.8 add button --diff button.tsx
235
+ npx shadcn@2.1.8 add @acme/form --view button.tsx
236
+ npx shadcn@2.1.8 add owner/repo/item --dry-run
238
237
 
239
238
  # Search registries.
240
- npx shadcn@latest search @shadcn -q "sidebar"
241
- npx shadcn@latest search @tailark -q "stats"
242
- npx shadcn@latest search owner/repo -q "login"
243
- npx shadcn@latest search # all configured registries
244
- npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type
239
+ npx shadcn@2.1.8 search @shadcn -q "sidebar"
240
+ npx shadcn@2.1.8 search @tailark -q "stats"
241
+ npx shadcn@2.1.8 search owner/repo -q "login"
242
+ npx shadcn@2.1.8 search # all configured registries
243
+ npx shadcn@2.1.8 search @shadcn -q "menu" -t ui # filter by item type
245
244
 
246
245
  # Get component docs and example URLs.
247
- npx shadcn@latest docs button dialog select
246
+ # ALWAYS use your MCP Tools (shadcn_get_item_examples_from_registries) instead of bash for docs.
248
247
 
249
248
  # View registry item details (for items not yet installed).
250
- npx shadcn@latest view @shadcn/button
251
- npx shadcn@latest view owner/repo/item
249
+ npx shadcn@2.1.8 view @shadcn/button
250
+ npx shadcn@2.1.8 view owner/repo/item
252
251
  ```
253
252
 
254
253
  **Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
@@ -1,17 +1,15 @@
1
1
  {
2
2
  "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "base-nova",
3
+ "style": "new-york",
4
4
  "rsc": true,
5
5
  "tsx": true,
6
6
  "tailwind": {
7
- "config": "",
7
+ "config": "tailwind.config.ts",
8
8
  "css": "src/app/globals.css",
9
- "baseColor": "neutral",
9
+ "baseColor": "zinc",
10
10
  "cssVariables": true,
11
11
  "prefix": ""
12
12
  },
13
- "iconLibrary": "lucide",
14
- "rtl": false,
15
13
  "aliases": {
16
14
  "components": "@/components",
17
15
  "utils": "@/lib/utils",
@@ -19,7 +17,5 @@
19
17
  "lib": "@/lib",
20
18
  "hooks": "@/hooks"
21
19
  },
22
- "menuColor": "default",
23
- "menuAccent": "subtle",
24
- "registries": {}
20
+ "iconLibrary": "lucide"
25
21
  }
@@ -297,7 +297,7 @@ export const bootstrap_nextjs_shadcn = tool({
297
297
  let componentsError: string | null = null
298
298
  if (args.components && args.components.length > 0) {
299
299
  try {
300
- const cmd = `npx shadcn@latest add ${args.components.join(" ")} --yes --overwrite`
300
+ const cmd = `npx shadcn@2.1.8 add ${args.components.join(" ")} --yes --overwrite`
301
301
  execSync(cmd, {
302
302
  cwd: targetPath,
303
303
  stdio: "ignore",
@@ -11,6 +11,27 @@ const getRoot = (context: any) => {
11
11
  return process.cwd();
12
12
  };
13
13
 
14
+ // Helper to read the targetDir from sdd_state.json or .sdd_bootstrap.json
15
+ const getTargetDir = (root: string): string => {
16
+ try {
17
+ const bootstrapPath = path.resolve(root, ".openspec/.sdd_bootstrap.json")
18
+ if (fs.existsSync(bootstrapPath)) {
19
+ const data = JSON.parse(fs.readFileSync(bootstrapPath, "utf8"))
20
+ if (data && data.targetDir) return data.targetDir
21
+ }
22
+ } catch (e) {}
23
+
24
+ try {
25
+ const statePath = path.resolve(root, ".openspec/sdd_state.json")
26
+ if (fs.existsSync(statePath)) {
27
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"))
28
+ if (state && state.targetDir) return state.targetDir
29
+ }
30
+ } catch (e) {}
31
+
32
+ return "."
33
+ }
34
+
14
35
  // Tool: sdd_clean_docker_environment
15
36
  export const clean_docker_environment = tool({
16
37
  description: "Asegura que Docker esté abierto y realiza una limpieza total y agresiva: detiene y elimina TODOS los contenedores (activos o inactivos), remueve TODAS las imágenes, volúmenes y redes para garantizar un lienzo en blanco absoluto antes de desplegar.",
@@ -100,6 +121,8 @@ export const generate_dockerfile = tool({
100
121
  },
101
122
  async execute(args, context) {
102
123
  const root = getRoot(context)
124
+ const targetDir = getTargetDir(root)
125
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
103
126
 
104
127
  if (args.stack === "agnostic") {
105
128
  const dockerfileAg = `FROM node:20-alpine
@@ -111,7 +134,7 @@ CMD ["node", "src/index.js"]
111
134
  const filesWritten: string[] = []
112
135
  let content = dockerfileAg
113
136
  let targetName = "src/index.js"
114
- if (fs.existsSync(path.resolve(root, "src/main.py"))) {
137
+ if (fs.existsSync(path.resolve(targetPath, "src/main.py"))) {
115
138
  content = `FROM python:3.11-slim
116
139
  WORKDIR /app
117
140
  COPY . .
@@ -121,9 +144,9 @@ CMD ["python", "src/main.py"]
121
144
  targetName = "src/main.py"
122
145
  }
123
146
 
124
- const fullPath = path.resolve(root, "Dockerfile")
147
+ const fullPath = path.resolve(targetPath, "Dockerfile")
125
148
  fs.writeFileSync(fullPath, content, "utf8")
126
- filesWritten.push("Dockerfile")
149
+ filesWritten.push(path.relative(root, fullPath))
127
150
 
128
151
  return JSON.stringify({
129
152
  status: "SUCCESS",
@@ -140,11 +163,11 @@ CMD ["python", "src/main.py"]
140
163
  let pm = "npm"
141
164
  let installCmd = "npm ci --frozen-lockfile"
142
165
  let buildCmd = "npm run build"
143
- if (fs.existsSync(path.resolve(root, "pnpm-lock.yaml"))) {
166
+ if (fs.existsSync(path.resolve(targetPath, "pnpm-lock.yaml"))) {
144
167
  pm = "pnpm"
145
168
  installCmd = "pnpm install --frozen-lockfile"
146
169
  buildCmd = "pnpm build"
147
- } else if (fs.existsSync(path.resolve(root, "yarn.lock"))) {
170
+ } else if (fs.existsSync(path.resolve(targetPath, "yarn.lock"))) {
148
171
  pm = "yarn"
149
172
  installCmd = "yarn install --frozen-lockfile"
150
173
  buildCmd = "yarn build"
@@ -231,7 +254,7 @@ test-results
231
254
  [".dockerignore", dockerignore],
232
255
  ["docker-compose.yml", compose],
233
256
  ] as const) {
234
- const fullPath = path.resolve(root, name)
257
+ const fullPath = path.resolve(targetPath, name)
235
258
  fs.writeFileSync(fullPath, content, "utf8")
236
259
  filesWritten.push(path.relative(root, fullPath))
237
260
  }
@@ -288,7 +311,7 @@ tests/
288
311
  [".dockerignore", dockerignorePy],
289
312
  ["docker-compose.yml", composePy],
290
313
  ] as const) {
291
- const fullPath = path.resolve(root, name)
314
+ const fullPath = path.resolve(targetPath, name)
292
315
  fs.writeFileSync(fullPath, content, "utf8")
293
316
  filesWritten.push(path.relative(root, fullPath))
294
317
  }
@@ -11,6 +11,27 @@ const getRoot = (context: any) => {
11
11
  return process.cwd();
12
12
  };
13
13
 
14
+ // Helper to read the targetDir from sdd_state.json or .sdd_bootstrap.json
15
+ const getTargetDir = (root: string): string => {
16
+ try {
17
+ const bootstrapPath = path.resolve(root, ".openspec/.sdd_bootstrap.json")
18
+ if (fs.existsSync(bootstrapPath)) {
19
+ const data = JSON.parse(fs.readFileSync(bootstrapPath, "utf8"))
20
+ if (data && data.targetDir) return data.targetDir
21
+ }
22
+ } catch (e) {}
23
+
24
+ try {
25
+ const statePath = path.resolve(root, ".openspec/sdd_state.json")
26
+ if (fs.existsSync(statePath)) {
27
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"))
28
+ if (state && state.targetDir) return state.targetDir
29
+ }
30
+ } catch (e) {}
31
+
32
+ return "."
33
+ }
34
+
14
35
  // Helper to get PID file path
15
36
  const getPidFilePath = (root: string) => {
16
37
  return path.resolve(root, ".openspec/dev_server.pid")
@@ -56,7 +77,8 @@ export const start_server = tool({
56
77
  },
57
78
  async execute(args, context) {
58
79
  const root = getRoot(context)
59
- const targetCwd = args.cwd ? path.resolve(root, args.cwd) : root
80
+ const targetDir = getTargetDir(root)
81
+ const targetCwd = args.cwd ? path.resolve(root, args.cwd) : (targetDir === "." ? root : path.resolve(root, targetDir))
60
82
  const pidFile = getPidFilePath(root)
61
83
 
62
84
  try {
@@ -11,6 +11,27 @@ const getRoot = (context: any) => {
11
11
  return process.cwd();
12
12
  };
13
13
 
14
+ // Helper to read the targetDir from sdd_state.json or .sdd_bootstrap.json
15
+ const getTargetDir = (root: string): string => {
16
+ try {
17
+ const bootstrapPath = path.resolve(root, ".openspec/.sdd_bootstrap.json")
18
+ if (fs.existsSync(bootstrapPath)) {
19
+ const data = JSON.parse(fs.readFileSync(bootstrapPath, "utf8"))
20
+ if (data && data.targetDir) return data.targetDir
21
+ }
22
+ } catch (e) {}
23
+
24
+ try {
25
+ const statePath = path.resolve(root, ".openspec/sdd_state.json")
26
+ if (fs.existsSync(statePath)) {
27
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"))
28
+ if (state && state.targetDir) return state.targetDir
29
+ }
30
+ } catch (e) {}
31
+
32
+ return "."
33
+ }
34
+
14
35
  // Helper to parse semantic errors from compiler and linter outputs (reducing raw trace log bloat for the LLM)
15
36
  const parseSemanticErrors = (rawOutput: string, type: "eslint" | "tsc"): any[] => {
16
37
  const parsed: any[] = []
@@ -54,8 +75,10 @@ export const quick_lint = tool({
54
75
  args: {},
55
76
  async execute(args, context) {
56
77
  const root = getRoot(context)
78
+ const targetDir = getTargetDir(root)
79
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
57
80
 
58
- const pkgPath = path.resolve(root, "package.json")
81
+ const pkgPath = path.resolve(targetPath, "package.json")
59
82
  if (!fs.existsSync(pkgPath)) {
60
83
  return JSON.stringify({
61
84
  status: "ERROR",
@@ -63,6 +86,13 @@ export const quick_lint = tool({
63
86
  }, null, 2)
64
87
  }
65
88
 
89
+ const getRelPath = (fullRel: string) => {
90
+ if (targetDir !== "." && fullRel.startsWith(targetDir + "/")) {
91
+ return fullRel.slice(targetDir.length + 1)
92
+ }
93
+ return fullRel
94
+ }
95
+
66
96
  let eslintFiles = "src/"
67
97
  try {
68
98
  const stateFile = path.resolve(root, ".openspec/sdd_state.json")
@@ -73,7 +103,9 @@ export const quick_lint = tool({
73
103
  if (fs.existsSync(contractPath)) {
74
104
  const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"))
75
105
  if (contract && Array.isArray(contract.files_affected) && contract.files_affected.length > 0) {
76
- const existingFiles = contract.files_affected.filter((f: string) => fs.existsSync(path.resolve(root, f)))
106
+ const existingFiles = contract.files_affected
107
+ .filter((f: string) => fs.existsSync(path.resolve(root, f)))
108
+ .map((f: string) => getRelPath(f))
77
109
  if (existingFiles.length > 0) {
78
110
  eslintFiles = existingFiles.join(" ")
79
111
  }
@@ -87,7 +119,7 @@ export const quick_lint = tool({
87
119
 
88
120
  try {
89
121
  const out = execSync(`npx eslint ${eslintFiles} --quiet --max-warnings 0 2>&1 || true`, {
90
- cwd: root,
122
+ cwd: targetPath,
91
123
  encoding: "utf8",
92
124
  timeout: 120_000,
93
125
  })
@@ -112,9 +144,11 @@ export const shift_left_verify = tool({
112
144
  args: {},
113
145
  async execute(args, context) {
114
146
  const root = getRoot(context)
147
+ const targetDir = getTargetDir(root)
148
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
115
149
 
116
150
  // Check if it is a Python project
117
- let isPython = fs.existsSync(path.resolve(root, "requirements.txt")) || fs.existsSync(path.resolve(root, "pyproject.toml"));
151
+ let isPython = fs.existsSync(path.resolve(targetPath, "requirements.txt")) || fs.existsSync(path.resolve(targetPath, "pyproject.toml"));
118
152
  try {
119
153
  const stateFile = path.resolve(root, ".openspec/sdd_state.json")
120
154
  if (fs.existsSync(stateFile)) {
@@ -133,6 +167,13 @@ export const shift_left_verify = tool({
133
167
  // ignore
134
168
  }
135
169
 
170
+ const getRelPath = (fullRel: string) => {
171
+ if (targetDir !== "." && fullRel.startsWith(targetDir + "/")) {
172
+ return fullRel.slice(targetDir.length + 1)
173
+ }
174
+ return fullRel
175
+ }
176
+
136
177
  if (isPython) {
137
178
  const resultPython: { ruff: { status: string, errors?: any[] } } = {
138
179
  ruff: { status: "SUCCESS" }
@@ -147,7 +188,9 @@ export const shift_left_verify = tool({
147
188
  if (fs.existsSync(contractPath)) {
148
189
  const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"))
149
190
  if (contract && Array.isArray(contract.files_affected) && contract.files_affected.length > 0) {
150
- const existingFiles = contract.files_affected.filter((f: string) => fs.existsSync(path.resolve(root, f)) && f.endsWith(".py"))
191
+ const existingFiles = contract.files_affected
192
+ .filter((f: string) => fs.existsSync(path.resolve(root, f)) && f.endsWith(".py"))
193
+ .map((f: string) => getRelPath(f))
151
194
  if (existingFiles.length > 0) {
152
195
  pythonFiles = existingFiles.join(" ")
153
196
  }
@@ -157,7 +200,7 @@ export const shift_left_verify = tool({
157
200
  }
158
201
 
159
202
  try {
160
- execSync(`ruff check ${pythonFiles} --quiet`, { cwd: root, stdio: "pipe", timeout: 20000 })
203
+ execSync(`ruff check ${pythonFiles} --quiet`, { cwd: targetPath, stdio: "pipe", timeout: 20000 })
161
204
  } catch (ruffErr: any) {
162
205
  const output = ruffErr.stdout?.toString() || ruffErr.stderr?.toString() || ""
163
206
  if (output.includes("command not found") || ruffErr.message?.includes("ENOENT") || ruffErr.status === 127) {
@@ -212,7 +255,7 @@ export const shift_left_verify = tool({
212
255
 
213
256
  // 1. Run tsc --noEmit
214
257
  try {
215
- execSync("npx tsc --noEmit", { cwd: root, stdio: "pipe", timeout: 60000 })
258
+ execSync("npx tsc --noEmit", { cwd: targetPath, stdio: "pipe", timeout: 60000 })
216
259
  } catch (e: any) {
217
260
  const rawOutput = e.stdout?.toString() || e.stderr?.toString() || ""
218
261
  const errors = parseSemanticErrors(rawOutput, "tsc")
@@ -233,7 +276,9 @@ export const shift_left_verify = tool({
233
276
  if (fs.existsSync(contractPath)) {
234
277
  const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"))
235
278
  if (contract && Array.isArray(contract.files_affected) && contract.files_affected.length > 0) {
236
- const existingFiles = contract.files_affected.filter((f: string) => fs.existsSync(path.resolve(root, f)))
279
+ const existingFiles = contract.files_affected
280
+ .filter((f: string) => fs.existsSync(path.resolve(root, f)))
281
+ .map((f: string) => getRelPath(f))
237
282
  if (existingFiles.length > 0) {
238
283
  eslintFiles = existingFiles.join(" ")
239
284
  }
@@ -247,7 +292,7 @@ export const shift_left_verify = tool({
247
292
 
248
293
  try {
249
294
  const out = execSync(`npx eslint ${eslintFiles} --quiet 2>&1 || true`, {
250
- cwd: root,
295
+ cwd: targetPath,
251
296
  encoding: "utf8",
252
297
  timeout: 60000,
253
298
  })
@@ -278,10 +323,13 @@ export const shift_left_verify = tool({
278
323
 
279
324
  // Tool: sdd_generate_tests
280
325
  export const generate_tests = tool({
281
- description: "Autogenera plantillas de pruebas unitarias/integración en tests/unit/ a partir de los escenarios de prueba descritos en el contrato activo de sdd_state.json. No pisa archivos de pruebas existentes.",
326
+ description: "Autogenera plantillas de pruebas unitarias/integración en el directorio corporativo de pruebas a partir de los escenarios de prueba descritos en el contrato activo de sdd_state.json. No pisa archivos de pruebas existentes.",
282
327
  args: {},
283
328
  async execute(args, context) {
284
329
  const root = getRoot(context)
330
+ const targetDir = getTargetDir(root)
331
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
332
+
285
333
  const stateFile = path.resolve(root, ".openspec/sdd_state.json")
286
334
  if (!fs.existsSync(stateFile)) {
287
335
  return JSON.stringify({ success: false, error: "sdd_state.json no existe. Inicia una sesión SDD primero." }, null, 2)
@@ -311,48 +359,76 @@ export const generate_tests = tool({
311
359
  const created: string[] = []
312
360
  const skipped: string[] = []
313
361
 
362
+ const isPythonProject = fs.existsSync(path.resolve(targetPath, "requirements.txt")) || fs.existsSync(path.resolve(targetPath, "pyproject.toml"))
363
+ const test_dir = isPythonProject
364
+ ? path.resolve(targetPath, "tests/unit")
365
+ : path.resolve(targetPath, "src/__tests__")
366
+
367
+ if (!fs.existsSync(test_dir)) {
368
+ fs.mkdirSync(test_dir, { recursive: true })
369
+ }
370
+
314
371
  for (const [feature, scenarios] of Object.entries(grouped_tests)) {
315
372
  const clean_feature = feature.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "")
316
373
  const hasReact = scenarios.some(s => s.type === "unit" || s.type === "visual")
317
374
  const ext = hasReact ? "tsx" : "ts"
318
- const test_dir = path.resolve(root, "tests", "unit")
319
- if (!fs.existsSync(test_dir)) {
320
- fs.mkdirSync(test_dir, { recursive: true })
321
- }
322
- const test_file = path.join(test_dir, `${clean_feature}.test.${ext}`)
375
+
376
+ const filename = isPythonProject
377
+ ? `test_${clean_feature}.py`
378
+ : `${feature}.test.${ext}`
379
+ const test_file = path.join(test_dir, filename)
380
+ const relative_test_path = path.relative(root, test_file)
323
381
 
324
382
  if (fs.existsSync(test_file)) {
325
- skipped.push(`tests/unit/${clean_feature}.test.${ext}`)
383
+ skipped.push(relative_test_path)
326
384
  continue
327
385
  }
328
386
 
329
387
  const lines: string[] = []
330
- lines.push('import { describe, it, expect } from "vitest";')
331
- if (hasReact) {
332
- lines.push('import { render, screen } from "@testing-library/react";')
333
- lines.push('import userEvent from "@testing-library/user-event";')
334
- lines.push(`// import ${feature} from "@/components/blocks/${clean_feature}";`)
335
- }
336
- lines.push("")
337
- lines.push(`describe("${feature} Tests (Contract Scenarios)", () => {`)
338
-
339
- for (const s of scenarios) {
340
- const tid = s.id || "TS-XX"
341
- const name = s.name || "Test case"
342
- lines.push(` // ${tid}: ${name}`)
343
- lines.push(` // Given: ${s.given || ""}`)
344
- lines.push(` // When: ${s.when || ""}`)
345
- lines.push(` // Then: ${s.then || ""}`)
346
- lines.push(` it("${tid}: ${name}", async () => {`)
347
- lines.push(' // TODO: Implement actual contract assertions')
348
- lines.push(' expect(true).toBe(true);')
349
- lines.push(' });')
388
+ if (isPythonProject) {
389
+ lines.push("import pytest")
390
+ lines.push(`from src.main import app # o tu router correspondiente`)
350
391
  lines.push("")
392
+ lines.push(`# tests para ${feature}`)
393
+ for (const s of scenarios) {
394
+ const tid = s.id || "TS-XX"
395
+ const name = s.name || "Test case"
396
+ const test_func_name = `test_${tid.toLowerCase().replace(/-/g, "_")}_${clean_feature.replace(/-/g, "_")}`
397
+ lines.push(`def ${test_func_name}():`)
398
+ lines.push(` # Given: ${s.given || ""}`)
399
+ lines.push(` # When: ${s.when || ""}`)
400
+ lines.push(` # Then: ${s.then || ""}`)
401
+ lines.push(" assert True")
402
+ lines.push("")
403
+ }
404
+ } else {
405
+ lines.push('import { describe, it, expect } from "vitest";')
406
+ if (hasReact) {
407
+ lines.push('import { render, screen } from "@testing-library/react";')
408
+ lines.push('import userEvent from "@testing-library/user-event";')
409
+ lines.push(`// import { ${feature} } from "@/components/blocks/${feature}";`)
410
+ }
411
+ lines.push("")
412
+ lines.push(`describe("${feature} Tests (Contract Scenarios)", () => {`)
413
+
414
+ for (const s of scenarios) {
415
+ const tid = s.id || "TS-XX"
416
+ const name = s.name || "Test case"
417
+ lines.push(` // ${tid}: ${name}`)
418
+ lines.push(` // Given: ${s.given || ""}`)
419
+ lines.push(` // When: ${s.when || ""}`)
420
+ lines.push(` // Then: ${s.then || ""}`)
421
+ lines.push(` it("${tid}: ${name}", async () => {`)
422
+ lines.push(' // TODO: Implement actual contract assertions')
423
+ lines.push(' expect(true).toBe(true);')
424
+ lines.push(' });')
425
+ lines.push("")
426
+ }
427
+ lines.push("});")
351
428
  }
352
- lines.push("});")
353
429
 
354
430
  fs.writeFileSync(test_file, lines.join("\n").trim() + "\n", "utf8")
355
- created.push(`tests/unit/${clean_feature}.test.${ext}`)
431
+ created.push(relative_test_path)
356
432
  }
357
433
 
358
434
  return JSON.stringify({
@@ -9,7 +9,7 @@ DB_PATH = "/Users/wavesbyte/.local/share/opencode/opencode.db"
9
9
 
10
10
  # Escribe aquí el ID de la sesión que deseas exportar (ejemplo: "ses_1234...")
11
11
  # Si se deja vacío, el script requerirá el ID como argumento al ejecutarlo
12
- TARGET_SESSION_ID = "ses_11e1"
12
+ TARGET_SESSION_ID = "ses_11d2"
13
13
 
14
14
 
15
15
  def format_timestamp(ts):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "Fácil instalador del arnés SDD de Zugzbot para proyectos OpenCode",
5
5
  "type": "module",
6
6
  "bin": {