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.
- package/.opencode/agents/sdd-coder.md +8 -5
- package/.opencode/agents/sdd-deployer.md +1 -0
- package/.opencode/agents/sdd-orchestrator.md +5 -3
- package/.opencode/agents/sdd-reviewer.md +1 -0
- package/.opencode/agents/sdd-spec-writer.md +1 -0
- package/.opencode/agents/sdd-tester.md +3 -1
- package/.opencode/rules/sdd-global.md +20 -0
- package/.opencode/skills/sdd-methodology/SKILL.md +5 -1
- package/.opencode/skills/sdd-quickstart/SKILL.md +1 -0
- package/.opencode/skills/shadcn/SKILL.md +57 -58
- package/.opencode/templates/nextjs-shadcn/components.json +4 -8
- package/.opencode/tools/sdd_bootstrap.ts +1 -1
- package/.opencode/tools/sdd_docker.ts +30 -7
- package/.opencode/tools/sdd_network.ts +23 -1
- package/.opencode/tools/sdd_testing.ts +114 -38
- package/.utils/export_opencode_session.py +1 -1
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ tools:
|
|
|
11
11
|
write: true
|
|
12
12
|
edit: true
|
|
13
13
|
bash: true
|
|
14
|
-
todowrite:
|
|
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@
|
|
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
|
|
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,
|
|
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 ' ')
|
|
@@ -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
|
|
67
|
-
- *Instrucción clave:*
|
|
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):**
|
|
@@ -11,7 +11,7 @@ tools:
|
|
|
11
11
|
write: true
|
|
12
12
|
edit: true
|
|
13
13
|
bash: true
|
|
14
|
-
todowrite:
|
|
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
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
81
|
-
- **Apply preset codes directly with the CLI.** Use `npx shadcn@
|
|
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@
|
|
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
|
-
|
|
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
|
-
|
|
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@
|
|
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@
|
|
173
|
-
4. **Get docs and examples** —
|
|
174
|
-
5. **Install or update** — `npx shadcn@
|
|
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@
|
|
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@
|
|
180
|
-
- **Inspect incoming preset**: `npx shadcn@
|
|
181
|
-
- **Overwrite**: `npx shadcn@
|
|
182
|
-
- **Partial**: `npx shadcn@
|
|
183
|
-
- **Merge**: `npx shadcn@
|
|
184
|
-
- **Skip**: `npx shadcn@
|
|
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@
|
|
192
|
-
2. For each file, run `npx shadcn@
|
|
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@
|
|
204
|
-
npx shadcn@
|
|
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@
|
|
208
|
-
npx shadcn@
|
|
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@
|
|
212
|
-
npx shadcn@
|
|
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@
|
|
216
|
-
npx shadcn@
|
|
217
|
-
npx shadcn@
|
|
218
|
-
npx shadcn@
|
|
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@
|
|
222
|
-
npx shadcn@
|
|
223
|
-
npx shadcn@
|
|
224
|
-
npx shadcn@
|
|
225
|
-
npx shadcn@
|
|
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@
|
|
229
|
-
npx shadcn@
|
|
230
|
-
npx shadcn@
|
|
231
|
-
npx shadcn@
|
|
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@
|
|
235
|
-
npx shadcn@
|
|
236
|
-
npx shadcn@
|
|
237
|
-
npx shadcn@
|
|
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@
|
|
241
|
-
npx shadcn@
|
|
242
|
-
npx shadcn@
|
|
243
|
-
npx shadcn@
|
|
244
|
-
npx shadcn@
|
|
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
|
-
|
|
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@
|
|
251
|
-
npx shadcn@
|
|
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": "
|
|
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": "
|
|
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
|
-
"
|
|
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@
|
|
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(
|
|
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(
|
|
147
|
+
const fullPath = path.resolve(targetPath, "Dockerfile")
|
|
125
148
|
fs.writeFileSync(fullPath, content, "utf8")
|
|
126
|
-
filesWritten.push(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const test_file = path.join(test_dir,
|
|
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(
|
|
383
|
+
skipped.push(relative_test_path)
|
|
326
384
|
continue
|
|
327
385
|
}
|
|
328
386
|
|
|
329
387
|
const lines: string[] = []
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
lines.push(
|
|
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(
|
|
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 = "
|
|
12
|
+
TARGET_SESSION_ID = "ses_11d2"
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def format_timestamp(ts):
|