zugzbot 1.0.35 → 1.0.36

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.
@@ -88,7 +88,7 @@ Eres el Programador de Código (sdd-coder) del arnés SDD. Tu trabajo es codific
88
88
  </coding_cheatsheet>
89
89
 
90
90
  <design_standards>
91
- - **Alineación con DESIGN.md**: Lee obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` y asimila los layouts de los archivos HTML interactivos (`preview.html` / `preview-dark.html`) para recrear diseños profesionales premium con grids, sidebars y headers. Quedan prohibidos los MVPs de página flotante.
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.
92
92
  - **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
93
  ```bash
94
94
  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 ' ')
@@ -31,7 +31,7 @@ Eres el Diseñador de Contratos (sdd-spec-writer) del flujo SDD. Tu único traba
31
31
  El JSON generado en `contract.json` debe cumplir estrictamente con `.opencode/contract-schema.json`. Asegúrate de definir detalladamente:
32
32
  - `contractName` & `description`: Nombre y alcance claro del spec.
33
33
  - `settings`: Configuración clave como `verificationMode: "visual" | "console"`, lenguaje y persistencia.
34
- - `files_affected`: Array obligatorio que define de forma explícita las rutas exactas de los archivos que serán modificados o creados (ej. `["src/components/layout/AppLayout.tsx", "src/components/blocks/SumadoraPanel.tsx", "src/app/page.tsx"]`). Esto es CRÍTICO para permitir la política de Zero-Search y la verificación dirigida de compilación, linter y testing.
34
+ - `files_affected`: Array obligatorio que define de forma explícita las rutas exactas de los archivos que serán modificados o creados (ej. `["src/components/layout/AppLayout.tsx", "src/components/blocks/SumadoraPanel.tsx", "src/app/page.tsx"]`). Si el proyecto vive en un subdirectorio (ej. `next-app` o `frontend`), las rutas **deben** ir obligatoriamente con el prefijo de dicho subdirectorio (ej. `["next-app/src/app/page.tsx"]`). Esto es CRÍTICO para permitir la política de Zero-Search y la verificación dirigida de compilación, linter y testing.
35
35
  - `stack`: El framework base, bases de datos y librerías clave.
36
36
  - `frontend` / `backend` / `database`: Componentes locales, props, state y endpoints de API. (Usa aliases locales como `@/components/ui/button` para components de Shadcn).
37
37
  - `stateFlow`: Owner de estado centralizado, props hacia abajo y transiciones prohibidas (`forbidden[]`).
