@trycore/spec-build-harness 0.8.1 → 0.8.3
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/.claude-plugin/plugin.json +5 -2
- package/GOVERNANCE.md +1 -1
- package/INSTALL.md +4 -4
- package/METODOLOGIA.md +50 -7
- package/README.md +8 -5
- package/VERSION +1 -1
- package/agents/build/architecture-evaluator.md +45 -0
- package/agents/build/asr-extractor.md +43 -0
- package/agents/build/dor-dod-gatekeeper.md +20 -0
- package/agents/build/stack-guardian.md +9 -0
- package/asset-types.json +75 -0
- package/commands/build/architect.md +66 -0
- package/docs/agents.md +32 -3
- package/docs/commands.md +9 -3
- package/docs/getting-started.md +1 -1
- package/docs/runtime/plan-migracion-harness-v0.9.md +84 -0
- package/docs/runtime/protocolo-cliente-runtime.md +116 -0
- package/package.json +2 -1
- package/skills/building-a-slice/references/dor.md +8 -0
- package/skills/building-a-slice/workflows/README.md +7 -1
- package/skills/building-a-slice/workflows/dor-fanout.workflow.js +98 -0
- package/skills/releasing-a-version/SKILL.md +5 -2
- package/skills/releasing-a-version/references/release-dod.md +1 -1
- package/skills/releasing-a-version/workflows/README.md +6 -1
- package/skills/releasing-a-version/workflows/release-gate.workflow.js +59 -4
- package/skills/setup-architecture/SKILL.md +94 -0
- package/skills/setup-architecture/assets/0000-drivers-y-asrs.template.md +101 -0
- package/skills/setup-architecture/assets/_backlog-arquitectonico.template.md +51 -0
- package/skills/setup-architecture/assets/adr-add.template.md +84 -0
- package/skills/setup-architecture/references/add-method.md +53 -0
- package/skills/setup-architecture/references/atam-lite.md +44 -0
- package/skills/setup-architecture/references/drivers-extraction.md +39 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Plan de migración — Harness v0.9: de archivos estáticos a cliente del Agent Orchestrator Runtime
|
|
2
|
+
|
|
3
|
+
> Repo: `trycore-spec-build-harness` (v0.8.1 → v0.9.0). Contraparte servidor: módulo `orchestrator` de **trycore-ia-hub** (PRD Agent Orchestrator Runtime v2.1, ADR-0012 del hub, épica EP-OR-08). Contrato de red: [protocolo-cliente-runtime.md](protocolo-cliente-runtime.md).
|
|
4
|
+
>
|
|
5
|
+
> **Principio rector:** la metodología no cambia — cambia el medio. Los dos loops, las fases, los gates y los veredictos son los mismos; lo que se moviliza es *dónde vive el estado* (del JSON local al runtime) y *quién valida las transiciones* (del prompt/honor system al servidor). Regla de corte: **hechos y transiciones de dominio → runtime · observación y enforcement del working tree → harness · protocolo de estado → cliente API.**
|
|
6
|
+
|
|
7
|
+
## 1. Matriz de movilización por artefacto
|
|
8
|
+
|
|
9
|
+
### 1.1 Estado (`state/`)
|
|
10
|
+
|
|
11
|
+
| Artefacto v0.8.1 | Acción en v0.9 | Detalle |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `state/build-state.json` | **RETIRAR** (tras modo dual) | Sustituido por el runtime. Queda solo `state/context.lock` (lockfile del plano de contexto) y un caché de proyección de solo lectura para modo offline. |
|
|
14
|
+
| `state/build-state.schema.json` + `state/README.md` | **RETIRAR** | La validación es server-side. El README se reescribe: "el estado vive en el runtime; esto es el caché". |
|
|
15
|
+
| `state/build-state.template.json` | **RETIRAR** | `init` ya no siembra estado: registra el proyecto/agente en el runtime. |
|
|
16
|
+
| Reglas de `reconcile-build-state.py` (evidence, ratchet, drift) | **MOVILIZAR** a reducers del runtime | La *observación* local (rama git real vs branch del slice) queda en un reporter que emite evento `branch_drift`. El script local desaparece. |
|
|
17
|
+
|
|
18
|
+
### 1.2 Hooks (`hooks/build/`)
|
|
19
|
+
|
|
20
|
+
| Hook | Acción | Detalle |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `load-build-state.sh` (SessionStart) | **REESCRIBIR** como cliente | Hidrata desde `GET /agent/context` (registro + slice activo + gates + resume_hint + `manifest_hash`); dispara sync de contexto; escribe caché local. Mantiene la inyección de `additionalContext` (mecánica Claude Code intacta). |
|
|
23
|
+
| `context-monitor.sh` + `statusline-bridge.sh` | **CONSERVAR + extender** | Umbral/statusline siguen locales. El handoff crítico además emite `POST /events` tipo `handoff_recorded`. La statusline muestra versión de contexto (`✓ v14` / `⚠ stale`). Corregir de paso el fallback cross-sesión (`ls /tmp/claude-ctx-*.json | head -1`). |
|
|
24
|
+
| `gitflow-guard.sh` | **CONSERVAR**; política desde el runtime | Mismo bloqueo PreToolUse con latencia cero; la política (ramas tipadas, rama de integración) se lee del asset `POLICY` sincronizado — muere la contradicción plugin vs CLAUDE.md del proyecto. |
|
|
25
|
+
| `stack-guard.sh` | **CONSERVAR**; allowlist desde el runtime | Sigue leyendo `.claude/config/stack-allowlist.json`, pero ese archivo ahora lo escribe el sync de contexto, no un humano. |
|
|
26
|
+
| `scaffold-guard.sh` / `design-source-guard.sh` | **CONSERVAR**; hechos desde el runtime | `scaffold.confirmed`/`design_source`/fase se leen del caché de proyección (refrescado en claim/SessionStart), no de un JSON por-clon. |
|
|
27
|
+
| `lint-typecheck.sh`, `coherence-flag.sh` | **CONSERVAR sin cambios** | 100% locales al working tree. |
|
|
28
|
+
| `build-gate-check.sh`, `reflect-nudge.sh`, `release-gate-nudge.sh` (Stop) | **ADELGAZAR** | La aritmética se moviliza al runtime; el hook renderiza `GET /nudges` (con caché). Hoy computan sobre estado parcial por-clon — 31/33 slices sin `reflected` demuestran su ineficacia. |
|
|
29
|
+
| **NUEVOS** | `heartbeat` (renovación de lease, intervalo < ⅓ TTL), `event-emitter` (PostToolUse → cola local → `POST /events` en lote idempotente), `context-sync` (pre-claim y SessionStart, por hash) | Telemetría como propiedad del harness, imposible de omitir por el modelo. |
|
|
30
|
+
| `hooks/build/lib/state-io.sh` | **RETIRAR** | Sin archivo de estado, sin read-modify-write. Nace `lib/runtime-client.sh` (curl + token + cola offline). |
|
|
31
|
+
|
|
32
|
+
### 1.3 Skills, commands y agentes
|
|
33
|
+
|
|
34
|
+
| Artefacto | Acción | Detalle |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `building-a-slice` | **REESCRIBIR el protocolo, conservar la inteligencia** | Fases/gates los dicta el claim (`GET /tasks/next` devuelve fase y siguiente paso); la skill conduce TDD/cableado como hoy. Toda escritura de estado se sustituye por `POST /verdicts|/checkpoints|/events`. `references/state-protocol.md` se reemplaza por `references/runtime-protocol.md`. |
|
|
37
|
+
| `releasing-a-version` | **REESCRIBIR protocolo** | Los 6 veredictos se reportan; el runtime agrega ("parciales no promueven") y cierra `PENDING→PASSED/FAILED`. |
|
|
38
|
+
| `managing-parallel-front` + `scripts/lib/front-plan.py` | **MOVILIZAR el plan al runtime; la skill queda como conductora local** | La selección disjunta, `merge_order` y `merge_status` viven en el runtime (la spec interna 2026-07-03 ya pedía "front-planner: código, no criterio libre del modelo"). La skill abre worktrees y conduce; cada worktree reporta solo. |
|
|
39
|
+
| `/build:resume` | **ADELGAZAR** | Su prioridad determinista (resume_hint → wiring failing → sub_slice → fase) ES `GET /tasks/next`. El command queda como envoltorio. |
|
|
40
|
+
| `/build:work`, `/build:slice`, `/build:release`, `/build:front`, `/build:onboard`, `/build:reflect` | **CONSERVAR**, protocolo API | `/build:onboard` además **persiste** el grafo (épicas, dependencias, layer, files_scope) vía API en lugar de solo frontmatter. Nuevos: `/build:claim`, `/build:status` (flota), `/build:escalate`. |
|
|
41
|
+
| Agentes de veredicto (`dor-dod-gatekeeper`, `wiring-adversarial-verifier`, `stack-guardian`, reviewers, etc.) | **CONSERVAR intactos** | Siguen emitiendo veredictos; solo cambia el destino (API, no JSON). El contrato de degradación segura lo impone ahora el servidor. |
|
|
42
|
+
| Skills `openspec-*` | **CONSERVAR**; enlazar | Los artefactos siguen en git. El slice del runtime referencia `openspec_change`; `opsx:archive` dispara además la transición `slice_archived`. |
|
|
43
|
+
| Workflows `*.workflow.js` | **CONSERVAR** | Read-only; sus resultados se reportan como eventos. |
|
|
44
|
+
|
|
45
|
+
### 1.4 Setup nuevo: generación de documentos de arquitectura (previsto)
|
|
46
|
+
|
|
47
|
+
La skill futura `setup-architecture` (estilo de arquitectura, patrones de diseño detallado, stack tecnológico, …) nace ya sobre el modelo v0.9: el paquete declara sus **Asset Type Descriptors** en `asset-types.json` (ver protocolo §6); la skill genera los `.md` y los **propone** vía API (nunca publica). Cualquier documento tipado futuro sigue este mismo camino sin tocar el frontend del hub.
|
|
48
|
+
|
|
49
|
+
### 1.5 CLI (`trycore-build`) e instalación
|
|
50
|
+
|
|
51
|
+
| Pieza | Acción |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `init` | Reescribir: pide URL del runtime + token de proyecto (emitido por un ADMIN en el hub), registra el agente, ejecuta el primer sync de contexto. Sigue sembrando settings/hooks/CLAUDE.md markers (bootstrap local legítimo). |
|
|
54
|
+
| `doctor` | Extender: conectividad al runtime, validez del token, frescura del lock de contexto, drift local. |
|
|
55
|
+
| `status` | Reescribir sobre `GET /agent/context` + `/nudges`. |
|
|
56
|
+
| `state-seed.ts` | Retirar (política [C1] ya no aplica: no hay archivo que proteger). |
|
|
57
|
+
| `sync-trycore-assets.sh` (en los repos consumidores) | **RETIRAR** — razón de ser eliminada por el plano de contexto. |
|
|
58
|
+
| `check-state-clean.sh` | Simplificar: ya no hay `state/` con artefactos vivos que perseguir. |
|
|
59
|
+
|
|
60
|
+
## 2. Modo dual y corte (sin big-bang)
|
|
61
|
+
|
|
62
|
+
1. **v0.9.0-beta (dual):** los hooks escriben a la API **y** mantienen `build-state.json` como espejo de solo escritura (nunca se lee). Flag `TRYCORE_RUNTIME_MODE=dual|runtime|legacy` en `build-config`. Piloto: `diagramador-ia-for-process`.
|
|
63
|
+
2. **Import histórico:** script one-shot que convierte el `build-state.json` del piloto (33 slices en `history[]`, 6 releases, con sus irregularidades conocidas: fase `parked`, clave `release`, gates `null`) en eventos del runtime. Las entradas no mapeables se importan como eventos `legacy_imported` con payload original — nada se pierde, nada bloquea.
|
|
64
|
+
3. **Corte (`runtime`):** tras 1 sprint del piloto sin discrepancias (comparador dual espejo vs proyección), se elimina la escritura local. `legacy` queda solo como escape documentado durante la beta y se retira en v0.9.0 final.
|
|
65
|
+
4. **Matriz de compatibilidad:** el runtime rechaza `harness_version` < 0.9.0 para proyectos ya migrados; v0.8.x sigue funcionando en proyectos no migrados (el plugin no rompe a nadie).
|
|
66
|
+
|
|
67
|
+
## 3. Checklist de aceptación de v0.9.0
|
|
68
|
+
|
|
69
|
+
- [ ] Cero lecturas/escrituras de `build-state.json` en modo `runtime` (grep de CI sobre hooks/skills, análogo a `check-agnostic.sh`).
|
|
70
|
+
- [ ] Un clon fresco llega a "primer claim con contexto completo" en < 1 min con solo `init` + token.
|
|
71
|
+
- [ ] Matar la sesión a mitad de slice ⇒ lease expira ⇒ otro agente continúa desde el checkpoint (test E2E contra runtime de staging).
|
|
72
|
+
- [ ] Guards funcionan offline con el último lock (`⚠ stale` visible); eventos encolados se entregan al reconectar, idempotentes.
|
|
73
|
+
- [ ] Los 4 guards bloqueantes conservan latencia local (sin red en el camino de PreToolUse).
|
|
74
|
+
- [ ] Import histórico del piloto reproduce `history[]`/`releases[]` como proyecciones consultables.
|
|
75
|
+
- [ ] `asset-types.json` del paquete se registra en el runtime al primer agente (tipos aditivos auto-publicados).
|
|
76
|
+
|
|
77
|
+
## 4. Riesgos propios de la migración
|
|
78
|
+
|
|
79
|
+
| Riesgo | Mitigación |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Latencia de red en hooks de camino caliente | Solo `heartbeat`/`event-emitter` tocan red y son asíncronos con cola local; PreToolUse jamás llama a la API. |
|
|
82
|
+
| Runtime caído a mitad de sprint | Modo degradado (RNF-6 del PRD): último lock + cola de eventos; el trabajo local nunca se bloquea — degrada exactamente a la situación v0.8.1. |
|
|
83
|
+
| Divergencia dual (espejo ≠ proyección) durante la beta | Comparador automático en el hook Stop; discrepancia = issue bloqueante del corte. |
|
|
84
|
+
| Deriva de versiones en la flota | El registro reporta `harness_version`; el runtime la exige en matriz; el tablero muestra la versión por agente. |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Protocolo cliente — Harness v0.9 ↔ Agent Orchestrator Runtime
|
|
2
|
+
|
|
3
|
+
> Contrato del lado cliente. El contrato del lado servidor vive en trycore-ia-hub: `docs/01-prd/anexos/orchestrator-runtime-design.md` (§4 catálogo de eventos, §5 API). Este documento define cómo el harness lo consume: autenticación, mapeo hook→endpoint, sincronización de contexto, cola offline y declaración de tipos de asset.
|
|
4
|
+
|
|
5
|
+
## 1. Autenticación y arranque
|
|
6
|
+
|
|
7
|
+
- **Token de proyecto:** emitido por un ADMIN en el hub, entregado al desarrollador, configurado una vez con `trycore-build init` (se guarda en `.claude/state/runtime.credentials`, fuera de git, `0600`). Todas las llamadas: `Authorization: Bearer <token>`.
|
|
8
|
+
- **Registro:** `POST /agents/register {harness_version, capabilities}` → `{agent_id, project, manifest_hash, poll_interval_s, lease_ttl_s}`. Los intervalos los dicta el servidor (el cliente no los hardcodea). `409 incompatible_version` ⇒ mensaje claro con la versión mínima requerida.
|
|
9
|
+
- **Identidad local:** `agent_id` persiste en `.claude/state/runtime.credentials`; un mismo clon re-registrado reactiva su agente (no crea otro).
|
|
10
|
+
|
|
11
|
+
## 2. Mapeo hook → endpoint (la telemetría es del harness, no del modelo)
|
|
12
|
+
|
|
13
|
+
| Evento Claude Code | Hook v0.9 | Llamada | Notas |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| SessionStart (`startup\|clear\|compact`) | `session-start.sh` | `POST /agents/register` (idempotente) + `GET /agent/context` + sync de contexto (§4) | Inyecta `additionalContext`: slice activo, fase, gates abiertos, wiring failing, resume_hint, nudges, versión de contexto. |
|
|
16
|
+
| PostToolUse (`Edit\|Write\|MultiEdit\|Bash\|Task`) | `event-emitter.sh` | encola → `POST /events` (lote) | **Nunca síncrono en el camino del tool.** Encola en `.claude/state/outbox/` y despacha en background (ver §5). |
|
|
17
|
+
| Timer / PostToolUse throttled | `heartbeat.sh` | `PUT /leases/renew` | Intervalo = `lease_ttl_s / 3` (valor del servidor). |
|
|
18
|
+
| PreCompact / Stop con contexto crítico | `context-monitor.sh` | evento `handoff_recorded {stopped_at, resume_hint}` | Además del aviso local existente. |
|
|
19
|
+
| Stop | `session-stop.sh` | flush de outbox + `GET /nudges` (cacheado) | Renderiza nudges del servidor; sin aritmética local. |
|
|
20
|
+
| PreToolUse (guards) | `gitflow/stack/scaffold/design-source-guard.sh` | **ninguna** (prohibido red en PreToolUse) | Leen archivos sincronizados + caché de proyección local. |
|
|
21
|
+
|
|
22
|
+
Eventos emitidos por skills (no por hooks): `POST /checkpoints` (tras cada commit significativo), `POST /slices/{id}/verdicts` (al recibir el veredicto de un agente revisor), `POST /slices/{id}/submit` (entrega), `GET /tasks/next` (claim).
|
|
23
|
+
|
|
24
|
+
## 3. Claim (el corazón del pull)
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
GET /tasks/next
|
|
28
|
+
200 → {slice_id, epic, phase, next_step, branch_base, acceptance_refs,
|
|
29
|
+
openspec_change, lease: {ttl_s, expires_at}, manifest_hash,
|
|
30
|
+
checkpoint: {branch, commit_sha} | null}
|
|
31
|
+
204 → sin trabajo disponible (la skill lo comunica y termina limpio)
|
|
32
|
+
409 → carrera perdida (otro agente reclamó primero): reintentar una vez
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Reglas del cliente:
|
|
36
|
+
|
|
37
|
+
1. Antes de trabajar: verificar `manifest_hash` contra el lock (§4); sincronizar si difiere. **No se abre trabajo con contexto viejo.**
|
|
38
|
+
2. Si `checkpoint != null`: hacer checkout/pull de la rama y **continuar desde el checkpoint, jamás reiniciar** (recuperación de otro agente caído).
|
|
39
|
+
3. En el claim se reportan los sha256 locales efectivos de los archivos gobernados (allowlist, policy, rules) — el servidor detecta drift/manipulación local.
|
|
40
|
+
4. Un agente mantiene **un solo** slice activo; `GET /tasks/next` con lease vigente devuelve el mismo slice (idempotencia).
|
|
41
|
+
|
|
42
|
+
## 4. Sincronización de contexto (content-addressed)
|
|
43
|
+
|
|
44
|
+
Lockfile: `.claude/state/context.lock`
|
|
45
|
+
|
|
46
|
+
```jsonc
|
|
47
|
+
{
|
|
48
|
+
"manifest_hash": "sha256:…",
|
|
49
|
+
"synced_at": "2026-08-04T…Z",
|
|
50
|
+
"files": [{"path": "config/stack-allowlist.json", "type_key": "core.library_allowlist", "sha256": "…", "version": 7}]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Algoritmo (`context-sync.sh`, corre en SessionStart y pre-claim):
|
|
55
|
+
|
|
56
|
+
1. Comparar `manifest_hash` esperado (del registro o del claim) con el del lock. Igual ⇒ fin (costo: comparación de strings).
|
|
57
|
+
2. Distinto ⇒ `GET /projects/{id}/context/manifest`; diff por `sha256` archivo a archivo; `GET /context/files/{path}?sha256=…` **solo** de los cambiados (ETag inmutable ⇒ cacheable).
|
|
58
|
+
3. Escritura atómica (`mktemp` + `os.replace`, patrón ya probado en v0.8.1) sobre los destinos gobernados: `config/`, `rules/`, bloques marcados de `CLAUDE.md` (vía `markers.ts`), `docs-cache/` para `DOC`.
|
|
59
|
+
4. Actualizar el lock; emitir `context_synced {from_hash, to_hash, files_changed[]}`; aviso de una línea al usuario (`⬆ contexto v13 → v14: allowlist (+2), PRD §4`).
|
|
60
|
+
5. **Prohibido** editar a mano archivos gobernados: el sync los sobreescribe y el drift se reporta. El cambio legítimo se hace en el hub (propuesta→publicación).
|
|
61
|
+
|
|
62
|
+
Offline: sin runtime, se trabaja con el último lock; la statusline marca `⚠ stale`; al reconectar, sync antes del siguiente claim.
|
|
63
|
+
|
|
64
|
+
## 5. Cola offline de eventos (`.claude/state/outbox/`)
|
|
65
|
+
|
|
66
|
+
- Un archivo JSON por lote, con `client_event_id` (UUID) por evento ⇒ **idempotencia server-side** (reintentos seguros).
|
|
67
|
+
- Despacho en background con backoff (1 s → 5 s → 30 s → 5 min, tope); orden FIFO por agregado.
|
|
68
|
+
- Cota: 5 MB / 72 h — al superarla se descartan primero los eventos de telemetría fina (PostToolUse), **nunca** checkpoints, veredictos ni submits; el descarte se reporta como evento `telemetry_gap` al reconectar.
|
|
69
|
+
- Flush forzado en Stop y en `trycore-build doctor`.
|
|
70
|
+
|
|
71
|
+
## 6. Declaración de tipos de asset (`asset-types.json` del paquete)
|
|
72
|
+
|
|
73
|
+
El paquete del plugin declara los tipos de documento que sabe generar. En el registro, el runtime hace upsert idempotente (aditivos auto-publicados; breaking quedan propuestos para un ADMIN):
|
|
74
|
+
|
|
75
|
+
```jsonc
|
|
76
|
+
{
|
|
77
|
+
"meta_schema_version": 1,
|
|
78
|
+
"types": [
|
|
79
|
+
{
|
|
80
|
+
"type_key": "arch.style",
|
|
81
|
+
"title": "Estilo de arquitectura",
|
|
82
|
+
"category": "Arquitectura",
|
|
83
|
+
"content_format": "markdown+frontmatter",
|
|
84
|
+
"frontmatter_schema": {"type": "object", "properties": {"style": {"type": "string"}, "drivers": {"type": "array", "items": {"type": "string"}}}, "required": ["style"]},
|
|
85
|
+
"body_sections": [
|
|
86
|
+
{"id": "decision", "title": "Estilo elegido", "required": true},
|
|
87
|
+
{"id": "rationale", "title": "Justificación", "required": true},
|
|
88
|
+
{"id": "consequences", "title": "Consecuencias", "required": false}
|
|
89
|
+
],
|
|
90
|
+
"cardinality": "single",
|
|
91
|
+
"generation": {"source": "plugin", "skill": "setup-architecture"},
|
|
92
|
+
"descriptor_version": 1
|
|
93
|
+
}
|
|
94
|
+
// arch.patterns, stack.profile, …
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Las skills generadoras producen la instancia y la **proponen**: `POST /projects/{id}/context/proposals {type_key, path, frontmatter, body}` — validada server-side contra el `frontmatter_schema`; la publica un ADMIN en el hub. Un agente jamás publica contexto.
|
|
100
|
+
|
|
101
|
+
## 7. Errores y degradación (tabla normativa)
|
|
102
|
+
|
|
103
|
+
| Situación | Comportamiento del cliente |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `401/403` (token inválido/revocado) | Detener claims; mensaje accionable ("pide un token nuevo al ADMIN"); guards siguen operando con el último lock. |
|
|
106
|
+
| `409` en claim | Un reintento inmediato; luego informar "otro agente tomó la tarea" y pedir la siguiente. |
|
|
107
|
+
| `422` (evento/veredicto rechazado por transición ilegal o schema) | **No reintentar**: mostrar la razón del servidor al modelo/usuario (compuerta mecánica funcionando); registrar localmente. |
|
|
108
|
+
| Timeout/red caída | Modo offline (§4/§5); jamás bloquear PreToolUse ni el trabajo local. |
|
|
109
|
+
| `410` en lease (expirado durante trabajo largo) | El slice pudo ser re-entregado: detener, hacer checkpoint local, re-claim (puede devolver el mismo slice si nadie lo tomó). |
|
|
110
|
+
| Reloj local desviado | El cliente usa siempre `expires_at` del servidor, nunca calcula TTL con reloj propio. |
|
|
111
|
+
|
|
112
|
+
## 8. Seguridad del cliente
|
|
113
|
+
|
|
114
|
+
- El token vive solo en `.claude/state/runtime.credentials` (gitignoreado, `0600`); nunca en `settings.json` ni en el repo — no repetir el incidente de rutas/credenciales de máquina fosilizadas en `settings.json`.
|
|
115
|
+
- El harness no envía código fuente al runtime: solo referencias git, hashes, veredictos y metadatos (los payloads se validan contra schema; el servidor rechaza payloads sobredimensionados).
|
|
116
|
+
- `trycore-build doctor` verifica: token válido, reloj razonable, lock fresco, outbox drenando, hashes sin drift.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trycore/spec-build-harness",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Arnés agéntico de construcción de Trycore para Claude Code: pipeline de dos loops (slice por épica + release gate) con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"config/",
|
|
17
17
|
"state/",
|
|
18
18
|
"templates/",
|
|
19
|
+
"asset-types.json",
|
|
19
20
|
"internal/",
|
|
20
21
|
"docs/",
|
|
21
22
|
"!docs/superpowers",
|
|
@@ -12,6 +12,14 @@ la épica; lo de abajo aplica a la épica y a **cada HU** que cubre (`hus[]`). L
|
|
|
12
12
|
- [ ] **Cimiento construido (épicas de negocio)**: si esta épica es `layer: business`, todo el cimiento que arrastra (auth, acceso a datos, arquitectura base, design-system/componentes base) ya existe como épica(s) `layer: foundational` **archivada(s)** en `history[]`. Si arrastra cimiento no construido → **STOP**: extráelo a una épica fundacional previa y constrúyela primero. Las épicas fundacionales se priorizan **antes** que las de negocio.
|
|
13
13
|
- [ ] **Tamaño acotado (gate de descomposición)**: si la épica supera el umbral —heurística por defecto **> 3 HU** ó **≥ 3 capas tocadas** (configurable por proyecto)— **no entra como slice único**: se descompone en `sub_slices[]` verificables construidos de a uno, con `journey_smoke` verde entre cada uno. El umbral es proporcional (no cuota rígida): una épica de 1 capa y pocas HU entra directa.
|
|
14
14
|
- [ ] **Cabe en el stack** del PRD §7 (no requiere tecnología fuera de `stack-allowlist.json`).
|
|
15
|
+
- [ ] **Cobertura arquitectónica (ADR)** *(opt-in, retrocompatible)*: si el proyecto adoptó la capa de
|
|
16
|
+
arquitectura (existe `docs/adr/_backlog-arquitectonico.md`, de `/build:architect`), los drivers
|
|
17
|
+
arquitecturalmente significativos que la épica ejerce tienen un ADR con estado ≥ `ABORDADO`. El join
|
|
18
|
+
épica↔driver es por la columna **Trazabilidad (EP/HU)** del tablero del backlog (fallback: la columna
|
|
19
|
+
Trazabilidad del `0000`). **Duro para épicas `layer: foundational`** (auth/datos/arquitectura
|
|
20
|
+
base/design-system): sin ADR que cubra su cimiento → **STOP**, corre `/build:architect` primero. Para
|
|
21
|
+
`layer: business` es advertencia si algún driver sigue `PENDIENTE`. Sin `docs/adr/` (proyecto sin la
|
|
22
|
+
capa) → **N/A** (no bloquea; sugiere la capa).
|
|
15
23
|
- [ ] **Datos de prueba disponibles** o identificables (p.ej. los datos de ejemplo / fixtures sintéticos del dominio del consumidor).
|
|
16
24
|
- [ ] **Fuente de diseño identificada (slices con UI)**: la fuente visual de verdad del slice
|
|
17
25
|
(el `DESIGN_SOURCE` del dominio) está declarada y confirmada (`design_source.confirmed`), y este
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
## Reglas duras (todas las plantillas las cumplen)
|
|
8
8
|
1. **Solo para épicas grandes.** Los workflows del inner loop son **OPT-IN** y solo para épicas troceadas por
|
|
9
9
|
el gate de tamaño (`sub_slices[]` no vacío). **Nunca** en el camino caliente ≤ ~20 min de una épica
|
|
10
|
-
atómica: inflaría el inner loop barato.
|
|
10
|
+
atómica: inflaría el inner loop barato. *Excepción de guard:* `dor-fanout` corre **antes** de abrir el
|
|
11
|
+
slice (aún no existe `sub_slices[]`), así que su guard es por **nº de HUs** (≥ 3; bajo eso se auto-salta
|
|
12
|
+
y la validación queda secuencial en sesión).
|
|
11
13
|
2. **Read-only sobre el estado.** Ninguna plantilla escribe `build-state.json`. El único escritor de los
|
|
12
14
|
gates del slice sigue siendo `build-orchestrator` (y los agentes dueños de cada gate). Las plantillas
|
|
13
15
|
**devuelven un diagnóstico**; la sesión/orquestador aplica el mapeo respetando el protocolo: **una
|
|
@@ -23,3 +25,7 @@
|
|
|
23
25
|
gated por `sub_slices[]`. Contrato detallado en `../references/exploration-fanout.md`.
|
|
24
26
|
- **`wiring-verify.workflow.js`** — conducción adversarial del gate `wiring_verified` (envuelve, read-only, al
|
|
25
27
|
agente `wiring-adversarial-verifier`); devuelve el veredicto, no escribe el gate.
|
|
28
|
+
- **`dor-fanout.workflow.js`** — fan-out **per-HU** de los chequeos lentos del DoR (frontmatter, AC G/W/T
|
|
29
|
+
proporcional, INVEST) con consolidación **fail-closed** y cobertura completa. Los criterios de **nivel
|
|
30
|
+
épica** (dependencias, cimiento, tamaño, stack, diseño, ADR) NO van aquí: los valida `dor-dod-gatekeeper`
|
|
31
|
+
en sesión, que combina ambos y es el único que emite el veredicto DoR. Guard: ≥ 3 HUs.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// PLANTILLA — referencia, NO un script a correr verbatim.
|
|
3
|
+
// dor-fanout.workflow.js — Fan-out de los chequeos PER-HU del DoR (inner loop).
|
|
4
|
+
//
|
|
5
|
+
// QUÉ PARALELIZA (y qué NO): SOLO los criterios del DoR que son por-HU e independientes
|
|
6
|
+
// entre sí — frontmatter completo + estado:lista, AC en Given/When/Then proporcional a
|
|
7
|
+
// `complejidad`, e INVEST. Los criterios de NIVEL ÉPICA (trazabilidad de la épica,
|
|
8
|
+
// dependencias en history[], cimiento construido, gate de tamaño, stack/allowlist,
|
|
9
|
+
// fuente de diseño, cobertura de ADR) NO van aquí: los valida dor-dod-gatekeeper en
|
|
10
|
+
// sesión — necesitan estado y son baratos. Este workflow ataca lo LENTO: O(HUs) → O(1).
|
|
11
|
+
//
|
|
12
|
+
// READ-ONLY sobre el estado: NO escribe gates.dor ni abre active_slice. El ÚNICO que
|
|
13
|
+
// abre el slice sigue siendo dor-dod-gatekeeper, que COMBINA este diagnóstico per-HU
|
|
14
|
+
// con sus criterios de épica y emite el veredicto DoR completo.
|
|
15
|
+
//
|
|
16
|
+
// CUÁNDO: SOLO con ≥ 3 HUs (bajo eso el fan-out no paga su overhead y el inner loop
|
|
17
|
+
// debe seguir barato). Consolidación FAIL-CLOSED con cobertura COMPLETA: una HU
|
|
18
|
+
// fallida o un shard ausente = DoR per-HU false, nunca muestreo silencioso.
|
|
19
|
+
//
|
|
20
|
+
// PLANTILLA AGNÓSTICA: sin vocabulario de dominio/cliente (scripts/check-agnostic.sh
|
|
21
|
+
// escanea *.js). Si METODOLOGIA.md (§3.1) contradice algo aquí, gana la metodología.
|
|
22
|
+
//
|
|
23
|
+
// RUNTIME: corre en el runtime de Workflow de Claude Code, que provee los globals
|
|
24
|
+
// agent()/parallel()/pipeline()/phase()/log()/args y envuelve el cuerpo en un contexto
|
|
25
|
+
// async (por eso usa `await` y `return` a nivel superior). NO es un módulo node standalone.
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
export const meta = {
|
|
29
|
+
name: 'dor-fanout',
|
|
30
|
+
description: 'Fan-out per-HU de los chequeos del DoR (frontmatter, AC G/W/T proporcional, INVEST) con consolidación fail-closed. Read-only; dor-dod-gatekeeper combina el diagnóstico y emite el veredicto.',
|
|
31
|
+
phases: [{ title: 'HUs', detail: 'un validador solo-lectura por HU, en paralelo' }],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const HU_CHECK_SCHEMA = {
|
|
35
|
+
type: 'object', additionalProperties: false,
|
|
36
|
+
required: ['hu', 'pass', 'faltantes'],
|
|
37
|
+
properties: {
|
|
38
|
+
hu: { type: 'string' },
|
|
39
|
+
pass: { type: 'boolean', description: 'true SOLO si los 3 bloques (frontmatter, AC, INVEST) están completos y verificados' },
|
|
40
|
+
faltantes: { type: 'array', items: { type: 'string' }, description: 'cada criterio incumplido, con detalle accionable' },
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Insumos por `args` (dor-dod-gatekeeper los pasa READ-ONLY; el workflow no decide alcance):
|
|
45
|
+
// args.epica : string — ID de la épica (solo para el reporte).
|
|
46
|
+
// args.hus : string[] — IDs de las HU en alcance (hus[] del slice candidato).
|
|
47
|
+
// args.husDir : string — directorio de las HU (default 'docs/04-historias').
|
|
48
|
+
const epica = (args && args.epica) || '<EP-XXX>'
|
|
49
|
+
const hus = (args && Array.isArray(args.hus)) ? args.hus.filter(Boolean) : []
|
|
50
|
+
const husDir = (args && args.husDir) || 'docs/04-historias'
|
|
51
|
+
|
|
52
|
+
// Guard de tamaño: bajo 3 HUs el fan-out no paga su overhead → el gatekeeper valida
|
|
53
|
+
// secuencial en sesión (comportamiento previo). Espeja el guard de explore-fanout.
|
|
54
|
+
if (hus.length < 3) {
|
|
55
|
+
log(`Épica ${epica} con ${hus.length} HU(s): fan-out no amortiza — validación secuencial en sesión.`)
|
|
56
|
+
return { skipped: true, reason: 'pocas-hus', hus_count: hus.length }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
phase('HUs')
|
|
60
|
+
log(`DoR per-HU: fan-out de ${hus.length} HUs de ${epica} (cobertura completa, sin muestreo)`)
|
|
61
|
+
const checks = await parallel(hus.map((hu) => async () => {
|
|
62
|
+
try {
|
|
63
|
+
const v = await agent(
|
|
64
|
+
`Eres un validador SOLO-LECTURA del DoR para UNA SOLA historia de usuario: ${hu} (épica ${epica}).
|
|
65
|
+
Lee ${husDir}/${hu}.md y verifica EXACTAMENTE estos 3 bloques (nada de nivel épica):
|
|
66
|
+
1. FRONTMATTER completo: id, titulo, epica, prioridad, complejidad, estado — y estado: lista.
|
|
67
|
+
2. AC en Given/When/Then PROPORCIONAL a complejidad: trivial/baja → 1-2 (happy + error/edge crítico si
|
|
68
|
+
existe); media → 3 (happy + error + edge); alta → 3-5 (cobertura completa). Regla dura: toda rama de
|
|
69
|
+
error/edge que exista DEBE tener su escenario; NO exijas cuota fija a una HU trivial.
|
|
70
|
+
3. INVEST: evalúa tú mismo los 6 criterios (Independent, Negotiable, Valuable, Estimable, Small, Testable).
|
|
71
|
+
pass:true SOLO si los 3 bloques cumplen. Cada incumplimiento va en faltantes[] con detalle accionable
|
|
72
|
+
(qué campo/escenario/criterio y por qué). Si el archivo no existe o es ilegible → pass:false con el motivo.
|
|
73
|
+
NO edites nada; NO valides otras HU ni criterios de épica.`,
|
|
74
|
+
{ label: `dor:${hu}`, phase: 'HUs', agentType: 'Explore', schema: HU_CHECK_SCHEMA },
|
|
75
|
+
)
|
|
76
|
+
if (!v) return { hu, pass: false, faltantes: [`${hu}: sin veredicto`] }
|
|
77
|
+
return { hu, pass: v.pass === true, faltantes: v.faltantes || [] }
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return { hu, pass: false, faltantes: [`${hu}: el validador falló — revalidar en sesión`] }
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
// Consolidación FAIL-CLOSED + cobertura completa: shard ausente = hueco, nunca verde.
|
|
84
|
+
const done = checks.filter(Boolean)
|
|
85
|
+
const missing = hus.length - done.length
|
|
86
|
+
const bad = done.filter((c) => !c.pass)
|
|
87
|
+
const all_pass = bad.length === 0 && missing === 0
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
epica,
|
|
91
|
+
all_pass, // pass per-HU; NO es el DoR completo (falta el nivel épica)
|
|
92
|
+
hus: done, // diagnóstico por HU para el reporte ✓/✗ del gatekeeper
|
|
93
|
+
faltantes: [
|
|
94
|
+
...bad.flatMap((c) => c.faltantes),
|
|
95
|
+
...(missing > 0 ? [`${missing} HU(s) sin resultado — cobertura incompleta, revalidar en sesión`] : []),
|
|
96
|
+
],
|
|
97
|
+
note: 'Read-only. dor-dod-gatekeeper combina esto con los criterios de NIVEL ÉPICA (dependencias, cimiento, tamaño, stack, diseño, ADR) y es el único que emite el veredicto DoR y abre active_slice.',
|
|
98
|
+
}
|
|
@@ -48,7 +48,7 @@ Ver `workflows/README.md`. Hoy: `workflows/release-gate.workflow.js`.
|
|
|
48
48
|
| `smell` | 4 reglas de Beck + code smells sobre el diff acumulado | `simple-design-reviewer` | `building-a-slice/references/simple-design.md` |
|
|
49
49
|
| `ux` | Krug + lighthouse sobre la UI ensamblada de la release (o `null` si sin UI) | `ux-krug-reviewer` | `building-a-slice/references/krug-ux.md` |
|
|
50
50
|
| `coherence` | Trazabilidad triple AC↔change↔código de **todas** las HU de la release | `coherence-three-way` | — |
|
|
51
|
-
| `stack_arch` | Arquitectura del PRD del consumidor (capa de servicios externos en la frontera declarada, capa de decisión determinista del dominio sin IA) | `stack-guardian` | — |
|
|
51
|
+
| `stack_arch` | Arquitectura del PRD del consumidor (capa de servicios externos en la frontera declarada, capa de decisión determinista del dominio sin IA) y, si el proyecto adoptó la capa de arquitectura (`/build:architect`), conformidad del diff con los ADRs `accepted` de `docs/adr/` | `stack-guardian` | — |
|
|
52
52
|
| `integration` | Recorrer el **journey completo** de la release con **deps reales** del proyecto, no stubs | skill `verify` / `run` (+ MCP chrome-devtools) | `release-dod.md` |
|
|
53
53
|
|
|
54
54
|
Checklist de cierre: `references/release-dod.md`.
|
|
@@ -66,7 +66,10 @@ Checklist de cierre: `references/release-dod.md`.
|
|
|
66
66
|
> **Opcional — conducir con workflow (releases grandes).** El fan-out del paso 3 puede conducirse con la
|
|
67
67
|
> plantilla `workflows/release-gate.workflow.js` (referencia, no obligatoria): SOLO paraleliza los 5 reviewers
|
|
68
68
|
> pesados; el gate `integration` (paso 4) sigue siendo **secuencial**, vía `verify`/`run` con **deps reales**,
|
|
69
|
-
> **fuera** del `parallel()`.
|
|
69
|
+
> **fuera** del `parallel()`. Pasa por `args` lo que computes **read-only**: `diffRange`, `hasUI` y **`hus[]`**
|
|
70
|
+
> (los IDs de todas las HU de las épicas de la release) — con ≥ 3 HUs el carril `coherence` se shardea por HU
|
|
71
|
+
> (lossless, fail-closed) en vez de recorrerlas en un solo agente; con menos, corre monolítico como siempre.
|
|
72
|
+
> El resultado se escribe igual en `releases[]` respetando **una escritura por
|
|
70
73
|
> entrada** y **validando contra el schema**; esta skill sigue siendo la única escritora. Parciales NO
|
|
71
74
|
> promueven a `passed`.
|
|
72
75
|
|
|
@@ -8,7 +8,7 @@ corre **una vez** sobre el diff acumulado de todas sus épicas. Resultado en `bu
|
|
|
8
8
|
- [ ] **`smell`** — `simple-design-reviewer` sin bloqueantes sobre el diff acumulado; 4 reglas de Beck respetadas.
|
|
9
9
|
- [ ] **`ux`** — `ux-krug-reviewer` ok sobre la UI ensamblada (o `null` si la release no tiene UI). Lighthouse/accesibilidad si la app corre.
|
|
10
10
|
- [ ] **`coherence`** — `coherence-three-way` confirma trazabilidad AC↔change↔código de **todas** las HU de **todas** las épicas de la release, sin huérfanos.
|
|
11
|
-
- [ ] **`stack_arch`** — `stack-guardian` confirma la arquitectura del PRD del consumidor: la capa de servicios externos/IA en la frontera declarada server-side (no decide), la capa de decisión determinista del dominio sin IA, sin claves de servicios externos en cliente.
|
|
11
|
+
- [ ] **`stack_arch`** — `stack-guardian` confirma la arquitectura del PRD del consumidor: la capa de servicios externos/IA en la frontera declarada server-side (no decide), la capa de decisión determinista del dominio sin IA, sin claves de servicios externos en cliente. Si existe `docs/adr/` (capa de arquitectura, `/build:architect`), confirma además que el diff acumulado **no contradice** los ADRs `accepted` (estilo, tácticas por driver, divergencias de stack registradas); sin la capa, esa parte es N/A.
|
|
12
12
|
- [ ] **`integration`** — el **journey completo** de la release se recorre end-to-end con **dependencias reales** del proyecto (servicios externos/IA y capa de decisión reales, según el PRD del consumidor), no stubs. Verificado con la skill `verify`/`run` (+ MCP `chrome-devtools`). Se corre **fuera** del fan-out
|
|
13
13
|
paralelo de reviewers (es **secuencial**, con deps reales); ver `../workflows/release-gate.workflow.js`.
|
|
14
14
|
|
|
@@ -16,4 +16,9 @@
|
|
|
16
16
|
|
|
17
17
|
## Plantillas
|
|
18
18
|
- **`release-gate.workflow.js`** — `parallel(5 reviewers)` → `integration` secuencial → síntesis a
|
|
19
|
-
`releases[].gates.{security, smell, ux, coherence, stack_arch, integration}`.
|
|
19
|
+
`releases[].gates.{security, smell, ux, coherence, stack_arch, integration}`. El carril **`coherence`**
|
|
20
|
+
se shardea **por HU** cuando la release tiene ≥ 3 HUs (`args.hus[]`, computadas read-only por la skill):
|
|
21
|
+
la trazabilidad triple de cada HU es independiente → el sharding es *lossless* y el carril más lento pasa
|
|
22
|
+
de O(HUs) a O(1) + consolidación **fail-closed** con cobertura completa (shard fallido/ausente = gate
|
|
23
|
+
false, nunca muestreo). `security`/`smell`/`ux`/`stack_arch` quedan **monolíticos a propósito**:
|
|
24
|
+
shardearlos por archivos puede perder hallazgos cross-cutting.
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
export const meta = {
|
|
21
21
|
name: 'release-gate',
|
|
22
|
-
description: 'Reviewers pesados en paralelo + integración secuencial + síntesis para el Release Gate (outer loop). Read-only; devuelve veredictos, no escribe estado.',
|
|
22
|
+
description: 'Reviewers pesados en paralelo (coherence shardeado por HU cuando la release es grande) + integración secuencial + síntesis para el Release Gate (outer loop). Read-only; devuelve veredictos, no escribe estado.',
|
|
23
23
|
phases: [
|
|
24
|
-
{ title: 'Reviewers', detail: '5 reviewers
|
|
24
|
+
{ title: 'Reviewers', detail: '5 reviewers en paralelo; el carril coherence fan-out por HU (≥3 HUs) con consolidación fail-closed' },
|
|
25
25
|
{ title: 'Integration', detail: 'journey completo con deps reales (SECUENCIAL, fuera del parallel)' },
|
|
26
26
|
],
|
|
27
27
|
}
|
|
@@ -35,24 +35,79 @@ const RELEASE_REVIEW_SCHEMA = {
|
|
|
35
35
|
},
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// diffRange
|
|
38
|
+
// diffRange, si la release tiene UI y la lista de HUs llegan por `args` (la skill los computa READ-ONLY).
|
|
39
|
+
// args.hus : string[] — IDs de TODAS las HU de las épicas de la release (para shardear coherence).
|
|
40
|
+
// Ausente o < 3 → coherence corre monolítico (comportamiento previo, sin inflar releases chicas).
|
|
39
41
|
const diffRange = (args && args.diffRange) || '<merge-anterior>..main'
|
|
40
42
|
const hasUI = !!(args && args.hasUI)
|
|
43
|
+
const hus = (args && Array.isArray(args.hus)) ? args.hus.filter(Boolean) : []
|
|
44
|
+
|
|
45
|
+
// --- Carril coherence: SHARDING por HU (lossless) ----------------------------
|
|
46
|
+
// La trazabilidad triple AC↔change↔código de cada HU es INDEPENDIENTE de las demás: shardearla
|
|
47
|
+
// no pierde hallazgos cruzados (a diferencia de security/smell, que quedan monolíticos a propósito).
|
|
48
|
+
// Consolidación FAIL-CLOSED y cobertura COMPLETA: un shard fallido/ausente = gate false, nunca
|
|
49
|
+
// muestreo silencioso. Con < 3 HUs el fan-out no paga su overhead → monolítico como siempre.
|
|
50
|
+
async function coherenceLane() {
|
|
51
|
+
if (hus.length < 3) {
|
|
52
|
+
try {
|
|
53
|
+
const v = await agent(
|
|
54
|
+
`Eres el reviewer pesado del Release Gate para el gate "coherence". Verifica READ-ONLY la trazabilidad
|
|
55
|
+
triple AC↔change↔código de TODAS las HU de la release sobre el diff acumulado (${diffRange}). Si NO puedes
|
|
56
|
+
verificar, devuelve pass:false con el motivo — nunca PASS por defecto.`,
|
|
57
|
+
{ label: 'release:coherence', agentType: 'coherence-three-way', phase: 'Reviewers', schema: RELEASE_REVIEW_SCHEMA },
|
|
58
|
+
)
|
|
59
|
+
if (!v) return { gate: 'coherence', value: false, error: 'sin veredicto' }
|
|
60
|
+
return { gate: 'coherence', value: v.pass === true, findings: v.findings || [] }
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return { gate: 'coherence', value: false, error: 'el reviewer falló' }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
log(`coherence: fan-out por HU (${hus.length} shards — cobertura completa, sin muestreo)`)
|
|
66
|
+
const shards = await parallel(hus.map((hu) => async () => {
|
|
67
|
+
try {
|
|
68
|
+
const v = await agent(
|
|
69
|
+
`Eres un shard del reviewer "coherence" del Release Gate. Verifica READ-ONLY la trazabilidad triple
|
|
70
|
+
AC↔change↔código de UNA SOLA HU: ${hu}. Lee sus AC (Given/When/Then), el change OpenSpec que la cubre y el
|
|
71
|
+
código/tests del diff acumulado (${diffRange}) que la implementan. pass:true SOLO si cada AC de ${hu} tiene
|
|
72
|
+
test real y código cableado, sin huérfanos. Si NO puedes verificar → pass:false con el motivo.`,
|
|
73
|
+
{ label: `release:coherence:${hu}`, agentType: 'coherence-three-way', phase: 'Reviewers', schema: RELEASE_REVIEW_SCHEMA },
|
|
74
|
+
)
|
|
75
|
+
if (!v) return { hu, pass: false, findings: [`${hu}: sin veredicto`] }
|
|
76
|
+
return { hu, pass: v.pass === true, findings: (v.findings || []).map((f) => `${hu}: ${f}`) }
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return { hu, pass: false, findings: [`${hu}: el shard falló`] }
|
|
79
|
+
}
|
|
80
|
+
}))
|
|
81
|
+
const done = shards.filter(Boolean)
|
|
82
|
+
const missing = hus.length - done.length // shard ausente (skip/kill) = hueco de cobertura
|
|
83
|
+
const bad = done.filter((s) => !s.pass)
|
|
84
|
+
const value = bad.length === 0 && missing === 0
|
|
85
|
+
return {
|
|
86
|
+
gate: 'coherence', value,
|
|
87
|
+
findings: [
|
|
88
|
+
...bad.flatMap((s) => s.findings),
|
|
89
|
+
...(missing > 0 ? [`coherence: ${missing} shard(s) sin resultado — cobertura incompleta, gate false`] : []),
|
|
90
|
+
],
|
|
91
|
+
}
|
|
92
|
+
}
|
|
41
93
|
|
|
42
94
|
// --- PASO A · Reviewers pesados EN PARALELO (exactamente estos 5) ------------
|
|
43
95
|
// Cada uno delega en su subagente sobre el MISMO diff acumulado y devuelve síntesis.
|
|
44
96
|
// Barrera deliberada: la síntesis de release necesita los 5 veredictos juntos.
|
|
97
|
+
// security/smell/ux/stack_arch quedan MONOLÍTICOS a propósito: shardearlos por archivos
|
|
98
|
+
// puede perder hallazgos cross-cutting (p.ej. un bypass de auth visible solo entre módulos).
|
|
45
99
|
phase('Reviewers')
|
|
46
100
|
const REVIEWERS = [
|
|
47
101
|
{ gate: 'security', agentType: 'security-reviewer' },
|
|
48
102
|
{ gate: 'smell', agentType: 'simple-design-reviewer' },
|
|
49
103
|
{ gate: 'ux', agentType: 'ux-krug-reviewer' }, // null SOLO si la release no tiene UI
|
|
50
|
-
{ gate: 'coherence', agentType: 'coherence-three-way' },
|
|
104
|
+
{ gate: 'coherence', agentType: 'coherence-three-way' }, // carril con sharding interno por HU (ver coherenceLane)
|
|
51
105
|
{ gate: 'stack_arch', agentType: 'stack-guardian' },
|
|
52
106
|
]
|
|
53
107
|
const reviews = await parallel(REVIEWERS.map((r) => async () => {
|
|
54
108
|
// N/A legítimo: ux sin UI → null (NO es fallo). El resto SIEMPRE corre.
|
|
55
109
|
if (r.gate === 'ux' && !hasUI) return { gate: r.gate, value: null, na: true }
|
|
110
|
+
if (r.gate === 'coherence') return coherenceLane()
|
|
56
111
|
try {
|
|
57
112
|
const v = await agent(
|
|
58
113
|
`Eres el reviewer pesado del Release Gate para el gate "${r.gate}". Revisa READ-ONLY el diff acumulado de la
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup-architecture
|
|
3
|
+
description: Use BEFORE building — after discovery (PRD + user story map + epicas + HUs exist) and before the first slice — to produce the project's architecture layer as ADRs using the ADD method (Attribute-Driven Design, Len Bass). Reads docs/ read-only, extracts architectural drivers/ASRs, drives ADD iterations (tactics → styles → views → ATAM-lite analysis), writes docs/adr/ (0000 drivers catalog + numbered ADRs + architectural backlog) and populates .claude/config/stack-allowlist.json. Runs autonomously and proposes maximally — stops for a single human review and only escalates genuine business trade-offs. The ADRs become build criteria: the DoR gate requires driver coverage and the release-gate stack_arch audits conformance. Delegates driver extraction to asr-extractor and evaluation to architecture-evaluator; never publishes, only proposes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Definir la arquitectura (método ADD) — capa previa a construcción
|
|
7
|
+
|
|
8
|
+
Genera la **capa de arquitectura** del proyecto **antes** de que empiece el inner loop de
|
|
9
|
+
`building-a-slice`. Su objetivo es que el agente entre a construir con las decisiones técnicas ya
|
|
10
|
+
claras (estilo, tácticas, stack), y que esas decisiones se vuelvan **criterios exigibles** por los gates
|
|
11
|
+
del arnés. Sigue el método **ADD** (Attribute-Driven Design, Len Bass — *Software Architecture in
|
|
12
|
+
Practice*), cuyos pasos son los 8 del diseño arquitectónico:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Negocio → ASRs → Tácticas → Estilos → Vistas → Evaluación (ATAM) → Stack → Código
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Dónde encaja.** Corre **después de discovery** (`@trycore/spec-product-flow` dejó listos PRD, user
|
|
19
|
+
> story map, `docs/03-backlog/epicas.md` y `docs/04-historias/HU-*.md`) y **antes** del primer slice. No
|
|
20
|
+
> reemplaza al Release Gate: lo **habilita**. El gate `stack_arch` sigue auditando en el outer loop, pero
|
|
21
|
+
> ahora contra los ADRs que esta capa produce.
|
|
22
|
+
|
|
23
|
+
## Principio de operación
|
|
24
|
+
|
|
25
|
+
- **Entrada de solo lectura: `docs/`** (PRD, story map, épicas, HUs). Esta skill **NO escribe** en los
|
|
26
|
+
artefactos de discovery (`docs/01-prd/`…`docs/04-historias/`). Su **única** salida en `docs/` es el
|
|
27
|
+
subárbol **`docs/adr/`** (propiedad de construcción; carve-out declarado en `METODOLOGIA.md`).
|
|
28
|
+
- **Propone, no publica.** Genera los `.md` con estado `proposed` / `living-document`. La promoción a
|
|
29
|
+
`accepted` la hace un humano en la revisión única (fase 5). Forward-compat v0.9: los tipos viven en
|
|
30
|
+
`asset-types.json`; cuando exista el runtime, la propuesta irá por API (`POST /context/proposals`).
|
|
31
|
+
- **Autónoma y de propuesta máxima.** La IA rellena todo el catálogo y todos los ADRs de una pasada;
|
|
32
|
+
**no** pregunta campo por campo. Solo se detiene ante **trade-offs de negocio** genuinos (ver fase 5).
|
|
33
|
+
- **Delega la exploración pesada en subagentes** (`asr-extractor`, `architecture-evaluator`): devuelven
|
|
34
|
+
síntesis condensada y protegen el presupuesto de atención de la sesión principal.
|
|
35
|
+
- **Divulgación progresiva:** carga el `references/<tema>.md` solo cuando la fase lo necesita.
|
|
36
|
+
- **El estado es el archivo.** `docs/adr/_backlog-arquitectonico.md` es el estado auto-descriptivo de la
|
|
37
|
+
capa (no toca `build-state.json`). Re-correr = **incremental**: añade iteraciones, no regenera.
|
|
38
|
+
|
|
39
|
+
## Fases (carga la referencia indicada en cada paso)
|
|
40
|
+
|
|
41
|
+
| Fase | Acción | Delega en | Salida | Referencia |
|
|
42
|
+
|---|---|---|---|---|
|
|
43
|
+
| 0 · preflight | Verifica discovery listo (PRD + `epicas.md` + ≥1 HU). Si falta → **STOP**, remite a `/trycore:*`. Solo lectura. | — | — | — |
|
|
44
|
+
| 1 · drivers | Extrae el catálogo de drivers/ASRs (OE, UC significativos, escenarios QA de 6 partes, CON, CRN, matriz de prioridad, plan de iteraciones) | `asr-extractor` (fan-out sobre `docs/`) | `docs/adr/0000-drivers-y-asrs.md` | `drivers-extraction.md` |
|
|
45
|
+
| 2 · iterar ADD | Por ronda (una por fase PRD / línea de release): selecciona drivers → elige **tácticas y estilos** → instancia responsabilidades/interfaces → boceta **vistas** (mermaid) | sesión principal + `references/add-method.md` | `docs/adr/000N-*.md` (plantilla 7 secciones) | `add-method.md` |
|
|
46
|
+
| 3 · evaluar (ATAM) | Análisis adversarial de cada ADR: puntos de sensibilidad, trade-offs, riesgos, drivers no cubiertos | `architecture-evaluator` | actualiza §5 del ADR + riesgos del backlog | `atam-lite.md` |
|
|
47
|
+
| 4 · backlog | Actualiza el tablero de cobertura driver↔ADR, riesgos abiertos y bitácora de iteraciones | sesión principal | `docs/adr/_backlog-arquitectonico.md` | `atam-lite.md` |
|
|
48
|
+
| 5 · revisión | Presenta el set completo; escala **solo** trade-offs de negocio (una `AskUserQuestion` batcheada); al aceptar promueve `proposed → accepted` y consolida el stack | usuario (default computado) | `.claude/config/stack-allowlist.json` + estados `accepted` | `add-method.md` |
|
|
49
|
+
|
|
50
|
+
**Plantillas de salida.** La spec canónica de cada archivo vive en **`assets/` de esta skill** —se
|
|
51
|
+
instala con ella en ambos canales—: `assets/0000-drivers-y-asrs.template.md`, `assets/adr-add.template.md`
|
|
52
|
+
y `assets/_backlog-arquitectonico.template.md` (única fuente de verdad de la estructura; declarada para el
|
|
53
|
+
runtime v0.9 vía `asset-types.json`). La skill **lee la plantilla y escribe** en `docs/adr/`; crea el
|
|
54
|
+
directorio si no existe. **No pises** ADRs ya `accepted` (re-corrida incremental).
|
|
55
|
+
|
|
56
|
+
## Fase 5 · Revisión única (mínimo HITL)
|
|
57
|
+
|
|
58
|
+
Al terminar de generar 0000 + los ADRs + el backlog:
|
|
59
|
+
|
|
60
|
+
1. **Resumen** de una pantalla: nº de drivers por tipo, nº de ADRs, estilo(s) elegidos, stack propuesto,
|
|
61
|
+
y **riesgos abiertos** del backlog.
|
|
62
|
+
2. **Escala solo lo que es decisión de negocio**, no de arquitectura, con **una sola** `AskUserQuestion`
|
|
63
|
+
que agrupe todos los trade-offs pendientes (p.ej.: "¿el historial de versiones es visible para
|
|
64
|
+
consumidores o solo auditores?", "¿se fijan objetivos de DR (RTO/RPO) ahora o se aplazan?"). Todo lo
|
|
65
|
+
demás la IA ya lo decidió y lo dejó registrado con su rationale. Sin TTY/headless: **no bloquees**;
|
|
66
|
+
deja los trade-offs como riesgos abiertos en el backlog y emítelos por stdout.
|
|
67
|
+
3. Al aceptar: promueve los ADRs de decisión `proposed → accepted`, añade la fila **Cierre** a la
|
|
68
|
+
bitácora, y **consolida el stack** → actualiza `.claude/config/stack-allowlist.json` con **merge
|
|
69
|
+
aditivo, nunca overwrite**: añade a `allow` las deps decididas y a `rationale` la referencia al ADR
|
|
70
|
+
que las justifica (p.ej. `"fastapi": "ADR-0001"`); **preserva `source`** (ese campo es el contrato
|
|
71
|
+
del arnés: la ruta a la sección técnica del PRD del consumidor — NO lo pises) y no borra entradas
|
|
72
|
+
que el consumidor haya puesto a mano. Desde aquí, `stack-guard.sh` ya hace cumplir la lista.
|
|
73
|
+
|
|
74
|
+
## Cómo alimenta al build (los ADRs son criterios)
|
|
75
|
+
|
|
76
|
+
- **`stack-allowlist.json`** — lo puebla esta capa; el hook `stack-guard.sh` (PreToolUse) bloquea deps
|
|
77
|
+
fuera de lista en tiempo real.
|
|
78
|
+
- **DoR** — `dor-dod-gatekeeper` exige que los drivers arquitectónicamente significativos de la épica
|
|
79
|
+
(sobre todo `layer: foundational`) tengan un ADR con estado ≥ `ABORDADO` en el backlog (ver
|
|
80
|
+
`building-a-slice/references/dor.md`).
|
|
81
|
+
- **`stack_arch`** (Release Gate) — `stack-guardian` audita el diff acumulado contra las decisiones de
|
|
82
|
+
`docs/adr/`, no solo contra la allowlist.
|
|
83
|
+
|
|
84
|
+
## Guardrails
|
|
85
|
+
|
|
86
|
+
- **Solo lectura sobre discovery.** Nunca edites `docs/01-prd/`…`docs/04-historias/`. La única escritura
|
|
87
|
+
en `docs/` es `docs/adr/`.
|
|
88
|
+
- **Propón, no publiques.** Estados `proposed`/`living-document`; la promoción a `accepted` es del humano.
|
|
89
|
+
- **Ante la duda técnica, decide y registra** (con alternativas descartadas y rationale). **Ante la duda
|
|
90
|
+
de negocio, escala** en la revisión única. No confundas una con otra.
|
|
91
|
+
- **Incremental en re-corridas.** No regeneres ADRs ya `accepted`; añade una iteración nueva a la bitácora.
|
|
92
|
+
- **Agnóstico.** El vocabulario ADD (drivers, tácticas, estilos, vistas) es genérico del arnés; el
|
|
93
|
+
dominio entra **por `docs/`**, nunca hardcodeado en la skill.
|
|
94
|
+
- Si algo contradice `METODOLOGIA.md`, **gana la metodología**.
|