@trycore/spec-build-harness 0.8.2 → 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/README.md +2 -1
- package/VERSION +1 -1
- package/agents/build/dor-dod-gatekeeper.md +8 -0
- package/package.json +1 -1
- 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 +4 -1
- package/skills/releasing-a-version/workflows/README.md +6 -1
- package/skills/releasing-a-version/workflows/release-gate.workflow.js +59 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "trycore-spec-build-harness",
|
|
4
4
|
"displayName": "Trycore — Spec & Build Harness",
|
|
5
|
-
"version": "0.8.
|
|
5
|
+
"version": "0.8.3",
|
|
6
6
|
"description": "Arnés de construcción de dos loops (slice por épica + release gate) para Claude Code, con gates de calidad, estado compartido y OpenSpec. Compañero de @trycore/spec-product-flow. Agnóstico al proyecto.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Trycore",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"trycore"
|
|
23
23
|
],
|
|
24
24
|
"skills": "./skills/",
|
|
25
|
-
"commands": [
|
|
25
|
+
"commands": [
|
|
26
|
+
"./commands/opsx/",
|
|
27
|
+
"./commands/build/"
|
|
28
|
+
],
|
|
26
29
|
"agents": "./agents/build/",
|
|
27
30
|
"hooks": "./hooks/build-harness.json"
|
|
28
31
|
}
|
package/README.md
CHANGED
|
@@ -183,7 +183,8 @@ El core no menciona ningún dominio de cliente. Toda parametrización entra por
|
|
|
183
183
|
- ✅ **v0.7.0** — **orquestación con workflows dinámicos + hardening** (de una evaluación adversarial del propio arnés): **3 plantillas `*.workflow.js`** opt-in y read-only (`explore-fanout`, `wiring-verify`, `release-gate`) que entran **solo donde aportan valor** y nunca en el camino caliente del inner loop; **3 comandos nuevos** `/build:slice` (entrada del inner loop), `/build:release` (outer loop) y `/build:work` (router *classify-and-act*); hook **`release-gate-nudge.sh`** (Stop, determinista: solo sugiere el Release Gate). Rename de los gates de los 5 reviewers pesados → `releases[].gates.{security,smell,ux,coherence,stack_arch}` (`stack`→`stack_arch`; separación `coherence` (release) / `coherence_link` (inner)). Hardening: degradación segura en 8 agentes, escritura atómica del estado, cierre del bypass de specs no-semver, `wiring` exige evidencia ejecutada y `check-agnostic` barre `*.js`. Total: **12 agentes**, **10 hooks**, **5 comandos `/build:*`**.
|
|
184
184
|
- ✅ **v0.8.0** — **motor de contexto + estado anclado a disco + front paralelo inter-épica**: hooks **`statusline-bridge.sh`** (canal CLI) + **`context-monitor.sh`** (umbrales `context.warning_pct`/`context.critical_pct` configurables, 35%/25% por defecto) con **auto-handoff** a `session_continuity` en critical/`PreCompact` y comando **`/build:resume`** para rehidratar desde disco; config **`context.auto_checkpoint`** (opt-in). Reconciliador **`reconcile-build-state.py`** (`SessionStart`): deriva de git + evidencia de tests, degrada `wiring_checklist` sin evidencia, anota *branch drift*, ratchet de gates, fail-open. **Front paralelo** (`parallel_front` en el estado): comando **`/build:front`** + skill **`managing-parallel-front`** + `scripts/lib/front-plan.py` (disjunción por `files_scope`, foundational-first). Schema nuevo: `slice.layer`, `slice.files_scope`, `slice.branch_drift`, `slice.session_continuity`. **Caveat:** `statusLine` es solo canal CLI; en plugin-only el motor de contexto degrada fail-open. Total: **12 agentes**, **13 hooks**, **7 comandos `/build:*`**, **14 skills**.
|
|
185
185
|
- ✅ **v0.8.1** — **hotfix del motor de contexto**: `context-monitor.sh` re-inyectaba `additionalContext` en cada evento `Stop`, lo que re-lanzaba el turno en bucle hasta el tope `CLAUDE_CODE_STOP_HOOK_BLOCK_CAP` (9→override). Ahora en `Stop` re-lanza como mucho una vez por sesión (solo la transición a crítico que graba el handoff) y `warning` nunca inyecta en `Stop`. Cubierto por `test-context-monitor.sh`.
|
|
186
|
-
- ✅ **v0.8.
|
|
186
|
+
- ✅ **v0.8.3 (actual)** — **gates de validación paralelizados (sharding lossless)**: el carril `coherence` del Release Gate se shardea **por HU** (≥ 3 HUs, `args.hus[]`) — de un solo agente opus O(HUs) a un shard por HU en paralelo con consolidación **fail-closed** y cobertura completa; nueva plantilla **`dor-fanout.workflow.js`** para los chequeos per-HU del DoR (frontmatter/G-W-T/INVEST en paralelo; el nivel épica sigue en `dor-dod-gatekeeper`, único emisor del veredicto). `security`/`smell`/`ux`/`stack_arch` quedan monolíticos a propósito (riesgo cross-cutting); `integration` sigue secuencial (regla dura §5).
|
|
187
|
+
- ✅ **v0.8.2** — **capa de arquitectura (ADD)** entre discovery y construcción: comando **`/build:architect`** + skill **`setup-architecture`** que aplica el método **Attribute-Driven Design** (Len Bass) leyendo `docs/` (solo lectura) y produciendo `docs/adr/` (drivers/ASRs → tácticas → estilos → vistas → ATAM-lite → stack), con **mínimo HITL** (autónomo, una revisión final; propone, no publica). Dos agentes nuevos (`asr-extractor`, `architecture-evaluator`), plantillas ADD embebidas en la skill (`skills/setup-architecture/assets/`) y **`asset-types.json`** (forward-compat runtime v0.9: `arch.drivers`/`arch.adr`/`arch.backlog`). Cierre del lazo: los ADRs se vuelven criterios — el **DoR** exige cobertura para el cimiento fundacional (opt-in, retrocompatible) y el gate **`stack_arch`** audita conformidad contra `docs/adr/`. Total: **14 agentes**, **13 hooks**, **8 comandos `/build:*`**, **15 skills**.
|
|
187
188
|
|
|
188
189
|
## Licencia
|
|
189
190
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.8.
|
|
1
|
+
0.8.3
|
|
@@ -19,6 +19,14 @@ las fases de código.)
|
|
|
19
19
|
La unidad es la **épica**. Identifica `EP-XXX` en `docs/03-backlog/epicas.md` y el conjunto de HU
|
|
20
20
|
que la componen (las que tienen `epica: EP-XXX` en `docs/04-historias/`). Pasa SOLO si **todas** se
|
|
21
21
|
cumplen; lista cada una con ✓/✗:
|
|
22
|
+
|
|
23
|
+
> **Conducción opcional — fan-out per-HU (épicas con ≥ 3 HUs).** Los criterios 3-5 (frontmatter, AC
|
|
24
|
+
> G/W/T, INVEST) son por-HU e independientes: puedes conducirlos con la plantilla
|
|
25
|
+
> `building-a-slice/workflows/dor-fanout.workflow.js` (read-only; `args: {epica, hus[]}`) para validarlos
|
|
26
|
+
> **en paralelo** en vez de HU por HU. La plantilla devuelve el diagnóstico per-HU con consolidación
|
|
27
|
+
> fail-closed (HU fallida o shard ausente = false); tú **combinas** ese diagnóstico con los criterios de
|
|
28
|
+
> nivel épica (1-2 y 6-10, que valides tú en sesión) y sigues siendo el **único** que emite el veredicto
|
|
29
|
+
> DoR y abre `active_slice`. Con < 3 HUs la plantilla se auto-salta: valida secuencial como siempre.
|
|
22
30
|
1. La épica existe en `docs/03-backlog/epicas.md` con su trazabilidad a objetivos del PRD.
|
|
23
31
|
2. Tiene **al menos una HU** asociada y todas se enumeran en `hus[]`.
|
|
24
32
|
3. **Cada HU** de la épica: frontmatter YAML completo (`id, titulo, epica, prioridad, complejidad,
|
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": {
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
|
@@ -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
|