@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,257 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Genera o verifica la vista diagnóstica del Harness IR.
6
+ * La publicación usa temp + fsync + CAS + replace entre escritores cooperativos.
7
+ * La CLI es el único escritor autorizado; procesos con privilegios equivalentes que
8
+ * ignoren el lock quedan fuera del modelo de amenaza portable de F29.
9
+ */
10
+
11
+ const childProcess = require('node:child_process');
12
+ const crypto = require('node:crypto');
13
+ const fs = require('node:fs');
14
+ const path = require('node:path');
15
+
16
+ const { compilarHarnessIR, serializarVista } = require('./lib/harness-ir');
17
+
18
+ const MODOS = new Set(['--check', '--write']);
19
+ const NOMBRE_VISTA = 'harness-ir.json';
20
+
21
+ function sha256(contenido) {
22
+ return crypto.createHash('sha256').update(contenido).digest('hex');
23
+ }
24
+
25
+ function resolverModo(args) {
26
+ if (!Array.isArray(args) || args.length !== 1) return null;
27
+ return MODOS.has(args[0]) ? args[0] : null;
28
+ }
29
+
30
+ function resolverRutaVista() {
31
+ const declarada = childProcess.execFileSync('git', ['rev-parse', '--show-toplevel'], {
32
+ cwd: process.cwd(),
33
+ encoding: 'utf8',
34
+ stdio: ['ignore', 'pipe', 'pipe'],
35
+ }).trim();
36
+ if (/^[\\/]{2}/u.test(declarada)) throw new Error('Raíz UNC prohibida');
37
+ const root = path.resolve(declarada);
38
+ const rootStat = fs.lstatSync(root);
39
+ if (rootStat.isSymbolicLink()) throw new Error('Raíz enlazada prohibida');
40
+ const rootReal = fs.realpathSync(root);
41
+ const directorio = path.join(root, 'manifiestos');
42
+ const directorioStat = fs.lstatSync(directorio);
43
+ if (directorioStat.isSymbolicLink() || !directorioStat.isDirectory()) {
44
+ throw new Error('Directorio de salida inválido');
45
+ }
46
+ const directorioReal = fs.realpathSync(directorio);
47
+ if (path.dirname(directorioReal) !== rootReal) throw new Error('Salida fuera de raíz');
48
+ return path.join(directorio, NOMBRE_VISTA);
49
+ }
50
+
51
+ function obtenerStatDestino(ruta) {
52
+ let stat;
53
+ try {
54
+ stat = fs.lstatSync(ruta);
55
+ } catch (error) {
56
+ if (error.code === 'ENOENT') return null;
57
+ throw error;
58
+ }
59
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error('Destino inválido');
60
+ const directorioReal = fs.realpathSync(path.dirname(ruta));
61
+ const real = fs.realpathSync(ruta);
62
+ if (path.dirname(real) !== directorioReal) {
63
+ throw new Error('Destino fuera del directorio confinado');
64
+ }
65
+ return { stat, real };
66
+ }
67
+
68
+ function validarDestino(ruta) {
69
+ return obtenerStatDestino(ruta);
70
+ }
71
+
72
+ function mismaIdentidad(antes, despues) {
73
+ return antes.dev === despues.dev
74
+ && antes.ino === despues.ino
75
+ && antes.size === despues.size
76
+ && antes.mtimeMs === despues.mtimeMs
77
+ && antes.ctimeMs === despues.ctimeMs
78
+ && antes.mode === despues.mode;
79
+ }
80
+
81
+ function leerDestinoEstable(ruta, snapshot) {
82
+ const flags = fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0);
83
+ const descriptor = fs.openSync(ruta, flags);
84
+ let bytes;
85
+ try {
86
+ const abierta = fs.fstatSync(descriptor);
87
+ if (!mismaIdentidad(snapshot.stat, abierta)) throw new Error('Destino cambió antes de leer');
88
+ bytes = fs.readFileSync(descriptor);
89
+ if (!mismaIdentidad(abierta, fs.fstatSync(descriptor))) {
90
+ throw new Error('Destino cambió durante la lectura');
91
+ }
92
+ } finally {
93
+ fs.closeSync(descriptor);
94
+ }
95
+ const despues = obtenerStatDestino(ruta);
96
+ if (!despues || snapshot.real !== despues.real || !mismaIdentidad(snapshot.stat, despues.stat)) {
97
+ throw new Error('Destino cambió durante la lectura');
98
+ }
99
+ return bytes;
100
+ }
101
+
102
+ function leerOpcional(ruta) {
103
+ const snapshot = obtenerStatDestino(ruta);
104
+ return snapshot ? leerDestinoEstable(ruta, snapshot) : null;
105
+ }
106
+
107
+ function sonMismosBytes(a, b) {
108
+ if (a === null || b === null) return a === b;
109
+ return Buffer.from(a).equals(Buffer.from(b));
110
+ }
111
+
112
+ function finalizar(code, evidencia = {}) {
113
+ process.exitCode = code;
114
+ return { code, ...evidencia };
115
+ }
116
+
117
+ function evidenciaPreservada(bytes) {
118
+ return bytes === null ? {} : { preservedSha256: sha256(bytes) };
119
+ }
120
+
121
+ function prepararVista() {
122
+ const vista = compilarHarnessIR();
123
+ const bytes = serializarVista(vista);
124
+ return {
125
+ vista,
126
+ bytes,
127
+ previewSha256: sha256(bytes),
128
+ };
129
+ }
130
+
131
+ function verificarVista(ruta, previa, preparada) {
132
+ if (preparada.vista.counts.errors > 0) {
133
+ return finalizar(2, {
134
+ previewSha256: preparada.previewSha256,
135
+ ...evidenciaPreservada(previa),
136
+ });
137
+ }
138
+ const actual = leerOpcional(ruta);
139
+ const code = sonMismosBytes(actual, Buffer.from(preparada.bytes)) ? 0 : 1;
140
+ return finalizar(code, { previewSha256: preparada.previewSha256 });
141
+ }
142
+
143
+ function rutaTemporal(ruta) {
144
+ return `${ruta}.tmp-${process.pid}`;
145
+ }
146
+
147
+ function persistirTemporal(temporal, bytes) {
148
+ fs.writeFileSync(temporal, bytes, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
149
+ const descriptor = fs.openSync(temporal, 'r+');
150
+ try {
151
+ fs.fsyncSync(descriptor);
152
+ } finally {
153
+ fs.closeSync(descriptor);
154
+ }
155
+ }
156
+
157
+ function limpiarTemporal(temporal) {
158
+ try {
159
+ fs.unlinkSync(temporal);
160
+ return true;
161
+ } catch (error) {
162
+ return error.code === 'ENOENT';
163
+ }
164
+ }
165
+
166
+ function adquirirLock(ruta) {
167
+ const lockPath = `${ruta}.lock`;
168
+ const descriptor = fs.openSync(lockPath, 'wx', 0o600);
169
+ return { descriptor, path: lockPath };
170
+ }
171
+
172
+ function liberarLock(lock) {
173
+ let ok = true;
174
+ try {
175
+ fs.closeSync(lock.descriptor);
176
+ } catch (_error) {
177
+ ok = false;
178
+ }
179
+ try {
180
+ fs.unlinkSync(lock.path);
181
+ } catch (_error) {
182
+ ok = false;
183
+ }
184
+ return ok;
185
+ }
186
+
187
+ function sincronizarDirectorio(directorio) {
188
+ const descriptor = fs.openSync(directorio, 'r');
189
+ try {
190
+ fs.fsyncSync(descriptor);
191
+ } catch (error) {
192
+ const noSoportado = process.platform === 'win32'
193
+ && (error.code === 'EPERM' || error.code === 'EINVAL');
194
+ if (!noSoportado) throw error;
195
+ } finally {
196
+ fs.closeSync(descriptor);
197
+ }
198
+ }
199
+
200
+ function publicarVista(ruta, previa, preparada) {
201
+ const evidencia = {
202
+ previewSha256: preparada.previewSha256,
203
+ ...evidenciaPreservada(previa),
204
+ };
205
+ if (preparada.vista.counts.errors > 0) return finalizar(2, evidencia);
206
+ const temporal = rutaTemporal(ruta);
207
+ let code = 2;
208
+ let lock;
209
+ let limpiar = false;
210
+ let committed = false;
211
+ try {
212
+ lock = adquirirLock(ruta);
213
+ validarDestino(ruta);
214
+ if (sonMismosBytes(previa, leerOpcional(ruta))) {
215
+ limpiar = true;
216
+ persistirTemporal(temporal, preparada.bytes);
217
+ fs.renameSync(temporal, ruta);
218
+ limpiar = false;
219
+ committed = true;
220
+ sincronizarDirectorio(path.dirname(ruta));
221
+ code = 0;
222
+ }
223
+ } catch (_error) {
224
+ code = 2;
225
+ } finally {
226
+ if (limpiar && !limpiarTemporal(temporal)) code = 2;
227
+ if (lock && !liberarLock(lock)) code = 2;
228
+ }
229
+ return finalizar(code, {
230
+ ...evidencia,
231
+ ...(committed ? { committed: true } : {}),
232
+ });
233
+ }
234
+
235
+ function main(args) {
236
+ const modo = resolverModo(args);
237
+ if (!modo) return finalizar(64);
238
+ let ruta;
239
+ let previa;
240
+ try {
241
+ ruta = resolverRutaVista();
242
+ previa = leerOpcional(ruta);
243
+ } catch (_error) {
244
+ return finalizar(2);
245
+ }
246
+ try {
247
+ const preparada = prepararVista();
248
+ if (modo === '--check') return verificarVista(ruta, previa, preparada);
249
+ return publicarVista(ruta, previa, preparada);
250
+ } catch (_error) {
251
+ return finalizar(2, evidenciaPreservada(previa));
252
+ }
253
+ }
254
+
255
+ if (require.main === module) main(process.argv.slice(2));
256
+
257
+ module.exports = { main };
@@ -18,6 +18,8 @@
18
18
 
19
19
  const fs = require('fs');
20
20
  const path = require('path');
21
+ const { renderBloque } = require('./generar-claims-runtime');
22
+ const { CENTINELA_TODOS, SKILLS_EXTERNAS_SET } = require('./lib/skills-externas');
21
23
 
22
24
  const RAIZ = path.resolve(__dirname, '..');
23
25
 
@@ -29,13 +31,6 @@ try {
29
31
  } catch {
30
32
  atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
31
33
  }
32
- const args = new Set(process.argv.slice(2));
33
- const DRY = args.has('--dry-run');
34
- const SOLO_INV = args.has('--inventario');
35
- const SOLO_SAL = args.has('--salud');
36
- const SOLO_LLMS = args.has('--llms');
37
- const AMBOS = !SOLO_INV && !SOLO_SAL && !SOLO_LLMS;
38
-
39
34
  // ── Helpers ────────────────────────────────────────────────────────────
40
35
 
41
36
  function leerFrontmatter(ruta) {
@@ -244,6 +239,8 @@ function generarInventario() {
244
239
  lines.push(`| Contextos | ${contextos} |`);
245
240
  lines.push(`| Plantillas | ${plantillasTotal} |`);
246
241
  lines.push('');
242
+ lines.push(renderBloque());
243
+ lines.push('');
247
244
 
248
245
  // Equipos
249
246
  lines.push('## Equipos de agentes (paralelización real)');
@@ -630,28 +627,22 @@ function generarMapeo() {
630
627
  L.push('');
631
628
  L.push('| Agente | # | skillsInvocables |');
632
629
  L.push('|--------|---|------------------|');
633
- // Skills de la colección oficial Anthropic (opt-in, instaladas en
634
- // .claude/skills/ vía `swl-ses skills add anthropics/skills`) — referencias
635
- // legítimas aunque no vivan en habilidades/. Ver MANUAL_USO § Anthropic Skills.
636
- const SKILLS_ANTHROPIC = new Set([
637
- 'pdf', 'docx', 'xlsx', 'pptx', 'frontend-design', 'web-artifacts-builder',
638
- 'canvas-design', 'algorithmic-art', 'theme-factory', 'slack-gif-creator',
639
- 'claude-api', 'mcp-builder', 'webapp-testing', 'doc-coauthoring',
640
- 'internal-comms', 'brand-guidelines', 'skill-creator',
641
- ]);
642
630
  const externas = new Set();
643
631
  const inexistentes = [];
644
632
  for (const a of [...agentes].sort((x, y) => x.name.localeCompare(y.name))) {
645
633
  for (const s of a.skillsInvocables) {
646
- if (s === 'todos') continue; // centinela: el agente puede invocar cualquier skill
634
+ if (s === CENTINELA_TODOS) continue;
647
635
  if (nombresSkill.has(s)) continue;
648
- if (SKILLS_ANTHROPIC.has(s)) externas.add(s);
636
+ if (SKILLS_EXTERNAS_SET.has(s)) externas.add(s);
649
637
  else inexistentes.push(`${a.name} → ${s}`);
650
638
  }
651
- const etiqueta = a.skillsInvocables.includes('todos')
639
+ const etiqueta = a.skillsInvocables.includes(CENTINELA_TODOS)
652
640
  ? 'todos (acceso a cualquier skill)'
653
641
  : (a.skillsInvocables.join(', ') || '—');
654
- L.push(`| ${a.name} | ${a.skillsInvocables.includes('todos') ? skills.length : a.skillsInvocables.length} | ${etiqueta} |`);
642
+ const cantidad = a.skillsInvocables.includes(CENTINELA_TODOS)
643
+ ? skills.length
644
+ : a.skillsInvocables.length;
645
+ L.push(`| ${a.name} | ${cantidad} | ${etiqueta} |`);
655
646
  }
656
647
  L.push('');
657
648
  if (externas.size > 0) {
@@ -683,46 +674,53 @@ function generarMapeo() {
683
674
 
684
675
  // ── Ejecutar ──────────────────────────────────────────────────────────
685
676
 
686
- if (AMBOS || SOLO_INV) {
687
- const inventario = generarInventario();
688
- if (DRY) {
689
- console.log('=== INVENTARIO.md (dry-run) ===\n');
690
- console.log(inventario.substring(0, 2000) + '\n...(truncado)');
691
- } else {
692
- atomicWriteSync(path.join(RAIZ, 'INVENTARIO.md'), inventario, 'utf8');
693
- console.log('INVENTARIO.md generado correctamente.');
677
+ function emitirArtefacto(artefacto, dryRun) {
678
+ const contenido = artefacto.generar();
679
+ if (dryRun) {
680
+ console.log(artefacto.encabezado);
681
+ const vista = artefacto.limite
682
+ ? contenido.substring(0, artefacto.limite) + '\n...(truncado)'
683
+ : contenido;
684
+ console.log(vista);
685
+ return;
694
686
  }
687
+ atomicWriteSync(path.join(RAIZ, artefacto.ruta), contenido, 'utf8');
688
+ console.log(`${path.basename(artefacto.ruta)} generado correctamente.`);
695
689
  }
696
690
 
697
- if (AMBOS || SOLO_SAL) {
698
- const salud = generarSalud();
699
- if (DRY) {
700
- console.log('\n=== SALUD.md (dry-run) ===\n');
701
- console.log(salud);
702
- } else {
703
- atomicWriteSync(path.join(RAIZ, 'SALUD.md'), salud, 'utf8');
704
- console.log('SALUD.md generado correctamente.');
705
- }
691
+ function definirArtefactos(args) {
692
+ const soloInventario = args.has('--inventario');
693
+ const soloSalud = args.has('--salud');
694
+ const soloLlms = args.has('--llms');
695
+ const ambos = !soloInventario && !soloSalud && !soloLlms;
696
+ return [
697
+ { activo: ambos || soloInventario, ruta: 'INVENTARIO.md', generar: generarInventario,
698
+ encabezado: '=== INVENTARIO.md (dry-run) ===\n', limite: 2000 },
699
+ { activo: ambos || soloSalud, ruta: 'SALUD.md', generar: generarSalud,
700
+ encabezado: '\n=== SALUD.md (dry-run) ===\n' },
701
+ { activo: ambos || soloLlms, ruta: 'llms.txt', generar: generarLlmsTxt,
702
+ encabezado: '\n=== llms.txt (dry-run) ===\n' },
703
+ { activo: ambos || args.has('--mapeo'), ruta: path.join('.planning', 'MAPEO_SKILLS_AGENTES.md'),
704
+ generar: generarMapeo, encabezado: '\n=== MAPEO_SKILLS_AGENTES.md (dry-run) ===\n', limite: 1500 },
705
+ ];
706
706
  }
707
707
 
708
- if (AMBOS || SOLO_LLMS) {
709
- const llms = generarLlmsTxt();
710
- if (DRY) {
711
- console.log('\n=== llms.txt (dry-run) ===\n');
712
- console.log(llms);
713
- } else {
714
- atomicWriteSync(path.join(RAIZ, 'llms.txt'), llms, 'utf8');
715
- console.log('llms.txt generado correctamente.');
708
+ function main(argv = process.argv.slice(2)) {
709
+ const args = new Set(argv);
710
+ const dryRun = args.has('--dry-run');
711
+ for (const artefacto of definirArtefactos(args)) {
712
+ if (artefacto.activo) emitirArtefacto(artefacto, dryRun);
716
713
  }
717
714
  }
718
715
 
719
- if (AMBOS || args.has('--mapeo')) {
720
- const mapeo = generarMapeo();
721
- if (DRY) {
722
- console.log('\n=== MAPEO_SKILLS_AGENTES.md (dry-run) ===\n');
723
- console.log(mapeo.substring(0, 1500) + '\n...(truncado)');
724
- } else {
725
- atomicWriteSync(path.join(RAIZ, '.planning', 'MAPEO_SKILLS_AGENTES.md'), mapeo, 'utf8');
726
- console.log('MAPEO_SKILLS_AGENTES.md generado correctamente.');
727
- }
716
+ if (require.main === module) {
717
+ main();
728
718
  }
719
+
720
+ module.exports = {
721
+ generarInventario,
722
+ generarLlmsTxt,
723
+ generarMapeo,
724
+ generarSalud,
725
+ main,
726
+ };
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Genera o verifica el manifiesto diagnóstico de PolicyBundle.
6
+ * La publicación portable coordina escritores cooperativos de esta CLI. Procesos
7
+ * con privilegios equivalentes que ignoran el lock quedan fuera del modelo W2;
8
+ * no se aceptan raíces ni destinos controlados por el usuario.
9
+ */
10
+
11
+ const childProcess = require('node:child_process');
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ const { compilarPolicyBundle, serializarPolicyBundle } = require('./lib/policy-bundle');
16
+
17
+ const MODOS = new Set(['--check', '--write']);
18
+ const NOMBRE_MANIFIESTO = 'policy-bundle.json';
19
+
20
+ function finalizar(code, evidencia = {}) {
21
+ process.exitCode = code;
22
+ return { code, ...evidencia };
23
+ }
24
+
25
+ function resolverModo(args) {
26
+ return Array.isArray(args) && args.length === 1 && MODOS.has(args[0]) ? args[0] : null;
27
+ }
28
+
29
+ function capturarDirectorio(directorio, rootReal) {
30
+ const stat = fs.lstatSync(directorio);
31
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error('Salida inválida');
32
+ const real = fs.realpathSync(directorio);
33
+ if (path.dirname(real) !== rootReal) throw new Error('Salida fuera de raíz');
34
+ return { real, fingerprint: identidadDirectorio(stat) };
35
+ }
36
+
37
+ function identidadDirectorio(stat) {
38
+ return [stat.dev, stat.ino, stat.birthtimeMs, stat.mode].join(':');
39
+ }
40
+
41
+ function resolverRuta() {
42
+ const declarada = childProcess.execFileSync('git', ['rev-parse', '--show-toplevel'], {
43
+ cwd: process.cwd(),
44
+ encoding: 'utf8',
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ }).trim();
47
+ if (/^[\\/]{2}/u.test(declarada)) throw new Error('Raíz UNC prohibida');
48
+ const root = path.resolve(declarada);
49
+ const rootReal = fs.realpathSync(root);
50
+ if (fs.lstatSync(root).isSymbolicLink() || path.relative(root, rootReal) !== '') {
51
+ throw new Error('Raíz enlazada prohibida');
52
+ }
53
+ const directorio = path.join(root, 'manifiestos');
54
+ const identidadSalida = capturarDirectorio(directorio, rootReal);
55
+ return {
56
+ directorio,
57
+ identidadSalida,
58
+ rootReal,
59
+ ruta: path.join(directorio, NOMBRE_MANIFIESTO),
60
+ };
61
+ }
62
+
63
+ function revalidarDirectorio(contexto) {
64
+ const actual = capturarDirectorio(contexto.directorio, contexto.rootReal);
65
+ const esperado = contexto.identidadSalida;
66
+ if (actual.real !== esperado.real || actual.fingerprint !== esperado.fingerprint) {
67
+ throw new Error('Directorio de salida cambió');
68
+ }
69
+ }
70
+
71
+ function statDestino(ruta) {
72
+ try {
73
+ const stat = fs.lstatSync(ruta);
74
+ if (stat.isSymbolicLink() || !stat.isFile()) throw new Error('Destino inválido');
75
+ const real = fs.realpathSync(ruta);
76
+ if (path.dirname(real) !== fs.realpathSync(path.dirname(ruta))) {
77
+ throw new Error('Destino fuera del directorio');
78
+ }
79
+ return { stat, real };
80
+ } catch (error) {
81
+ if (error.code === 'ENOENT') return null;
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ function identidad(stat) {
87
+ return [stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs, stat.mode].join(':');
88
+ }
89
+
90
+ function leerEstable(ruta) {
91
+ const inicial = statDestino(ruta);
92
+ if (!inicial) return null;
93
+ const descriptor = fs.openSync(ruta, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0));
94
+ let bytes;
95
+ try {
96
+ if (identidad(fs.fstatSync(descriptor)) !== identidad(inicial.stat)) {
97
+ throw new Error('Destino cambió antes de leer');
98
+ }
99
+ bytes = fs.readFileSync(descriptor);
100
+ if (identidad(fs.fstatSync(descriptor)) !== identidad(inicial.stat)) {
101
+ throw new Error('Destino cambió durante lectura');
102
+ }
103
+ } finally {
104
+ fs.closeSync(descriptor);
105
+ }
106
+ const final = statDestino(ruta);
107
+ if (!final || final.real !== inicial.real || identidad(final.stat) !== identidad(inicial.stat)) {
108
+ throw new Error('Destino cambió después de leer');
109
+ }
110
+ return bytes;
111
+ }
112
+
113
+ function iguales(a, b) {
114
+ if (a === null || b === null) return a === b;
115
+ return Buffer.from(a).equals(Buffer.from(b));
116
+ }
117
+
118
+ function compilarBytes() {
119
+ return Buffer.from(serializarPolicyBundle(compilarPolicyBundle()), 'utf8');
120
+ }
121
+
122
+ function sincronizarDirectorio(directorio) {
123
+ const descriptor = fs.openSync(directorio, 'r');
124
+ try {
125
+ fs.fsyncSync(descriptor);
126
+ } catch (error) {
127
+ const noSoportado = process.platform === 'win32'
128
+ && (error.code === 'EPERM' || error.code === 'EINVAL');
129
+ if (!noSoportado) throw error;
130
+ } finally {
131
+ fs.closeSync(descriptor);
132
+ }
133
+ }
134
+
135
+ function escribirTemporal(temporal, bytes) {
136
+ fs.writeFileSync(temporal, bytes, { flag: 'wx', mode: 0o600 });
137
+ const descriptor = fs.openSync(temporal, 'r+');
138
+ try {
139
+ fs.fsyncSync(descriptor);
140
+ } finally {
141
+ fs.closeSync(descriptor);
142
+ }
143
+ }
144
+
145
+ function limpiar(ruta) {
146
+ try {
147
+ fs.unlinkSync(ruta);
148
+ } catch (error) {
149
+ if (error.code !== 'ENOENT') throw error;
150
+ }
151
+ }
152
+
153
+ function publicar(contexto, previa, bytes) {
154
+ const { ruta, rootReal } = contexto;
155
+ const lockPath = path.join(rootReal, '.policy-bundle.lock');
156
+ const temporal = `${ruta}.tmp-${process.pid}`;
157
+ let lock;
158
+ try {
159
+ lock = fs.openSync(lockPath, 'wx', 0o600);
160
+ revalidarDirectorio(contexto);
161
+ if (!iguales(previa, leerEstable(ruta))) throw new Error('CAS rechazado');
162
+ revalidarDirectorio(contexto);
163
+ escribirTemporal(temporal, bytes);
164
+ revalidarDirectorio(contexto);
165
+ fs.renameSync(temporal, ruta);
166
+ revalidarDirectorio(contexto);
167
+ sincronizarDirectorio(contexto.directorio);
168
+ return finalizar(0, { committed: true });
169
+ } catch (_error) {
170
+ try {
171
+ revalidarDirectorio(contexto);
172
+ limpiar(temporal);
173
+ } catch { /* identidad incierta: no seguir paths mutables */ }
174
+ return finalizar(2);
175
+ } finally {
176
+ if (lock !== undefined) fs.closeSync(lock);
177
+ if (lock !== undefined) limpiar(lockPath);
178
+ }
179
+ }
180
+
181
+ function main(args) {
182
+ const modo = resolverModo(args);
183
+ if (!modo) return finalizar(64);
184
+ try {
185
+ const contexto = resolverRuta();
186
+ const previa = leerEstable(contexto.ruta);
187
+ const bytes = compilarBytes();
188
+ revalidarDirectorio(contexto);
189
+ if (modo === '--check') {
190
+ const igualesAlDisco = iguales(leerEstable(contexto.ruta), bytes);
191
+ revalidarDirectorio(contexto);
192
+ return finalizar(igualesAlDisco ? 0 : 1);
193
+ }
194
+ return publicar(contexto, previa, bytes);
195
+ } catch (_error) {
196
+ return finalizar(2);
197
+ }
198
+ }
199
+
200
+ if (require.main === module) main(process.argv.slice(2));
201
+
202
+ module.exports = { main };
@@ -16,7 +16,13 @@
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
18
 
19
- const { obtenerRuntime, calcularRutas, resumenDeteccion, detectarInstalacionesDuales } = require('./lib/detectar-runtime');
19
+ const {
20
+ calcularRutas,
21
+ detectarInstalacionesDuales,
22
+ obtenerEtiquetaConformidad,
23
+ obtenerRuntime,
24
+ resumenDeteccion,
25
+ } = require('./lib/detectar-runtime');
20
26
  const { preguntarSiNo, ES_TTY } = require('./lib/ui');
21
27
  const { resolverPerfil, generarPlan } = require('./lib/manifiestos');
22
28
  const { crearEstado, registrarArchivo, registrarComponente, guardarEstado, cargarEstado } = require('./lib/estado');
@@ -200,9 +206,11 @@ async function install(opciones) {
200
206
  process.exit(1);
201
207
  }
202
208
 
203
- if (runtime.soporte === 'parcial') {
204
- console.log(`Nota: Runtime "${target}" (${runtime.nombre}) tiene soporte parcial — solo agentes y reglas.\n`);
205
- }
209
+ const etiquetaConformidad = obtenerEtiquetaConformidad(target);
210
+ console.log(
211
+ `Conformidad: ${runtime.nombre} (${target}) = ${etiquetaConformidad}. ` +
212
+ 'La disponibilidad del instalador no equivale a certificación del harness.\n'
213
+ );
206
214
 
207
215
  // 1b. Detectar conflicto dual-scope: instalar en un scope cuando el opuesto
208
216
  // ya tiene SWL instalado causa duplicación visible de comandos en la UI.
@@ -1145,6 +1153,17 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1145
1153
  return { instalado: false, razon: `Target "${runtime.id}" no soporta "${archivo.tipo}"` };
1146
1154
  }
1147
1155
 
1156
+ // Base de los backups de actualización. En install GLOBAL no se ancla a
1157
+ // process.cwd(): si el usuario corre `install --global` desde su HOME, cwd es
1158
+ // el HOME y los backups aterrizaban en ~/.planning/backups (bug 2026-07-15,
1159
+ // sembraba un ~/.planning espurio). Se co-ubican con el scope global
1160
+ // (~/.claude/.planning/backups) — el restore usa la rutaBackup absoluta
1161
+ // guardada en el estado, así que reubicar la base es seguro. En install de
1162
+ // proyecto, los backups viven en el proyecto (comportamiento correcto).
1163
+ const rutaBackupBase = opciones.esGlobal
1164
+ ? path.join(require('os').homedir(), '.claude')
1165
+ : process.cwd();
1166
+
1148
1167
  let dirDestino;
1149
1168
 
1150
1169
  switch (archivo.tipo) {
@@ -1330,7 +1349,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1330
1349
 
1331
1350
  if (strategy.strategy === 'conflict') {
1332
1351
  // Población A — evolución del usuario. NUNCA overwrite: backup + merge (diff).
1333
- const backup = crearBackup(process.cwd(), cmpDestino, versionBackup);
1352
+ const backup = crearBackup(rutaBackupBase, cmpDestino, versionBackup);
1334
1353
  if (!backup.respaldado) {
1335
1354
  console.log(` ⚠ Backup falló (${backup.error || 'desconocido'}) para ${nombreArchivo} — merge abortado, original preservado`);
1336
1355
  auditEvolved({ archivo: path.relative(process.cwd(), cmpDestino), clasificacion: 'A', accion: 'preserve', hashAntes, evidencia: `backup falló: ${backup.error || 'desconocido'}` });
@@ -1390,7 +1409,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1390
1409
  }
1391
1410
 
1392
1411
  // Componente-archivo (agentes/comandos/reglas): backup del archivo + fall-through.
1393
- const backup = crearBackup(process.cwd(), cmpDestino, versionBackup);
1412
+ const backup = crearBackup(rutaBackupBase, cmpDestino, versionBackup);
1394
1413
  // H1 (nemesis O5): si el backup falla, NO sobrescribir — preservar original.
1395
1414
  if (!backup.respaldado) {
1396
1415
  console.log(` ⚠ Backup falló (${backup.error || 'desconocido'}) para ${nombreArchivo} — overwrite abortado, original preservado`);
@@ -1428,7 +1447,7 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
1428
1447
  // Nivel 2: Backup antes de sobreescribir con --force (el caso B shipped-evolved
1429
1448
  // ya hizo su propio backup arriba; no duplicar).
1430
1449
  if (fs.existsSync(destino) && opciones.force && opciones.versionAnterior && !forzarEscrituraEvolved) {
1431
- const backup = crearBackup(process.cwd(), destino, opciones.versionAnterior);
1450
+ const backup = crearBackup(rutaBackupBase, destino, opciones.versionAnterior);
1432
1451
  if (backup.respaldado) {
1433
1452
  if (typeof opciones.onProgress === 'function') {
1434
1453
  opciones.onProgress({