@saulwade/swl-ses 2.6.1 → 2.8.0

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.
Files changed (268) hide show
  1. package/CLAUDE.md +14 -2
  2. package/README.md +65 -18
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/bin/swl-ses.js +10 -0
  6. package/comandos/swl/brainstorm.md +1 -0
  7. package/comandos/swl/briefing.md +119 -119
  8. package/comandos/swl/contribuir.md +233 -233
  9. package/comandos/swl/deuda-codigo.md +97 -97
  10. package/comandos/swl/mcp-status.md +1 -0
  11. package/gateway/lib/event-channel.js +191 -191
  12. package/habilidades/agent-deep-links/SKILL.md +148 -148
  13. package/habilidades/backend-async-postgres-testing/SKILL.md +216 -216
  14. package/habilidades/backend-error-design/SKILL.md +221 -221
  15. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  16. package/habilidades/calidad-anti-patrones-universales/SKILL.md +105 -1
  17. package/habilidades/calidad-contract-testing/SKILL.md +165 -165
  18. package/habilidades/calidad-mutation-testing/SKILL.md +25 -1
  19. package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
  20. package/habilidades/ci-cd-pipelines/SKILL.md +5 -1
  21. package/habilidades/css-moderno/SKILL.md +7 -1
  22. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  23. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  24. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
  25. package/habilidades/extractor-de-aprendizajes/SKILL.md +5 -1
  26. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  27. package/habilidades/harness-claude-code/SKILL.md +3 -2
  28. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  29. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  30. package/habilidades/perfil-usuario/SKILL.md +200 -200
  31. package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
  32. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  33. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  34. package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
  35. package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
  36. package/habilidades/proceso-dynamic-workflows/SKILL.md +60 -0
  37. package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
  38. package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
  39. package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
  40. package/habilidades/proceso-modular-split/SKILL.md +256 -256
  41. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  42. package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
  43. package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
  44. package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
  45. package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
  46. package/hooks/calidad-pre-commit.js +159 -10
  47. package/hooks/ciclo-evolucion-subagente.js +26 -26
  48. package/hooks/ciclo-evolucion.js +26 -26
  49. package/hooks/contexto-subagente.js +68 -68
  50. package/hooks/lib/auto-consolidator.js +335 -335
  51. package/hooks/lib/ciclo-evolucion.js +47 -47
  52. package/hooks/lib/deep-links.js +185 -185
  53. package/hooks/lib/error-classifier.js +308 -308
  54. package/hooks/lib/notificacion-formato.js +45 -11
  55. package/hooks/lib/provenance-tracker.js +191 -191
  56. package/hooks/lib/raiz-proyecto.js +35 -4
  57. package/hooks/lib/resource-quota.js +122 -122
  58. package/hooks/lib/retry-jitter.js +165 -165
  59. package/hooks/lib/security-net.js +201 -201
  60. package/hooks/lib/skill-auditor.js +588 -588
  61. package/hooks/lib/sync-status.js +228 -228
  62. package/hooks/lib/taint-tracker.js +107 -107
  63. package/hooks/lib/text-similarity.js +241 -241
  64. package/hooks/lib/toon-compressor.js +245 -245
  65. package/hooks/notificacion-telegram.js +5 -11
  66. package/hooks/session-briefing.js +12 -4
  67. package/instintos/autonomia.yaml +27 -27
  68. package/instintos/prompt-appendices.yaml +57 -57
  69. package/llms.txt +1 -1
  70. package/manifiestos/agent-output-schemas.json +57 -57
  71. package/manifiestos/canonical-hashes.json +662 -0
  72. package/manifiestos/harness-ir.json +47536 -0
  73. package/manifiestos/hooks-config.json +469 -469
  74. package/manifiestos/invariantes-criticos.json +30 -30
  75. package/manifiestos/policy-bundle.json +2065 -0
  76. package/manifiestos/policy-corpus-w2.json +3926 -0
  77. package/manifiestos/runtime-adapters-core3.json +208 -0
  78. package/manifiestos/runtime-conformance.json +139 -0
  79. package/manifiestos/skills-lock.json +43 -43
  80. package/package.json +2 -2
  81. package/plantillas/auditor-veto-template.md +105 -105
  82. package/plantillas/github-workflows/release-please.yml +44 -44
  83. package/plantillas/github-workflows/swl-ci.yml +107 -107
  84. package/plantillas/github-workflows/swl-security.yml +51 -51
  85. package/plugin.json +2 -2
  86. package/reglas/accesibilidad.md +10 -10
  87. package/reglas/auditorias-documentales-estructurales.md +7 -7
  88. package/reglas/cloud-infra.md +8 -8
  89. package/reglas/consultar-vault-primero.md +195 -195
  90. package/reglas/git-workflow.md +1 -0
  91. package/reglas/hooks.md +6 -6
  92. package/reglas/intent-engineering.md +218 -218
  93. package/reglas/markitdown.md +8 -8
  94. package/reglas/monitor-ci.md +12 -0
  95. package/reglas/patrones.md +6 -6
  96. package/reglas/testing.md +7 -7
  97. package/reglas/tests-cleanup.md +224 -224
  98. package/schemas/agent-message.schema.json +73 -73
  99. package/schemas/agent-output-implementacion.schema.json +114 -114
  100. package/schemas/agent-output-planificacion.schema.json +150 -150
  101. package/schemas/agent-output-review.schema.json +98 -98
  102. package/schemas/diary-entry.schema.json +112 -112
  103. package/schemas/gate-state.schema.json +76 -0
  104. package/schemas/harness-ir.schema.json +369 -0
  105. package/schemas/hook-profiles.schema.json +54 -54
  106. package/schemas/hooks-config.schema.json +89 -89
  107. package/schemas/legacy-gates.schema.json +45 -0
  108. package/schemas/modulos.schema.json +38 -38
  109. package/schemas/perfiles.schema.json +36 -36
  110. package/schemas/plugin.schema.json +77 -77
  111. package/schemas/policy-bundle.schema.json +140 -0
  112. package/schemas/policy-enforcement.schema.json +117 -0
  113. package/schemas/policy-operation.schema.json +261 -0
  114. package/schemas/runtime-adapter.schema.json +176 -0
  115. package/schemas/runtime-build-attestation.schema.json +100 -0
  116. package/schemas/runtime-conformance.schema.json +239 -0
  117. package/schemas/runtime-diagnostic.schema.json +395 -0
  118. package/schemas/skill-evals.schema.json +119 -119
  119. package/schemas/skill-frontmatter.schema.json +245 -245
  120. package/schemas/w4-certification-request.schema.json +72 -0
  121. package/schemas/w4-certification-verdict.schema.json +224 -0
  122. package/schemas/w4-corpus.schema.json +172 -0
  123. package/schemas/w4-mutation-report.schema.json +116 -0
  124. package/schemas/w4-replay-result.schema.json +164 -0
  125. package/schemas/w4-scoring-report.schema.json +89 -0
  126. package/scripts/audit-tools/audit-history.js +330 -330
  127. package/scripts/audit-tools/bundle-tracker.js +290 -290
  128. package/scripts/audit-tools/canary-monitor.js +352 -352
  129. package/scripts/audit-tools/code-profiler.js +605 -605
  130. package/scripts/audit-tools/dep-doctor.js +320 -320
  131. package/scripts/audit-tools/env-validator.js +206 -206
  132. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  133. package/scripts/audit-tools/lib/output.js +23 -23
  134. package/scripts/audit-tools/migration-checker.js +392 -392
  135. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  136. package/scripts/bootstrap-instintos.js +3 -0
  137. package/scripts/cli/aprobar-plan.js +73 -73
  138. package/scripts/cli/briefing.js +23 -23
  139. package/scripts/cli/ciclo-evolucion.js +26 -26
  140. package/scripts/cli/derivar-feature-list.js +25 -25
  141. package/scripts/cli/detectar-host.js +27 -27
  142. package/scripts/cli/diary-entry.js +69 -69
  143. package/scripts/cli/execution-state.js +18 -18
  144. package/scripts/cli/gateway-notify.js +41 -41
  145. package/scripts/cli/liberar-fase.js +42 -42
  146. package/scripts/cli/mark-evolved.js +56 -56
  147. package/scripts/cli/metricas-dora.js +26 -26
  148. package/scripts/cli/near-duplicate.js +55 -55
  149. package/scripts/cli/notificaciones.js +123 -123
  150. package/scripts/cli/propose-step.js +29 -29
  151. package/scripts/cli/schedule-parse.js +19 -19
  152. package/scripts/cli/sugerir-modelo.js +20 -20
  153. package/scripts/cli/verificar-plan.js +36 -36
  154. package/scripts/cli/verificar-trazabilidad.js +35 -35
  155. package/scripts/comandos/install-asistido.js +8 -7
  156. package/scripts/configurar-branch-protection.js +418 -418
  157. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  158. package/scripts/doctor.js +61 -36
  159. package/scripts/generar-checklists-consolidados.js +273 -273
  160. package/scripts/generar-claims-runtime.js +1342 -0
  161. package/scripts/generar-harness-ir.js +257 -0
  162. package/scripts/generar-inventario.js +52 -54
  163. package/scripts/generar-policy-bundle.js +202 -0
  164. package/scripts/instalador.js +26 -7
  165. package/scripts/lib/approval-receipts.js +190 -0
  166. package/scripts/lib/artefactos-python.js +43 -43
  167. package/scripts/lib/benchmark-metrics.js +160 -160
  168. package/scripts/lib/budget-enforcer.js +252 -252
  169. package/scripts/lib/certificacion-loop-state.js +421 -0
  170. package/scripts/lib/ci-reader.js +193 -193
  171. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +56 -0
  172. package/scripts/lib/clasificar-directorio.js +92 -0
  173. package/scripts/lib/contadores-inventario.js +217 -217
  174. package/scripts/lib/detectar-host-swl.js +175 -175
  175. package/scripts/lib/detectar-runtime.js +29 -20
  176. package/scripts/lib/detectar-stack-detallado.js +307 -307
  177. package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
  178. package/scripts/lib/detector-reglas-duplicadas.js +220 -220
  179. package/scripts/lib/eval-metrics-store.js +218 -218
  180. package/scripts/lib/eval-quality.js +171 -171
  181. package/scripts/lib/eval-schemas.js +144 -144
  182. package/scripts/lib/eval-self-correct.js +106 -106
  183. package/scripts/lib/eval-validator.js +185 -185
  184. package/scripts/lib/evidence-verifier.js +192 -0
  185. package/scripts/lib/evidencia-release.js +322 -322
  186. package/scripts/lib/frontmatter-canonico.js +509 -0
  187. package/scripts/lib/gate-engine.js +871 -0
  188. package/scripts/lib/gate-hooks-requires.js +249 -249
  189. package/scripts/lib/gate-licencias.js +212 -212
  190. package/scripts/lib/git-config-preflight.js +48 -0
  191. package/scripts/lib/git-metricas.js +257 -257
  192. package/scripts/lib/harness-ir.js +778 -0
  193. package/scripts/lib/harness-source-snapshot.js +309 -0
  194. package/scripts/lib/integrity-ledger.js +1147 -0
  195. package/scripts/lib/jaccard-similarity.js +98 -98
  196. package/scripts/lib/legacy-gate-migration.js +324 -0
  197. package/scripts/lib/limpiar-basura-global.js +45 -2
  198. package/scripts/lib/longmemeval-runner.js +125 -125
  199. package/scripts/lib/metricas-dora.js +204 -204
  200. package/scripts/lib/notificaciones-telegram.js +1 -0
  201. package/scripts/lib/npm-version.js +1 -0
  202. package/scripts/lib/paquetes-conocidos.js +50 -50
  203. package/scripts/lib/plan-lock.js +61 -13
  204. package/scripts/lib/policy-broker.js +338 -0
  205. package/scripts/lib/policy-bundle.js +342 -0
  206. package/scripts/lib/policy-context-provider.js +310 -0
  207. package/scripts/lib/policy-contract.js +479 -0
  208. package/scripts/lib/policy-verifier-utils.js +65 -0
  209. package/scripts/lib/pr-analyzer.js +399 -399
  210. package/scripts/lib/principal-verifier.js +178 -0
  211. package/scripts/lib/prompt-builder.js +264 -264
  212. package/scripts/lib/resolver-plan-fase.js +37 -37
  213. package/scripts/lib/rrf-fusion.js +175 -175
  214. package/scripts/lib/runtime-adapter-contract.js +267 -0
  215. package/scripts/lib/runtime-artifact-verifier.js +426 -0
  216. package/scripts/lib/runtime-build-attestation.js +127 -0
  217. package/scripts/lib/runtime-bundle-installer.js +586 -0
  218. package/scripts/lib/runtime-compiler.js +327 -0
  219. package/scripts/lib/runtime-conformance.js +202 -0
  220. package/scripts/lib/runtime-doctor-core3.js +567 -0
  221. package/scripts/lib/runtime-doctor-input.js +59 -0
  222. package/scripts/lib/runtime-operation-adapter.js +267 -0
  223. package/scripts/lib/schema-version.js +164 -164
  224. package/scripts/lib/semantic-search.js +252 -252
  225. package/scripts/lib/signed-envelope.js +545 -0
  226. package/scripts/lib/single-use-store.js +359 -0
  227. package/scripts/lib/skills-externas.js +31 -0
  228. package/scripts/lib/transformadores/codex.js +15 -8
  229. package/scripts/lib/transformadores/gemini.js +79 -5
  230. package/scripts/lib/w4-attestation-adapter.js +158 -0
  231. package/scripts/lib/w4-canario.js +337 -0
  232. package/scripts/lib/w4-claims.js +182 -0
  233. package/scripts/lib/w4-corpus-generador.js +542 -0
  234. package/scripts/lib/w4-gate-c5.js +115 -0
  235. package/scripts/lib/w4-harness-bajo-prueba.js +155 -0
  236. package/scripts/lib/w4-matriz-combos.js +55 -0
  237. package/scripts/lib/w4-motor-mutacion.js +1348 -0
  238. package/scripts/lib/w4-motor-replay.js +735 -0
  239. package/scripts/lib/w4-pin-origen.js +54 -0
  240. package/scripts/lib/w4-publicar-request.js +132 -0
  241. package/scripts/lib/w4-revocacion.js +62 -0
  242. package/scripts/lib/w4-runtimes-core3.js +38 -0
  243. package/scripts/lib/w4-scorer-certificacion.js +692 -0
  244. package/scripts/lib/w4-superficie-candidato.js +49 -0
  245. package/scripts/lib/w4-veredicto.js +452 -0
  246. package/scripts/lib/w4-verificar-veredicto.js +302 -0
  247. package/scripts/limpiar-artefactos-python.js +131 -131
  248. package/scripts/migrar-csv-a-array.js +168 -168
  249. package/scripts/migrar-fase-dominio.js +200 -200
  250. package/scripts/migrar-gates-legacy.js +108 -0
  251. package/scripts/publicar-certification-request.js +115 -0
  252. package/scripts/runtime-doctor.js +107 -0
  253. package/scripts/tui/componentes/selector-multi.js +189 -189
  254. package/scripts/tui/componentes/selector-unico.js +158 -158
  255. package/scripts/tui/ejecutores.js +375 -375
  256. package/scripts/tui/lib/colores.js +129 -129
  257. package/scripts/tui/lib/render.js +264 -264
  258. package/scripts/tui/lib/teclas.js +113 -113
  259. package/scripts/tui/pantallas/install-wizard.js +12 -7
  260. package/scripts/tui/pantallas/menu-principal.js +52 -52
  261. package/scripts/tui/pantallas/progreso.js +274 -274
  262. package/scripts/tui/pantallas/resumen.js +132 -132
  263. package/scripts/validar-userland-vacio.js +110 -110
  264. package/scripts/verificar-aislamiento-swl-eval.js +87 -0
  265. package/scripts/verificar-empaquetado-downstream.js +375 -0
  266. package/scripts/verificar-loop-constructor.js +215 -0
  267. package/scripts/verificar-trazabilidad.js +13 -6
  268. package/scripts/verificar-veredicto-real.js +84 -0
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Ciclo aislado pack -> install -> doctor -> update -> uninstall sobre el
6
+ * TARBALL real que `npm pack` produciría — no el árbol fuente. Detecta la
7
+ * clase de bug histórica "declarado en package.json#files pero el contenido
8
+ * empaquetado está roto/incompleto" (gateway/ ausente antes de v1.4.0,
9
+ * ver CLAUDE.md § package.json#files) que ni los tests unitarios ni el
10
+ * gate de sincronización de manifiestos pueden atrapar, porque ambos leen
11
+ * el árbol fuente, no lo que realmente viaja en el tarball.
12
+ *
13
+ * Fase "update": el subcomando `update` real compara la versión local
14
+ * contra el registro npm (`npm view`) y, si el paquete local difiere del
15
+ * publicado, se salta el reinstalo ANTES de evaluar `--force`
16
+ * (scripts/actualizar.js) — no determinista en desarrollo local con una
17
+ * versión sin publicar (local 3.0.0 vs. remoto real 2.6.1 al momento de
18
+ * escribir este script). Ejercitar el subcomando `update` real produciría
19
+ * un no-op silencioso que fingiría cobertura sin probar nada. Esta fase en
20
+ * su lugar corre `update --check` (solo lectura, tolerante a falta de red)
21
+ * MÁS un reinstall en sitio con los mismos flags del install inicial — el
22
+ * mecanismo real que `update()` invoca internamente (`instalador()` con
23
+ * `force:true`) cuando SÍ decide actualizar — probando así el camino de
24
+ * re-aplicación idempotente sin depender del estado del registro npm.
25
+ *
26
+ * Exit codes: 0 OK, 1 si cualquier fase falla.
27
+ *
28
+ * Uso: node scripts/verificar-empaquetado-downstream.js [--keep-tmp]
29
+ */
30
+
31
+ const { spawnSync } = require('node:child_process');
32
+ const fs = require('node:fs');
33
+ const path = require('node:path');
34
+ const os = require('node:os');
35
+
36
+ const { comandoNpm } = require('./lib/npm-version');
37
+
38
+ const RAIZ = path.resolve(__dirname, '..');
39
+ const KEEP_TMP = process.argv.includes('--keep-tmp');
40
+
41
+ function log(msg) {
42
+ console.log(msg);
43
+ }
44
+
45
+ /**
46
+ * Invoca `npm <args>` vía `node npm-cli.js` (nunca `npm.cmd` con
47
+ * `shell:true`): en Windows, `spawnSync('npm.cmd', ...)` sin shell lanza
48
+ * EINVAL, y `shell:true` reintroduce DEP0190 — mismo problema ya resuelto
49
+ * en scripts/lib/npm-version.js § comandoNpm.
50
+ */
51
+ function spawnNpm(args, opts) {
52
+ const { bin, argsList } = comandoNpm(args);
53
+ return spawnSync(bin, argsList, opts);
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Fase 0: inspección del tarball (dry-run, sin escribir en disco).
58
+ // ---------------------------------------------------------------------------
59
+
60
+ const RUTAS_REQUERIDAS = [
61
+ 'bin/', 'manifiestos/', 'schemas/', 'agentes/', 'habilidades/',
62
+ 'comandos/', 'hooks/', 'reglas/', 'package.json', 'plugin.json', 'CLAUDE.md',
63
+ ];
64
+ // Nombres de ARCHIVO que delatan credenciales reales, no menciones al tema
65
+ // (el paquete incluye legítimamente hooks/skills de escaneo de secretos —
66
+ // "escaneo-secretos.js", "habilidades/iam-secretos/" — que hablan DE
67
+ // seguridad sin filtrar ninguna).
68
+ const PATRONES_PROHIBIDOS = [
69
+ /^tests\//, /^\.planning\//, /(^|\/)\.env(\.|$)/, /\.pem$/, /\.key$/,
70
+ /(^|\/)\.npmrc$/, /(^|\/)\.netrc$/,
71
+ /(^|\/)credentials\.json$/, /(^|\/)secrets\.(json|ya?ml)$/,
72
+ /^\.git\//, /^temp\//, /^respositorios-git\//,
73
+ ];
74
+
75
+ function faseInspeccion() {
76
+ log('\n[FASE 0/5] inspección del tarball (npm pack --dry-run --json --ignore-scripts)');
77
+ const r = spawnNpm(['pack', '--dry-run', '--json', '--ignore-scripts'], {
78
+ cwd: RAIZ, encoding: 'utf-8',
79
+ });
80
+ if (r.status !== 0) {
81
+ log(` [FALLA] npm pack --dry-run exit=${r.status}`);
82
+ log(r.stderr || '');
83
+ return { ok: false };
84
+ }
85
+ let parseado;
86
+ try {
87
+ parseado = JSON.parse(r.stdout);
88
+ } catch (error) {
89
+ log(` [FALLA] salida de npm pack --dry-run no es JSON válido: ${error.message}`);
90
+ return { ok: false };
91
+ }
92
+ const entrada = parseado[0];
93
+ const archivos = (entrada.files || []).map((f) => f.path);
94
+
95
+ const faltantes = RUTAS_REQUERIDAS.filter(
96
+ (req) => !archivos.some((a) => a === req || a.startsWith(req))
97
+ );
98
+ if (faltantes.length > 0) {
99
+ log(` [FALLA] rutas requeridas ausentes del tarball: ${faltantes.join(', ')}`);
100
+ return { ok: false };
101
+ }
102
+ log(` [OK] ${RUTAS_REQUERIDAS.length}/${RUTAS_REQUERIDAS.length} rutas requeridas presentes`);
103
+
104
+ const prohibidos = archivos.filter((a) => PATRONES_PROHIBIDOS.some((pat) => pat.test(a)));
105
+ if (prohibidos.length > 0) {
106
+ log(` [FALLA] rutas prohibidas presentes en el tarball: ${prohibidos.slice(0, 10).join(', ')}`);
107
+ return { ok: false };
108
+ }
109
+ log(` [OK] sin fixtures/tests/secretos/evidencia privada (${archivos.length} archivos totales)`);
110
+ return { ok: true };
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Fase 1: pack real + install aislado del tarball vía npm (igual que un
115
+ // consumidor downstream real: `npm install @saulwade/swl-ses`).
116
+ // ---------------------------------------------------------------------------
117
+
118
+ function fasePackEInstalarPaquete(tmpBase) {
119
+ log('\n[FASE 1/5] npm pack real + instalación del tarball en prefix aislado');
120
+ const packDest = path.join(tmpBase, 'pack-dest');
121
+ const installDest = path.join(tmpBase, 'consumer');
122
+ fs.mkdirSync(packDest, { recursive: true });
123
+ fs.mkdirSync(installDest, { recursive: true });
124
+
125
+ const pack = spawnNpm(['pack', '--pack-destination', packDest, '--ignore-scripts'], {
126
+ cwd: RAIZ, encoding: 'utf-8',
127
+ });
128
+ if (pack.status !== 0) {
129
+ log(` [FALLA] npm pack exit=${pack.status}`);
130
+ log(pack.stderr || '');
131
+ return { ok: false };
132
+ }
133
+ const tgz = fs.readdirSync(packDest).find((f) => f.endsWith('.tgz'));
134
+ if (!tgz) {
135
+ log(' [FALLA] npm pack no produjo ningún .tgz');
136
+ return { ok: false };
137
+ }
138
+ log(` [OK] tarball producido: ${tgz}`);
139
+
140
+ const install = spawnNpm(
141
+ ['install', '--prefix', installDest, '--no-save', '--ignore-scripts', path.join(packDest, tgz)],
142
+ { cwd: installDest, encoding: 'utf-8' }
143
+ );
144
+ if (install.status !== 0) {
145
+ log(` [FALLA] npm install del tarball exit=${install.status}`);
146
+ log(install.stderr || '');
147
+ return { ok: false };
148
+ }
149
+ const paqueteDir = path.join(installDest, 'node_modules', '@saulwade', 'swl-ses');
150
+ const cli = path.join(paqueteDir, 'bin', 'swl-ses.js');
151
+ if (!fs.existsSync(cli)) {
152
+ log(` [FALLA] el tarball instalado no contiene ${path.relative(installDest, cli)}`);
153
+ return { ok: false };
154
+ }
155
+ log(' [OK] tarball instalado en prefix aislado; bin/swl-ses.js presente');
156
+ return { ok: true, cli, paqueteDir };
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Fases 2-4: install -> doctor -> update -> uninstall, ejecutando el CLI
161
+ // EMPACADO (no el árbol fuente) contra un HOME aislado.
162
+ // ---------------------------------------------------------------------------
163
+
164
+ function correrCliEmpacado(cli, args, cwd, env) {
165
+ const r = spawnSync(process.execPath, [cli, ...args], {
166
+ cwd, env: { ...process.env, ...env }, encoding: 'utf-8', timeout: 120000,
167
+ });
168
+ return {
169
+ code: r.status, stdout: r.stdout || '', stderr: r.stderr || '',
170
+ error: r.error ? (r.error.code || r.error.message) : null,
171
+ };
172
+ }
173
+
174
+ function faseInstalarDesdeEmpacado(cli, cwd, home) {
175
+ log('\n[FASE 2/5] install (desde el tarball) --profile completo --global --all-langs');
176
+ const r = correrCliEmpacado(
177
+ cli,
178
+ ['install', '--target', 'claude', '--profile', 'completo', '--global', '--force', '--all-langs'],
179
+ cwd, { HOME: home, USERPROFILE: home }
180
+ );
181
+ if (r.code !== 0) {
182
+ log(` [FALLA] install exit=${r.code} spawnError=${r.error || '-'}`);
183
+ if (r.stderr.trim()) log(r.stderr.split('\n').slice(0, 10).map((l) => ` ${l}`).join('\n'));
184
+ return { ok: false };
185
+ }
186
+ const destino = path.join(home, '.claude');
187
+ const marcadores = [
188
+ path.join(destino, 'agents'), path.join(destino, 'skills'),
189
+ path.join(destino, 'commands'), path.join(destino, 'hooks'),
190
+ path.join(destino, 'rules'),
191
+ ];
192
+ const ausentes = marcadores.filter((m) => !fs.existsSync(m));
193
+ if (ausentes.length > 0) {
194
+ log(` [FALLA] rutas esperadas ausentes tras install: ${ausentes.map((m) => path.relative(destino, m)).join(', ')}`);
195
+ return { ok: false };
196
+ }
197
+ log(' [OK] install terminó sin errores; estructura esperada presente');
198
+ return { ok: true };
199
+ }
200
+
201
+ function faseDoctorDesdeEmpacado(cli, cwd, home) {
202
+ log('\n[FASE 3/5] doctor (desde el tarball)');
203
+ const r = correrCliEmpacado(cli, ['doctor'], cwd, { HOME: home, USERPROFILE: home });
204
+ if (r.code !== 0) {
205
+ log(` [FALLA] doctor exit=${r.code}`);
206
+ log(r.stdout.split('\n').slice(-15).map((l) => ` ${l}`).join('\n'));
207
+ return { ok: false };
208
+ }
209
+ const m = r.stdout.match(/(\d+)\s*errores/);
210
+ if (!m || parseInt(m[1], 10) !== 0) {
211
+ log(' [FALLA] doctor no reportó "0 errores" en su resumen');
212
+ log(r.stdout.split('\n').slice(-15).map((l) => ` ${l}`).join('\n'));
213
+ return { ok: false };
214
+ }
215
+ log(' [OK] doctor pasó sin errores');
216
+ return { ok: true };
217
+ }
218
+
219
+ function faseActualizarDesdeEmpacado(cli, cwd, home) {
220
+ log('\n[FASE 4/5] update --check (solo lectura) + reinstall en sitio (mecanismo real de update)');
221
+ const check = correrCliEmpacado(cli, ['update', '--check'], cwd, { HOME: home, USERPROFILE: home });
222
+ if (check.code !== 0) {
223
+ log(` [FALLA] update --check exit=${check.code}`);
224
+ log(check.stderr.split('\n').slice(0, 10).map((l) => ` ${l}`).join('\n'));
225
+ return { ok: false };
226
+ }
227
+ log(' [OK] update --check terminó sin errores');
228
+
229
+ // Reinstall en sitio con los MISMOS flags del install inicial: el
230
+ // mecanismo idempotente que update() invoca internamente vía
231
+ // instalador({..., force:true}) cuando decide actualizar. Prueba que
232
+ // re-aplicar el mismo perfil sobre una instalación existente no rompe
233
+ // ni deja archivos a medias — sin depender del registro npm.
234
+ const reinstall = correrCliEmpacado(
235
+ cli,
236
+ ['install', '--target', 'claude', '--profile', 'completo', '--global', '--force', '--all-langs'],
237
+ cwd, { HOME: home, USERPROFILE: home }
238
+ );
239
+ if (reinstall.code !== 0) {
240
+ log(` [FALLA] reinstall en sitio exit=${reinstall.code}`);
241
+ log(reinstall.stderr.split('\n').slice(0, 10).map((l) => ` ${l}`).join('\n'));
242
+ return { ok: false };
243
+ }
244
+ const destino = path.join(home, '.claude');
245
+ if (!fs.existsSync(path.join(destino, 'agents')) || !fs.existsSync(path.join(destino, 'skills'))) {
246
+ log(' [FALLA] tras el reinstall en sitio, agents/ o skills/ ya no están presentes');
247
+ return { ok: false };
248
+ }
249
+ log(' [OK] reinstall en sitio (mecanismo de update) terminó sin errores');
250
+ return { ok: true };
251
+ }
252
+
253
+ function faseDesinstalarDesdeEmpacado(cli, cwd, home) {
254
+ log('\n[FASE 5/5] uninstall (desde el tarball)');
255
+ const r = correrCliEmpacado(cli, ['uninstall', '--target', 'claude', '--global', '--force'], cwd, {
256
+ HOME: home, USERPROFILE: home,
257
+ });
258
+ if (r.code !== 0) {
259
+ log(` [FALLA] uninstall exit=${r.code}`);
260
+ log(r.stderr.split('\n').slice(0, 10).map((l) => ` ${l}`).join('\n'));
261
+ return { ok: false };
262
+ }
263
+ log(' [OK] uninstall terminó sin errores');
264
+ return { ok: true };
265
+ }
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // Invariantes que ninguna fase debe romper: los siete claims públicos
269
+ // permanecen unverified y las tres ubicaciones de versión no cambian.
270
+ // ---------------------------------------------------------------------------
271
+
272
+ function verificarInvariantesDelRepoFuente(snapshotPrevio) {
273
+ const conformancePath = path.join(RAIZ, 'manifiestos', 'runtime-conformance.json');
274
+ const conformance = JSON.parse(fs.readFileSync(conformancePath, 'utf8'));
275
+ const estados = Object.values(conformance.runtimes).map((r) => r.certificationStatus);
276
+ const noUnverified = estados.filter((e) => e !== 'unverified');
277
+ if (noUnverified.length > 0) {
278
+ return { ok: false, motivo: `runtime-conformance.json tiene estados fuera de unverified: ${noUnverified.join(', ')}` };
279
+ }
280
+ if (estados.length !== 7) {
281
+ return { ok: false, motivo: `runtime-conformance.json declara ${estados.length} runtimes, se esperaban 7` };
282
+ }
283
+
284
+ const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, 'package.json'), 'utf8'));
285
+ const plugin = JSON.parse(fs.readFileSync(path.join(RAIZ, 'plugin.json'), 'utf8'));
286
+ const salud = fs.readFileSync(path.join(RAIZ, 'SALUD.md'), 'utf8');
287
+ if (pkg.version !== snapshotPrevio.pkgVersion) {
288
+ return { ok: false, motivo: `package.json#version cambió: ${snapshotPrevio.pkgVersion} -> ${pkg.version}` };
289
+ }
290
+ if (plugin.version !== snapshotPrevio.pluginVersion) {
291
+ return { ok: false, motivo: `plugin.json#version cambió: ${snapshotPrevio.pluginVersion} -> ${plugin.version}` };
292
+ }
293
+ if (!salud.includes(snapshotPrevio.pkgVersion)) {
294
+ return { ok: false, motivo: `SALUD.md ya no menciona la versión ${snapshotPrevio.pkgVersion}` };
295
+ }
296
+ return { ok: true };
297
+ }
298
+
299
+ function snapshotVersiones() {
300
+ const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, 'package.json'), 'utf8'));
301
+ const plugin = JSON.parse(fs.readFileSync(path.join(RAIZ, 'plugin.json'), 'utf8'));
302
+ return { pkgVersion: pkg.version, pluginVersion: plugin.version };
303
+ }
304
+
305
+ function main() {
306
+ log('Ciclo aislado de empaquetado downstream (pack -> install -> doctor -> update -> uninstall)');
307
+ log('═══════════════════════════════════════════\n');
308
+
309
+ const snapshotPrevio = snapshotVersiones();
310
+
311
+ const inspeccion = faseInspeccion();
312
+ if (!inspeccion.ok) {
313
+ log('\n✗ FALLÓ en la fase de inspección — abortando sin tocar disco');
314
+ process.exit(1);
315
+ }
316
+
317
+ const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), 'swl-pack-cycle-'));
318
+ const home = path.join(tmpBase, 'home');
319
+ const cwd = path.join(tmpBase, 'workspace');
320
+ fs.mkdirSync(home, { recursive: true });
321
+ fs.mkdirSync(cwd, { recursive: true });
322
+ log(`TMP: ${tmpBase}`);
323
+
324
+ let todoOk = true;
325
+ try {
326
+ const paquete = fasePackEInstalarPaquete(tmpBase);
327
+ if (!paquete.ok) {
328
+ todoOk = false;
329
+ } else {
330
+ const install = faseInstalarDesdeEmpacado(paquete.cli, cwd, home);
331
+ if (!install.ok) todoOk = false;
332
+
333
+ if (install.ok) {
334
+ const doctor = faseDoctorDesdeEmpacado(paquete.cli, cwd, home);
335
+ if (!doctor.ok) todoOk = false;
336
+
337
+ const update = faseActualizarDesdeEmpacado(paquete.cli, cwd, home);
338
+ if (!update.ok) todoOk = false;
339
+
340
+ const uninstall = faseDesinstalarDesdeEmpacado(paquete.cli, cwd, home);
341
+ if (!uninstall.ok) todoOk = false;
342
+ }
343
+ }
344
+ } finally {
345
+ if (!KEEP_TMP) {
346
+ try {
347
+ fs.rmSync(tmpBase, { recursive: true, force: true });
348
+ } catch {
349
+ /* best-effort: no bloquear el resultado del gate por un residuo de limpieza */
350
+ }
351
+ } else {
352
+ log(`\n(--keep-tmp) directorio preservado en ${tmpBase}`);
353
+ }
354
+ }
355
+
356
+ log('\n[INVARIANTES] runtime-conformance unverified + versiones sin cambio en el repo fuente');
357
+ const invariantes = verificarInvariantesDelRepoFuente(snapshotPrevio);
358
+ if (!invariantes.ok) {
359
+ log(` [FALLA] ${invariantes.motivo}`);
360
+ todoOk = false;
361
+ } else {
362
+ log(' [OK] los siete claims siguen unverified; package.json/plugin.json/SALUD.md sin cambio');
363
+ }
364
+
365
+ log('\n═══════════════════════════════════════════');
366
+ if (todoOk) {
367
+ log('✓ Ciclo de empaquetado downstream OK');
368
+ process.exit(0);
369
+ } else {
370
+ log('✗ Ciclo de empaquetado downstream FALLÓ — revisar fases arriba');
371
+ process.exit(1);
372
+ }
373
+ }
374
+
375
+ main();
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Verifica, en modo de sólo lectura, un loop con el gate estructural de un
6
+ * checkout exacto de constructor-loops.
7
+ */
8
+
9
+ const { execFileSync } = require('node:child_process');
10
+ const fs = require('node:fs');
11
+ const path = require('node:path');
12
+
13
+ const ARGUMENTOS = new Set(['constructor-dir', 'expected-commit', 'loop']);
14
+ const CAMPOS_GATE = new Set(['pass', 'summary', 'findings', 'matchedRules']);
15
+ const SCRIPT_GATE = [
16
+ "import fs from 'node:fs';",
17
+ "import { pathToFileURL } from 'node:url';",
18
+ 'const [builderPath, loopPath] = process.argv.slice(1);',
19
+ 'const modulo = await import(pathToFileURL(builderPath).href);',
20
+ "if (typeof modulo.runStructuralGate !== 'function') {",
21
+ " throw new TypeError('runStructuralGate no está exportado');",
22
+ '}',
23
+ "const markdown = fs.readFileSync(loopPath, 'utf8');",
24
+ 'const resultado = modulo.runStructuralGate(markdown);',
25
+ 'process.stdout.write(JSON.stringify(resultado));',
26
+ ].join('\n');
27
+
28
+ function parsearArgs(argv) {
29
+ const opciones = {};
30
+ for (const argumento of argv) {
31
+ const separador = argumento.indexOf('=');
32
+ const clave = separador > 2 ? argumento.slice(2, separador) : '';
33
+ const valor = separador >= 0 ? argumento.slice(separador + 1) : '';
34
+ if (!argumento.startsWith('--') || !ARGUMENTOS.has(clave) || !valor) {
35
+ throw new TypeError(`argumento inválido: ${argumento}`);
36
+ }
37
+ if (Object.hasOwn(opciones, clave)) throw new TypeError(`argumento duplicado: --${clave}`);
38
+ opciones[clave] = valor;
39
+ }
40
+ const faltantes = [...ARGUMENTOS].filter((clave) => !Object.hasOwn(opciones, clave));
41
+ if (faltantes.length > 0) throw new TypeError(`argumentos requeridos: ${faltantes.join(', ')}`);
42
+ return opciones;
43
+ }
44
+
45
+ function resolverEntradas(opciones) {
46
+ const constructorDir = path.resolve(opciones.constructorDir);
47
+ const loopPath = path.resolve(opciones.loopPath);
48
+ if (!fs.existsSync(constructorDir) || !fs.statSync(constructorDir).isDirectory()) {
49
+ throw new Error(`constructor-dir no existe o no es directorio: ${constructorDir}`);
50
+ }
51
+ if (!fs.existsSync(loopPath) || !fs.statSync(loopPath).isFile()) {
52
+ throw new Error(`loop no existe o no es archivo: ${loopPath}`);
53
+ }
54
+ const constructorReal = fs.realpathSync(constructorDir);
55
+ const builderPath = path.join(constructorReal, 'app', 'lib', 'loop-builder.ts');
56
+ if (!fs.existsSync(builderPath) || !fs.statSync(builderPath).isFile()) {
57
+ throw new Error(`loop-builder.ts no existe: ${builderPath}`);
58
+ }
59
+ if (fs.lstatSync(builderPath).isSymbolicLink()) {
60
+ throw new Error(`loop-builder.ts no puede ser enlace simbólico: ${builderPath}`);
61
+ }
62
+ return { builderPath, constructorDir: constructorReal, loopPath: fs.realpathSync(loopPath) };
63
+ }
64
+
65
+ function construirEntornoGit(entorno = process.env) {
66
+ const limpio = Object.fromEntries(Object.entries(entorno).filter(
67
+ ([clave]) => clave.toUpperCase() !== 'GIT_OPTIONAL_LOCKS'
68
+ ));
69
+ return { ...limpio, GIT_OPTIONAL_LOCKS: '0' };
70
+ }
71
+
72
+ function ejecutarGit(constructorDir, args, descripcion) {
73
+ try {
74
+ return execFileSync('git', args, {
75
+ cwd: constructorDir,
76
+ encoding: 'utf8',
77
+ env: construirEntornoGit(),
78
+ stdio: ['ignore', 'pipe', 'pipe'],
79
+ }).trim().toLowerCase();
80
+ } catch (error) {
81
+ const detalle = String(error.stderr || error.message).trim();
82
+ throw new Error(`${descripcion}: ${detalle}`);
83
+ }
84
+ }
85
+
86
+ function verificarCommit(constructorDir, expectedCommit) {
87
+ if (!/^[a-f0-9]{7,40}$/i.test(expectedCommit)) {
88
+ throw new TypeError('expected-commit debe ser un SHA hexadecimal de 7 a 40 caracteres');
89
+ }
90
+ const revision = `${expectedCommit}^{commit}`;
91
+ const esperado = ejecutarGit(
92
+ constructorDir,
93
+ ['rev-parse', '--verify', '--end-of-options', revision],
94
+ 'expected-commit no resoluble'
95
+ );
96
+ const head = ejecutarGit(constructorDir, ['rev-parse', 'HEAD'], 'HEAD no resoluble');
97
+ if (esperado !== head) {
98
+ throw new Error(`commit distinto: esperado ${esperado}; HEAD ${head}`);
99
+ }
100
+ const blobEsperado = ejecutarGit(
101
+ constructorDir,
102
+ ['rev-parse', 'HEAD:app/lib/loop-builder.ts'],
103
+ 'loop-builder.ts no existe en HEAD'
104
+ );
105
+ const blobActual = ejecutarGit(
106
+ constructorDir,
107
+ ['hash-object', '--', 'app/lib/loop-builder.ts'],
108
+ 'no se pudo calcular el blob actual de loop-builder.ts'
109
+ );
110
+ if (blobEsperado !== blobActual) {
111
+ throw new Error(`loop-builder.ts tiene cambios fuera de HEAD: ${blobEsperado} != ${blobActual}`);
112
+ }
113
+ const estadoCheckout = ejecutarGit(
114
+ constructorDir,
115
+ ['status', '--porcelain=v1', '--untracked-files=all'],
116
+ 'no se pudo verificar el checkout completo'
117
+ );
118
+ if (estadoCheckout) throw new Error(`checkout tiene cambios fuera de HEAD: ${estadoCheckout}`);
119
+ return { expectedCommit: esperado, headCommit: head };
120
+ }
121
+
122
+ function ejecutarGate(builderPath, loopPath) {
123
+ try {
124
+ const salida = execFileSync(process.execPath, [
125
+ '--experimental-strip-types',
126
+ '--input-type=module',
127
+ '-e',
128
+ SCRIPT_GATE,
129
+ builderPath,
130
+ loopPath,
131
+ ], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 1024 * 1024 });
132
+ return JSON.parse(salida);
133
+ } catch (error) {
134
+ const detalle = String(error.stderr || error.stdout || error.message).trim();
135
+ throw new Error(`falló el gate de constructor-loops: ${detalle}`);
136
+ }
137
+ }
138
+
139
+ function validarResultado(resultado) {
140
+ if (!resultado || typeof resultado !== 'object' || Array.isArray(resultado)) {
141
+ throw new TypeError('resultado estructural inválido');
142
+ }
143
+ const desconocidos = Object.keys(resultado).filter((campo) => !CAMPOS_GATE.has(campo));
144
+ if (desconocidos.length > 0) {
145
+ throw new TypeError(`resultado estructural contiene campo desconocido: ${desconocidos[0]}`);
146
+ }
147
+ const reglas = resultado.matchedRules;
148
+ if (resultado.pass !== true) {
149
+ const hallazgos = Array.isArray(resultado.findings) ? resultado.findings.join('; ') : 'sin detalle';
150
+ throw new Error(`gate estructural rechazó el loop: ${hallazgos}`);
151
+ }
152
+ if (!Array.isArray(resultado.findings) || resultado.findings.length > 0) {
153
+ throw new Error('gate PASS contiene findings y se rechaza por inconsistencia');
154
+ }
155
+ if (typeof resultado.summary !== 'string' || !resultado.summary.trim()) {
156
+ throw new TypeError('gate PASS no contiene summary válido');
157
+ }
158
+ if (!Array.isArray(reglas) || reglas.length !== 10 || new Set(reglas).size !== 10) {
159
+ throw new Error('gate estructural no devolvió exactamente diez reglas PASS únicas');
160
+ }
161
+ if (!reglas.every((regla) => typeof regla === 'string' && regla.trim())) {
162
+ throw new TypeError('matchedRules contiene una regla inválida');
163
+ }
164
+ return resultado;
165
+ }
166
+
167
+ function verificarLoopConstructor(opciones) {
168
+ const entradas = resolverEntradas(opciones);
169
+ const commits = verificarCommit(entradas.constructorDir, opciones.expectedCommit);
170
+ const gate = validarResultado(ejecutarGate(entradas.builderPath, entradas.loopPath));
171
+ return {
172
+ pass: true,
173
+ expectedCommit: commits.expectedCommit,
174
+ headCommit: commits.headCommit,
175
+ summary: gate.summary,
176
+ findings: gate.findings,
177
+ matchedRules: gate.matchedRules,
178
+ ...entradas,
179
+ };
180
+ }
181
+
182
+ function imprimirResultado(resultado) {
183
+ console.log(`[constructor-loop] commit PASS: ${resultado.headCommit}`);
184
+ for (const regla of resultado.matchedRules) {
185
+ console.log(`[constructor-loop] regla PASS: ${regla}`);
186
+ }
187
+ console.log(`[constructor-loop] resultado PASS: ${resultado.summary}`);
188
+ }
189
+
190
+ function main(argv = process.argv.slice(2)) {
191
+ const args = parsearArgs(argv);
192
+ const resultado = verificarLoopConstructor({
193
+ constructorDir: args['constructor-dir'],
194
+ expectedCommit: args['expected-commit'],
195
+ loopPath: args.loop,
196
+ });
197
+ imprimirResultado(resultado);
198
+ return resultado;
199
+ }
200
+
201
+ if (require.main === module) {
202
+ try {
203
+ main();
204
+ } catch (error) {
205
+ console.error(`[constructor-loop] ERROR: ${error.message}`);
206
+ process.exitCode = 1;
207
+ }
208
+ }
209
+
210
+ module.exports = {
211
+ construirEntornoGit,
212
+ main,
213
+ parsearArgs,
214
+ verificarLoopConstructor,
215
+ };
@@ -18,7 +18,7 @@
18
18
  * calidad-pre-commit) con marker `verifica: REQ-NN`