@@ -54,7 +54,7 @@ Extrae los tokens canónicos (paleta de colores, escala de bordes, tipografía,
54
54
 
55
55
  <design_standards>
56
56
  - **Alineación con DESIGN.md**: Debes leer obligatoriamente `.openspec/design-assets/<brandId>/DESIGN.md` para extraer los tokens de diseño. Basándote además en los ejemplos HTML interactivos (`preview.html`, `preview-dark.html`) correspondientes de la marca, define en el contrato los layouts exactos (sidebars, grids, headers) a implementar.
57
- - **Diseño Premium**: El contrato debe instruir el uso del skill `shadcn-templates` (Sección 3.5) alineado a la guía del `DESIGN.md`. Quedan prohibidos los MVPs de pantalla única flotante.
57
+ - **Diseño Premium y Bloques Shadcn**: El contrato debe instruir el uso de bloques prehechos oficiales de Shadcn (`dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`, etc.) mediante el skill `shadcn-templates` (Sección 3.1) alineado a la guía del `DESIGN.md`. Quedan prohibidos los MVPs de pantalla única flotante. El spec-writer debe usar proactivamente las herramientas MCP (`shadcn_search_items_in_registries`, `shadcn_list_items_in_registries`) para identificar qué bloques de tipo `registry:block` o `registry:ui` se adecuan a la necesidad y declararlos en `frontend.components` y `files_affected`.
58
58
  - **Escenarios de Test**:
59
59
  - Si `verificationMode === "console"`, genera únicamente escenarios de tipo `unit` o `integration` (entre 3 y 5 escenarios en total). No generes escenarios de tipo `visual` ni tests basados en browser.
60
60
  - Si `verificationMode === "visual"`, incluye los escenarios visuales necesarios con selectores CSS reales.
@@ -21,3 +21,16 @@ Cuando el estado del sistema tiene `loopMode === true` (activado mediante el com
21
21
  - **Intercepción de Barrera**: Si algún agente intenta llamar a la herramienta `question` en este modo, el sistema abortará la llamada mecánicamente a través del plugin enforcer.
22
22
  - **Specs Incrementales**: Si la petición es grande, el orquestador debe dividirla secuencialmente en contratos incrementales. Completa un spec por completo (F0->F4) y arranca el siguiente de inmediato de forma autónoma hasta finalizar todo el plan.
23
23
  - **Rollback de Emergencia**: Si una misma fase falla de forma continua (más de 2 rollbacks), desactiva `loopMode` usando `sdd_set_phase` y solicita asistencia humana en el chat.
24
+
25
+ ## 3. Uso Mandatorio de Bloques Prehechos de Shadcn UI (dashboard, sidebar, login, etc.)
26
+ Para optimizar el tiempo de desarrollo, garantizar una experiencia visual premium y evitar la invención de layouts o formularios comunes desde cero:
27
+ - **Prioridad de Bloques**: Al diseñar cualquier interfaz de usuario (Layouts, Dashboards, Paneles de Control, Vistas de Configuración, Páginas de Autenticación, Tablas de Datos, Gráficos), los agentes **deben** buscar y utilizar de forma prioritaria los bloques prehechos del registro oficial `@shadcn` (como `dashboard-01`, `sidebar-01` al `16`, `login-01` al `05`, `signup-01` al `05`).
28
+ - **Búsqueda Proactiva**: El Spec-Writer **debe** consultar el registro utilizando las herramientas del MCP `shadcn` (`shadcn_search_items_in_registries`, `shadcn_list_items_in_registries`) al redactar el contrato (F1) para identificar qué bloques se ajustan a la necesidad, y declararlos en `frontend.components` y `files_affected` de `contract.json`.
29
+ - **Implementación por Distribución y Composición**: El Coder (F2) **debe** instalar estos bloques usando `npx shadcn@latest add <block-name>` (ej: `dashboard-01`, `login-03`) en su proyecto, integrando y distribuyendo los elementos con el estado y lógica del backend, sin rediseñar o codificar layouts que ya existen de forma oficial.
30
+
31
+ ## 4. Soporte para Subdirectorios, Monorepos (targetDir) y Estándar Corporativo `src/`
32
+ Si el proyecto o aplicación principal vive en un subdirectorio (ej. `next-app`, `frontend`, `backend`), se deben seguir estas directivas estrictas para mantener la consistencia y el "estándar corporativo con carpeta `src/`":
33
+ - **Estructura del Proyecto**: Toda la estructura de código fuente, páginas, componentes, estilos, pruebas y configuraciones debe vivir agrupada dentro de la subcarpeta del proyecto y bajo la carpeta `src/` obligatoriamente (ej. `next-app/src/app/...`, `next-app/src/components/...`, `next-app/tsconfig.json`, `next-app/package.json`).
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
+ - **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
+ - **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.
@@ -72,8 +72,14 @@ npx shadcn@latest add <component-name>
72
72
 
73
73
  Al diseñar o codificar páginas con Shadcn UI, sigue estas reglas estrictas:
74
74
 
75
- ### 3.1 Componer, no reinventar
76
- Utiliza las plantillas y bloques existentes de Shadcn (como `dashboard-01`, `settings-form`, `login-01`) como punto de partida. Busca layouts de dashboards o formularios complejos en el registro mediante el MCP `shadcn` antes de escribir código personalizado.
75
+ ### 3.1 Componer, no reinventar (MANDATORIO)
76
+ Utiliza las plantillas y bloques existentes de Shadcn como el estándar absoluto de tu interfaz. Queda **estrictamente prohibido** escribir layouts, páginas de autenticación o cuadros de mando desde cero si ya existe un bloque oficial que resuelva el caso de uso:
77
+ - **Dashboards**: Utiliza `dashboard-01` como base para cualquier panel de control interactivo.
78
+ - **Sidebars / Barras de Navegación**: Utiliza `sidebar-01` al `16` para paneles laterales funcionales, colapsables, dinámicos o anidados. No programes componentes de navegación complejos a mano.
79
+ - **Login / Registro**: Utiliza `login-01` al `05` y `signup-01` al `05` para flujos de login y registro de usuarios.
80
+ - **Gráficos y Métricas**: Busca y añade bloques de gráficos oficiales de Recharts (`chart-area-*`, `chart-bar-*`, `chart-pie-*`, `chart-radar-*`) para dar vida a la visualización de datos.
81
+
82
+ Busca layouts de dashboards o formularios complejos en el registro mediante el MCP `shadcn` antes de escribir cualquier código personalizado. Una vez agregado el bloque, distribuye y adapta sus elementos con el estado y la lógica de tu aplicación.
77
83
 
78
84
  ### 3.2 Form Layouts (FieldGroup + Field)
79
85
  Para estructurar campos de formularios, no uses `div`s arbitrarios con clases de espaciado complejas. Usa las composiciones semánticas oficiales de Shadcn:
@@ -0,0 +1 @@
1
+ # Carpeta base para bloques complejos y layouts de Shadcn UI
@@ -0,0 +1 @@
1
+ # Carpeta base para layouts generales de la aplicacion
@@ -0,0 +1 @@
1
+ # Carpeta base para componentes primitivos de Shadcn UI
@@ -145,12 +145,15 @@ export const bootstrap_status = tool({
145
145
  export const bootstrap_nextjs_shadcn = tool({
146
146
  description: "Inicializa un proyecto Next.js 16 + Shadcn UI + Tailwind v4 + Vitest a partir de la plantilla canónica en .opencode/templates/nextjs-shadcn/. Es IDEMPOTENTE: si el proyecto ya está inicializado y no se pasa force=true, no toca nada. Copia archivo-por-archivo (sin pisar los existentes), mergea package.json, y opcionalmente instala dependencias y shadcn components.",
147
147
  args: {
148
+ targetDir: tool.schema.string().default(".").describe("Subdirectorio donde se inicializará el proyecto Next.js (ej: 'next-app' o '.')."),
148
149
  components: tool.schema.array(tool.schema.string()).default([]).describe("Lista de shadcn components a instalar (ej: ['button','input','table']). Vacío = no instalar shadcn components."),
149
150
  install: tool.schema.boolean().default(true).describe("Si true, ejecuta npm/pnpm/yarn install después de copiar. Si false, solo copia archivos."),
150
151
  force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta (mergea package.json)."),
151
152
  },
152
153
  async execute(args, context) {
153
154
  const root = getRoot(context)
155
+ const targetDir = args.targetDir || "."
156
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
154
157
  const start = Date.now()
155
158
  const templateDir = path.resolve(root, ".opencode/templates/nextjs-shadcn")
156
159
 
@@ -172,9 +175,9 @@ export const bootstrap_nextjs_shadcn = tool({
172
175
  }
173
176
 
174
177
  const isInitialized =
175
- fs.existsSync(path.resolve(root, "src/app/page.tsx")) &&
176
- fs.existsSync(path.resolve(root, "package.json")) &&
177
- fs.existsSync(path.resolve(root, "src/lib/utils.ts"))
178
+ fs.existsSync(path.resolve(targetPath, "src/app/page.tsx")) &&
179
+ fs.existsSync(path.resolve(targetPath, "package.json")) &&
180
+ fs.existsSync(path.resolve(targetPath, "src/lib/utils.ts"))
178
181
 
179
182
  if (isInitialized && !args.force) {
180
183
  return JSON.stringify({
@@ -189,7 +192,7 @@ export const bootstrap_nextjs_shadcn = tool({
189
192
  const filesSkipped: string[] = []
190
193
  const copyFileSafe = (rel: string) => {
191
194
  const src = path.join(templateDir, rel)
192
- const dst = path.resolve(root, rel)
195
+ const dst = path.resolve(targetPath, rel)
193
196
  if (!fs.existsSync(src)) return
194
197
  const dstExists = fs.existsSync(dst)
195
198
  if (dstExists && !args.force) {
@@ -201,7 +204,7 @@ export const bootstrap_nextjs_shadcn = tool({
201
204
  filesCopied.push(rel)
202
205
  }
203
206
 
204
- const userPkgPath = path.resolve(root, "package.json")
207
+ const userPkgPath = path.resolve(targetPath, "package.json")
205
208
  let mergedPkg: any = null
206
209
  if (fs.existsSync(userPkgPath) && !args.force) {
207
210
  try {
@@ -223,7 +226,7 @@ export const bootstrap_nextjs_shadcn = tool({
223
226
  copyFileSafe("package.json")
224
227
  }
225
228
 
226
- const userNextConfig = path.resolve(root, "next.config.ts")
229
+ const userNextConfig = path.resolve(targetPath, "next.config.ts")
227
230
  if (fs.existsSync(userNextConfig) && !args.force) {
228
231
  try {
229
232
  const content = fs.readFileSync(userNextConfig, "utf8")
@@ -255,17 +258,20 @@ export const bootstrap_nextjs_shadcn = tool({
255
258
  "src/app/globals.css",
256
259
  "src/lib/utils.ts",
257
260
  "src/components/theme-provider.tsx",
261
+ "src/components/ui/.gitkeep",
262
+ "src/components/blocks/.gitkeep",
263
+ "src/components/layout/.gitkeep",
258
264
  "src/test/setup.ts",
259
265
  "public/.gitkeep",
260
266
  ]
261
267
  for (const f of standardFiles) copyFileSafe(f)
262
268
 
263
- const pm = detectPackageManager(root)
269
+ const pm = detectPackageManager(targetPath)
264
270
 
265
271
  let installDuration = 0
266
272
  let installSkipped = false
267
273
  let installError: string | null = null
268
- const hasNodeModules = fs.existsSync(path.resolve(root, "node_modules"))
274
+ const hasNodeModules = fs.existsSync(path.resolve(targetPath, "node_modules"))
269
275
  if (args.install && (args.force || !hasNodeModules)) {
270
276
  const installStart = Date.now()
271
277
  const installCmd = pm === "pnpm"
@@ -275,7 +281,7 @@ export const bootstrap_nextjs_shadcn = tool({
275
281
  : "npm install --prefer-offline"
276
282
  try {
277
283
  execSync(installCmd, {
278
- cwd: root,
284
+ cwd: targetPath,
279
285
  stdio: "ignore",
280
286
  timeout: 300_000,
281
287
  })
@@ -293,7 +299,7 @@ export const bootstrap_nextjs_shadcn = tool({
293
299
  try {
294
300
  const cmd = `npx shadcn@latest add ${args.components.join(" ")} --yes --overwrite`
295
301
  execSync(cmd, {
296
- cwd: root,
302
+ cwd: targetPath,
297
303
  stdio: "ignore",
298
304
  timeout: 180_000,
299
305
  })
@@ -314,6 +320,7 @@ export const bootstrap_nextjs_shadcn = tool({
314
320
  componentsInstalled,
315
321
  installDuration,
316
322
  totalDuration: Date.now() - start,
323
+ targetDir,
317
324
  }
318
325
  writeBootstrapStatus(root, bootstrapRecord)
319
326
 
@@ -348,12 +355,15 @@ export const bootstrap_nextjs_shadcn = tool({
348
355
  export const bootstrap_fastapi = tool({
349
356
  description: "Inicializa un proyecto FastAPI + Pydantic + Uvicorn + Pytest + Ruff a partir de la plantilla canónica en .opencode/templates/fastapi-sdd/. Es IDEMPOTENTE: si el proyecto ya está inicializado y no se pasa force=true, no toca nada. Copia archivo-por-archivo (sin pisar los existentes), opcionalmente instala dependencias con uv (fallback pip).",
350
357
  args: {
358
+ targetDir: tool.schema.string().default(".").describe("Subdirectorio donde se inicializará el proyecto FastAPI (ej: 'backend' o '.')."),
351
359
  extras: tool.schema.array(tool.schema.string()).default([]).describe("Lista de paquetes Python adicionales a instalar (ej: ['sqlalchemy','pydantic-settings','pytest-asyncio']). Vacío = no instalar extras extra."),
352
360
  install: tool.schema.boolean().default(true).describe("Si true, ejecuta uv sync (o pip install -e '.[dev]') después de copiar. Si false, solo copia archivos."),
353
361
  force: tool.schema.boolean().default(false).describe("Si true, sobrescribe archivos existentes. Si false, los salta."),
354
362
  },
355
363
  async execute(args, context) {
356
364
  const root = getRoot(context)
365
+ const targetDir = args.targetDir || "."
366
+ const targetPath = targetDir === "." ? root : path.resolve(root, targetDir)
357
367
  const start = Date.now()
358
368
  const templateDir = path.resolve(root, ".opencode/templates/fastapi-sdd")
359
369
 
@@ -375,8 +385,8 @@ export const bootstrap_fastapi = tool({
375
385
  }
376
386
 
377
387
  const isInitialized =
378
- fs.existsSync(path.resolve(root, "src/main.py")) &&
379
- fs.existsSync(path.resolve(root, "pyproject.toml"))
388
+ fs.existsSync(path.resolve(targetPath, "src/main.py")) &&
389
+ fs.existsSync(path.resolve(targetPath, "pyproject.toml"))
380
390
 
381
391
  if (isInitialized && !args.force) {
382
392
  return JSON.stringify({
@@ -391,7 +401,7 @@ export const bootstrap_fastapi = tool({
391
401
  const filesSkipped: string[] = []
392
402
  const copyFileSafe = (rel: string) => {
393
403
  const src = path.join(templateDir, rel)
394
- const dst = path.resolve(root, rel)
404
+ const dst = path.resolve(targetPath, rel)
395
405
  if (!fs.existsSync(src)) return
396
406
  const dstExists = fs.existsSync(dst)
397
407
  if (dstExists && !args.force) {
@@ -403,7 +413,7 @@ export const bootstrap_fastapi = tool({
403
413
  filesCopied.push(rel)
404
414
  }
405
415
 
406
- const userPyprojectPath = path.resolve(root, "pyproject.toml")
416
+ const userPyprojectPath = path.resolve(targetPath, "pyproject.toml")
407
417
  let mergedPyproject: string | null = null
408
418
  if (fs.existsSync(userPyprojectPath) && !args.force) {
409
419
  try {
@@ -443,13 +453,13 @@ export const bootstrap_fastapi = tool({
443
453
  ]
444
454
  for (const f of standardFiles) copyFileSafe(f)
445
455
 
446
- const pm = detectPythonPackageManager(root)
456
+ const pm = detectPythonPackageManager(targetPath)
447
457
 
448
458
  let installDuration = 0
449
459
  let installSkipped = false
450
460
  let installError: string | null = null
451
- const hasVenv = fs.existsSync(path.resolve(root, ".venv"))
452
- const hasInstalled = fs.existsSync(path.resolve(root, "uv.lock")) || hasVenv
461
+ const hasVenv = fs.existsSync(path.resolve(targetPath, ".venv"))
462
+ const hasInstalled = fs.existsSync(path.resolve(targetPath, "uv.lock")) || hasVenv
453
463
  if (args.install && (args.force || !hasInstalled)) {
454
464
  const installStart = Date.now()
455
465
  const installCmd = pm === "uv"
@@ -457,7 +467,7 @@ export const bootstrap_fastapi = tool({
457
467
  : "pip install -e '.[dev]'"
458
468
  try {
459
469
  execSync(installCmd, {
460
- cwd: root,
470
+ cwd: targetPath,
461
471
  stdio: "ignore",
462
472
  timeout: 300_000,
463
473
  })
@@ -466,7 +476,9 @@ export const bootstrap_fastapi = tool({
466
476
  installError = e.message?.slice(0, 500) || "unknown error"
467
477
  }
468
478
  } else if (hasInstalled && !args.force) {
469
- installSkipped = true
479
+ try {
480
+ installSkipped = true
481
+ } catch (e) {}
470
482
  }
471
483
 
472
484
  const extrasInstalled: string[] = []
@@ -477,7 +489,7 @@ export const bootstrap_fastapi = tool({
477
489
  : `pip install ${args.extras.join(" ")}`
478
490
  try {
479
491
  execSync(extrasCmd, {
480
- cwd: root,
492
+ cwd: targetPath,
481
493
  stdio: "ignore",
482
494
  timeout: 180_000,
483
495
  })
@@ -498,6 +510,7 @@ export const bootstrap_fastapi = tool({
498
510
  extrasInstalled,
499
511
  installDuration,
500
512
  totalDuration: Date.now() - start,
513
+ targetDir,
501
514
  }
502
515
  writeBootstrapStatus(root, bootstrapRecord)
503
516
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zugzbot",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
4
4
  "description": "Fácil instalador del arnés SDD de Zugzbot para proyectos OpenCode",
5
5
  "type": "module",
6
6
  "bin": {