19
19
  *
20
20
  * Exit codes:
21
- * 0 — cadena completa, o CONTEXTO legacy sin REQ-IDs (gracia, con nota)
21
+ * 0 — cadena completa
22
22
  * 1 — huérfanos detectados (REQ sin tarea / sin commit / sin test)
23
23
  * 2 — error de invocación o parseo (fase inexistente, args inválidos)
24
24
  *
@@ -189,9 +189,16 @@ function validarFase({ cwd, fase, soloPlan }) {
189
189
  if (activos.length === 0) {
190
190
  return {
191
191
  fase: nn,
192
- legacy: true,
193
- nota: 'CONTEXTO sin REQ-IDs — trazabilidad no exigible (gracia legacy, fases pre-10)',
194
- exit: 0,
192
+ legacy: false,
193
+ reqs: [],
194
+ retirados,
195
+ trazabilidad: {},
196
+ validaciones: {
197
+ contexto_sin_req: true,
198
+ req_sin_tarea: [], req_sin_commit: [], req_sin_test: [],
199
+ tarea_sin_req: [], headers_malformados: [],
200
+ },
201
+ exit: 1,
195
202
  };
196
203
  }
197
204
 
@@ -256,8 +263,8 @@ function imprimirReporte(r) {
256
263
  console.error(`ERROR: ${r.error}`);
257
264
  return;
258
265
  }
259
- if (r.legacy) {
260
- console.log(`Fase ${r.fase}: ${r.nota}. Exit 0.`);
266
+ if (r.validaciones.contexto_sin_req) {
267
+ console.log(`Fase ${r.fase}: CONTEXTO sin REQ explícitos — migración o inferencia legacy no satisface G4.`);
261
268
  return;
262
269
  }
263
270
  console.log(`Trazabilidad REQ→T→commit→test — Fase ${r.fase}${r.soloPlan ? ' (--solo-plan: matriz REQ×T)' : ''}`);