@saulwade/swl-ses 2.5.3 → 2.6.1

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 (214) hide show
  1. package/CLAUDE.md +9 -9
  2. package/README.md +37 -37
  3. package/agentes/_intent-spec.md +73 -73
  4. package/agentes/_propose-step.md +90 -90
  5. package/agentes/accesibilidad-wcag-swl.md +690 -690
  6. package/agentes/arquitecto-swl.md +267 -267
  7. package/agentes/auto-evolucion-swl.md +932 -908
  8. package/agentes/backend-csharp-swl.md +420 -420
  9. package/agentes/backend-go-swl.md +390 -390
  10. package/agentes/backend-java-swl.md +281 -281
  11. package/agentes/backend-rust-swl.md +364 -364
  12. package/agentes/backend-workers-swl.md +482 -482
  13. package/agentes/cloud-infra-swl.md +509 -509
  14. package/agentes/consolidador-swl.md +541 -541
  15. package/agentes/depurador-swl.md +352 -352
  16. package/agentes/devops-ci-swl.md +400 -400
  17. package/agentes/disenador-ui-swl.md +569 -569
  18. package/agentes/documentador-swl.md +345 -345
  19. package/agentes/frontend-angular-swl.md +621 -621
  20. package/agentes/frontend-css-swl.md +716 -716
  21. package/agentes/frontend-react-swl.md +692 -692
  22. package/agentes/frontend-swl.md +496 -496
  23. package/agentes/frontend-tailwind-swl.md +826 -826
  24. package/agentes/investigador-swl.md +432 -432
  25. package/agentes/investigador-ux-swl.md +505 -505
  26. package/agentes/migrador-swl.md +442 -442
  27. package/agentes/mobile-android-swl.md +511 -511
  28. package/agentes/mobile-cross-swl.md +541 -541
  29. package/agentes/mobile-ios-swl.md +502 -502
  30. package/agentes/mobile-testing-swl.md +302 -302
  31. package/agentes/nemesis-auditor-swl.md +285 -285
  32. package/agentes/observabilidad-swl.md +438 -438
  33. package/agentes/pagos-swl.md +310 -310
  34. package/agentes/perfilador-usuario-swl.md +321 -321
  35. package/agentes/planificador-swl.md +399 -399
  36. package/agentes/producto-prd-swl.md +589 -589
  37. package/agentes/red-team-swl.md +218 -218
  38. package/agentes/release-manager-swl.md +590 -590
  39. package/agentes/rendimiento-swl.md +713 -713
  40. package/agentes/revisor-angular-swl.md +278 -278
  41. package/agentes/revisor-csharp-swl.md +264 -264
  42. package/agentes/revisor-go-swl.md +259 -259
  43. package/agentes/revisor-java-swl.md +257 -257
  44. package/agentes/revisor-kotlin-swl.md +273 -273
  45. package/agentes/revisor-nextjs-swl.md +281 -281
  46. package/agentes/revisor-php-swl.md +271 -271
  47. package/agentes/revisor-react-swl.md +278 -278
  48. package/agentes/revisor-rust-swl.md +346 -346
  49. package/agentes/revisor-seguridad-swl.md +399 -399
  50. package/agentes/revisor-swift-swl.md +268 -268
  51. package/agentes/revisor-typescript-swl.md +346 -346
  52. package/agentes/tdd-qa-swl.md +393 -393
  53. package/bin/swl-ses.js +32 -7
  54. package/comandos/swl/actualizar.md +3 -3
  55. package/comandos/swl/aprender.md +13 -0
  56. package/comandos/swl/deuda-codigo.md +97 -97
  57. package/comandos/swl/evaluar-skill.md +18 -3
  58. package/comandos/swl/evolucion-continua.md +73 -0
  59. package/comandos/swl/evolucionar.md +13 -0
  60. package/comandos/swl/instalar.md +4 -4
  61. package/comandos/swl/notificaciones.md +1 -1
  62. package/comandos/swl/status.md +2 -2
  63. package/gateway/cron/jobs.example.json +12 -0
  64. package/habilidades/auto-evolucion-protocolo/SKILL.md +19 -1
  65. package/habilidades/autoresearch/SKILL.md +3 -2
  66. package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
  67. package/habilidades/benchmark-memoria/SKILL.md +7 -7
  68. package/habilidades/changelog-generator/SKILL.md +1 -1
  69. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  70. package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
  71. package/habilidades/compactacion-contexto/SKILL.md +2 -1
  72. package/habilidades/contenedores-docker/SKILL.md +4 -2
  73. package/habilidades/context-builder/SKILL.md +4 -0
  74. package/habilidades/doubt-driven-review/SKILL.md +17 -1
  75. package/habilidades/drift-detection/SKILL.md +6 -1
  76. package/habilidades/ejecutar-fase/SKILL.md +6 -6
  77. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  78. package/habilidades/eval-framework/SKILL.md +8 -3
  79. package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
  80. package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
  81. package/habilidades/harness-claude-code/SKILL.md +7 -3
  82. package/habilidades/infra-github-actions/SKILL.md +4 -3
  83. package/habilidades/instalar-sistema/SKILL.md +5 -1
  84. package/habilidades/memoria-busqueda/SKILL.md +31 -39
  85. package/habilidades/planear-fase/SKILL.md +9 -1
  86. package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
  87. package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
  88. package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
  89. package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
  90. package/habilidades/release-semver/SKILL.md +2 -2
  91. package/habilidades/swl-claudemd/SKILL.md +6 -7
  92. package/habilidades/swl-dashboard/SKILL.md +11 -43
  93. package/habilidades/tdd-workflow/SKILL.md +12 -7
  94. package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
  95. package/hooks/agente-lifecycle.js +2 -1
  96. package/hooks/aiisms-detector.js +13 -4
  97. package/hooks/audit-trail.js +2 -1
  98. package/hooks/auto-consolidacion.js +2 -1
  99. package/hooks/captura-acciones-post.js +2 -1
  100. package/hooks/captura-acciones-session.js +2 -1
  101. package/hooks/captura-feedback-usuario.js +3 -2
  102. package/hooks/claudemd-bloat-detector.js +12 -3
  103. package/hooks/claudemd-duplicacion-detector.js +13 -3
  104. package/hooks/contexto-iteracion.js +2 -1
  105. package/hooks/contexto-subagente.js +68 -68
  106. package/hooks/degradacion-instintos.js +2 -1
  107. package/hooks/extraccion-aprendizajes.js +109 -15
  108. package/hooks/grafo-contexto.js +2 -1
  109. package/hooks/guardrail-modelo.js +2 -1
  110. package/hooks/inbox-aviso.js +2 -1
  111. package/hooks/inyeccion-contexto.js +2 -1
  112. package/hooks/lib/agent-matcher.js +2 -1
  113. package/hooks/lib/agent-routing.js +2 -1
  114. package/hooks/lib/autonomia.js +5 -3
  115. package/hooks/lib/captura-acciones.js +2 -1
  116. package/hooks/lib/consolidation-lock.js +21 -10
  117. package/hooks/lib/etapa-auto-evolucion.js +10 -4
  118. package/hooks/lib/etapa-metricas.js +2 -1
  119. package/hooks/lib/etapa-perfil-usuario.js +20 -4
  120. package/hooks/lib/evolution-tracker.js +2 -1
  121. package/hooks/lib/gateway-notify.js +17 -3
  122. package/hooks/lib/loop-telemetry.js +5 -4
  123. package/hooks/lib/mcp-health.js +2 -1
  124. package/hooks/lib/memory-search.js +4 -0
  125. package/hooks/lib/merkle-audit.js +58 -6
  126. package/hooks/lib/notificacion-formato.js +58 -0
  127. package/hooks/lib/nudge-tracker.js +2 -1
  128. package/hooks/lib/otlp-exporter.js +2 -1
  129. package/hooks/lib/propose-step.js +3 -2
  130. package/hooks/lib/raiz-proyecto.js +127 -0
  131. package/hooks/lib/run-log.js +2 -1
  132. package/hooks/lib/singleton-guard.js +225 -27
  133. package/hooks/lib/telegram-cliente.js +28 -11
  134. package/hooks/notificacion-telegram.js +13 -3
  135. package/hooks/preservar-estado-pre-compact.js +2 -1
  136. package/hooks/proteccion-rutas.js +59 -3
  137. package/hooks/registro-turnos.js +2 -1
  138. package/hooks/resumen-sesion.js +2 -1
  139. package/hooks/risk-scoring.js +2 -1
  140. package/hooks/rotar-audit-auto.js +46 -20
  141. package/hooks/session-briefing.js +127 -1
  142. package/hooks/spec-gate.js +2 -1
  143. package/hooks/sugerir-contribuir.js +6 -3
  144. package/hooks/sugerir-regenerar-inventario.js +3 -2
  145. package/hooks/tdd-gate.js +2 -1
  146. package/hooks/telemetria-agentes.js +2 -1
  147. package/hooks/telemetria-skill-routing.js +2 -1
  148. package/hooks/tracking-costos.js +4 -3
  149. package/hooks/validar-formato-post-subagente.js +2 -1
  150. package/hooks/validar-intent-spec.js +2 -1
  151. package/hooks/validar-memoria-hook.js +13 -3
  152. package/hooks/validar-planning-paths.js +2 -1
  153. package/instintos/perfil-usuario.yaml +506 -3
  154. package/instintos/proyecto.yaml +78 -0
  155. package/llms.txt +29 -29
  156. package/manifiestos/canonical-hashes.json +5588 -4925
  157. package/manifiestos/hooks-config.json +469 -469
  158. package/manifiestos/invariantes-criticos.json +30 -30
  159. package/manifiestos/modulos.json +1429 -1423
  160. package/manifiestos/planning-paths.json +1 -0
  161. package/manifiestos/skills-lock.json +1275 -1275
  162. package/package.json +94 -95
  163. package/plugin.json +369 -369
  164. package/scripts/actualizar.js +3 -0
  165. package/scripts/auditar-clases-conocidas.js +134 -106
  166. package/scripts/benchmark-memoria.js +1 -0
  167. package/scripts/bootstrap-instintos.js +85 -14
  168. package/scripts/canario-hooks.js +166 -166
  169. package/scripts/cli/autonomia.js +23 -0
  170. package/scripts/cli/benchmark-memoria.js +37 -0
  171. package/scripts/cli/ciclo-autonomo.js +73 -0
  172. package/scripts/cli/ciclo-fase-b.js +102 -0
  173. package/scripts/cli/guardrail-metrics.js +39 -0
  174. package/scripts/cli/loop-telemetry.js +4 -2
  175. package/scripts/cli/memoria-search.js +69 -0
  176. package/scripts/cli/nudge-accionar.js +39 -0
  177. package/scripts/cli/run-eval.js +38 -0
  178. package/scripts/cli/run-skill-evals.js +13 -2
  179. package/scripts/derivar-feature-list.js +15 -14
  180. package/scripts/desinstalar.js +11 -0
  181. package/scripts/doctor.js +50 -13
  182. package/scripts/evidencia-valor.js +101 -101
  183. package/scripts/field-report.js +16 -16
  184. package/scripts/instalador.js +98 -7
  185. package/scripts/lib/activar-hooks-proyecto.js +116 -104
  186. package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
  187. package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
  188. package/scripts/lib/ciclo-autonomo/config.js +165 -0
  189. package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
  190. package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
  191. package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
  192. package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
  193. package/scripts/lib/ciclo-autonomo/index.js +301 -0
  194. package/scripts/lib/ciclo-autonomo/lock.js +124 -0
  195. package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
  196. package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
  197. package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
  198. package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
  199. package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
  200. package/scripts/lib/estado.js +9 -0
  201. package/scripts/lib/evidencia-valor.js +228 -228
  202. package/scripts/lib/expandir-targets.js +71 -71
  203. package/scripts/lib/gitignore-manifest.js +8 -1
  204. package/scripts/lib/hooks-settings.js +45 -0
  205. package/scripts/lib/limpiar-basura-global.js +161 -0
  206. package/scripts/lib/toml-merge.js +204 -204
  207. package/scripts/mcp-server/auth.js +105 -105
  208. package/scripts/mcp-server/cache.js +106 -106
  209. package/scripts/rotar-audit-logs.js +48 -2
  210. package/scripts/run-eval.js +1 -0
  211. package/scripts/run-skill-evals.js +287 -8
  212. package/scripts/smoke-test.js +16 -8
  213. package/scripts/tui/pantallas/install-wizard.js +69 -13
  214. package/scripts/validar.js +40 -1
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  const fs = require('fs');
20
+ const { raizProyecto } = require('./raiz-proyecto');
20
21
  const path = require('path');
21
22
  const os = require('os');
22
23
 
@@ -151,7 +152,7 @@ function getHealthReport() {
151
152
  * @returns {string[]} Nombres de servidores MCP configurados.
152
153
  */
153
154
  function detectConfiguredMCPs(cwd) {
154
- const dir = cwd || process.cwd();
155
+ const dir = cwd || raizProyecto(process.cwd());
155
156
  const mcpPath = path.join(dir, '.mcp.json');
156
157
  try {
157
158
  const config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
@@ -246,6 +246,10 @@ function _buscarEnSesionesFallback(baseDir, terminos) {
246
246
  * @returns {object|null}
247
247
  */
248
248
  function obtenerSesion(baseDir, id) {
249
+ // El id interpola en un path: solo caracteres de id legítimo (ses-...,
250
+ // apr-N) — sin separadores ni '..' que resuelvan fuera de sessions/
251
+ // (defensa en profundidad para el CLI `swl-ses memoria-search fetch`).
252
+ if (!/^[a-zA-Z0-9_-]+$/.test(String(id))) return null;
249
253
  const sessionPath = path.join(baseDir, '.planning', 'sessions', `${id}.json`);
250
254
  if (!fs.existsSync(sessionPath)) return null;
251
255
 
@@ -18,8 +18,34 @@
18
18
  const crypto = require('crypto');
19
19
  const fs = require('fs');
20
20
 
21
- function _sha256(data) {
22
- return crypto.createHash('sha256').update(data).digest('hex');
21
+ /**
22
+ * Hash de cada entrada de la cadena. Si `SWL_AUDIT_HMAC_KEY` está definida, usa
23
+ * HMAC-SHA256 con esa clave; si no, SHA256 plano (default zero-config).
24
+ *
25
+ * MODELO DE AMENAZA (resuelve el residual "SHA256 sin clave"):
26
+ * - SIN clave (default): la cadena es tamper-EVIDENTE ante corrupción
27
+ * accidental (escrituras parciales, error de disco, edición por una
28
+ * herramienta buggy) y ediciones parciales. NO defiende contra quien tenga
29
+ * acceso de escritura Y pueda recomputar hashes — pero en una máquina local
30
+ * de un solo usuario ESE es el dueño, que es la raíz de confianza: no hay
31
+ * adversario distinto del dueño. Correcto por diseño para audit local.
32
+ * - CON clave en env (opt-in): defiende contra un adversario a nivel de
33
+ * ARCHIVO —dependencia npm maliciosa, ransomware, conflicto de sync— que
34
+ * puede escribir `.planning/` pero NO leer el entorno del proceso del hook.
35
+ * Sin la clave del env no puede forjar HMACs válidos → la manipulación se
36
+ * detecta. NO defiende contra quien lea el env del proceso (el dueño);
37
+ * para eso hace falta anclaje externo (publicar el último hash en un store
38
+ * append-only inmutable), fuera del alcance de este módulo.
39
+ * - La clave vive en el ENV (no en disco): `setx SWL_AUDIT_HMAC_KEY <valor>`
40
+ * o export. Cambiar la clave inicia una época nueva e invalida cadenas
41
+ * previas (esperado). Cargar la clave desde el env en cada llamada para
42
+ * respetar rotación sin reiniciar procesos de larga vida.
43
+ */
44
+ function _hash(data) {
45
+ const clave = process.env.SWL_AUDIT_HMAC_KEY;
46
+ return clave
47
+ ? crypto.createHmac('sha256', clave).update(data).digest('hex')
48
+ : crypto.createHash('sha256').update(data).digest('hex');
23
49
  }
24
50
 
25
51
  function crearEntrada(datos, hashAnterior) {
@@ -32,7 +58,7 @@ function crearEntrada(datos, hashAnterior) {
32
58
  result: datos.result || 'ok',
33
59
  prev: hashAnterior,
34
60
  });
35
- const hash = _sha256(payload);
61
+ const hash = _hash(payload);
36
62
  return { payload, hash };
37
63
  }
38
64
 
@@ -56,12 +82,38 @@ function appendAudit(rutaArchivo, datos) {
56
82
  return hash;
57
83
  }
58
84
 
59
- function verificarCadena(rutaArchivo) {
85
+ /**
86
+ * Verifica la integridad de la cadena Merkle de un archivo de auditoría.
87
+ *
88
+ * @param {string} rutaArchivo
89
+ * @param {object} [opts]
90
+ * @param {string} [opts.hashInicial] - Hash del que la cadena continúa. Por
91
+ * defecto el génesis ('0'*64). Tras una rotación, el archivo activo empieza
92
+ * en una entrada cuyo `prev` apunta a una entrada ya archivada (no al
93
+ * génesis) — pasar aquí el `hashInicial` del checkpoint
94
+ * (`.checkpoint-<archivo>.json`) para validar el activo como CONTINUACIÓN de
95
+ * la cadena archivada, sin falso positivo de rotura (nemesis iter-1 S3-5).
96
+ *
97
+ * LÍMITE (re-auditoría nemesis iter-2): con `hashInicial` esto prueba la
98
+ * consistencia INTERNA del activo, NO que continúe la cadena archivada — el
99
+ * check `prev===hashInicial` de la entrada-0 es tautológico si `hashInicial`
100
+ * viene del propio checkpoint auto-derivado. Un consumidor que quiera probar
101
+ * la continuidad archivo↔activo debe recomputar independientemente el hash de
102
+ * la última entrada del `.gz` archivado y verificar que coincide con
103
+ * `hashInicial`. La fuerza criptográfica de la cadena depende de `_hash`
104
+ * (ver su docstring): SHA256 plano (accidental) o HMAC con `SWL_AUDIT_HMAC_KEY`
105
+ * (defensa contra manipulación a nivel de archivo sin la clave del env).
106
+ * verificarCadena usa la MISMA función `_hash`, así que recomputa con la
107
+ * clave activa del env — verificar con la clave con que se escribió.
108
+ */
109
+ function verificarCadena(rutaArchivo, opts = {}) {
60
110
  if (!fs.existsSync(rutaArchivo)) return { valido: true, entradas: 0 };
61
111
 
62
112
  const contenido = fs.readFileSync(rutaArchivo, 'utf8').trim();
63
113
  const lineas = contenido.split('\n').filter(Boolean);
64
- let hashEsperado = '0'.repeat(64);
114
+ let hashEsperado = (typeof opts.hashInicial === 'string' && opts.hashInicial)
115
+ ? opts.hashInicial
116
+ : '0'.repeat(64);
65
117
  let roturaEn = -1;
66
118
 
67
119
  for (let i = 0; i < lineas.length; i++) {
@@ -73,7 +125,7 @@ function verificarCadena(rutaArchivo) {
73
125
  }
74
126
  const { hash: storedHash, ...resto } = entrada;
75
127
  const payloadRecalc = JSON.stringify(resto);
76
- const hashRecalc = _sha256(payloadRecalc);
128
+ const hashRecalc = _hash(payloadRecalc);
77
129
  if (hashRecalc !== storedHash) {
78
130
  roturaEn = i;
79
131
  break;
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * notificacion-formato.js — Formato del cuerpo de las notificaciones.
5
+ *
6
+ * Módulo sin efectos secundarios (solo funciones puras), separado del hook
7
+ * `notificacion-telegram.js` para poder unit-testearlo sin disparar el `run()`
8
+ * del hook (que se ejecuta al `require`, porque los hooks se invocan vía el
9
+ * wrapper `node -e "require('./hooks/X.js')"` — no admiten gate de entrypoint).
10
+ *
11
+ * Zero-deps.
12
+ *
13
+ * @module hooks/lib/notificacion-formato
14
+ */
15
+
16
+ /**
17
+ * Resume el último mensaje del asistente para una notificación: una alerta debe
18
+ * ser un extracto BREVE y legible, no el volcado crudo del mensaje completo (que
19
+ * mete tablas markdown, encabezados y cientos de líneas). Quita el ruido
20
+ * estructural de markdown (fences de código, filas de tabla, reglas horizontales)
21
+ * y el markdown inline (negritas, itálicas, código, enlaces, viñetas), y trunca a
22
+ * `maxChars`.
23
+ *
24
+ * @param {string} texto - Mensaje completo del asistente (markdown).
25
+ * @param {number} [maxChars=400] - Largo máximo del extracto.
26
+ * @returns {string} Extracto limpio y corto en texto plano.
27
+ */
28
+ function resumirParaNotificacion(texto, maxChars = 400) {
29
+ if (!texto) return '';
30
+ const limpias = [];
31
+ let enFence = false;
32
+ for (const raw of String(texto).split(/\r?\n/)) {
33
+ const l = raw.trim();
34
+ if (/^```/.test(l)) { enFence = !enFence; continue; } // abre/cierra bloque de código
35
+ if (enFence) continue; // omitir contenido de código
36
+ if (/^\|.*\|$/.test(l)) continue; // omitir filas de tabla
37
+ if (/^[-=_*]{3,}$/.test(l)) continue; // omitir reglas horizontales
38
+ if (l === '') {
39
+ if (limpias.length && limpias[limpias.length - 1] !== '') limpias.push('');
40
+ continue;
41
+ }
42
+ const t = l
43
+ .replace(/^#{1,6}\s+/, '') // encabezados
44
+ .replace(/^>\s?/, '') // citas
45
+ .replace(/^[-*+]\s+/, '• ') // viñetas → •
46
+ .replace(/^\d+\.\s+/, '') // listas numeradas
47
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // negritas
48
+ .replace(/(^|[^*])\*([^*]+)\*/g, '$1$2') // itálicas
49
+ .replace(/`([^`]+)`/g, '$1') // código inline
50
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1'); // enlaces → texto
51
+ limpias.push(t);
52
+ }
53
+ const resumen = limpias.join('\n').replace(/\n{3,}/g, '\n\n').trim();
54
+ if (resumen.length <= maxChars) return resumen;
55
+ return resumen.slice(0, maxChars - 1).trimEnd() + '…';
56
+ }
57
+
58
+ module.exports = { resumirParaNotificacion };
@@ -22,6 +22,7 @@
22
22
  */
23
23
 
24
24
  const fs = require('fs');
25
+ const { raizProyecto } = require('./raiz-proyecto');
25
26
  const path = require('path');
26
27
 
27
28
  // Escritura atómica para reescritura completa del JSONL (marcar accionado).
@@ -45,7 +46,7 @@ try {
45
46
  // Configuración
46
47
  // ---------------------------------------------------------------------------
47
48
 
48
- const DIR_EVOL = path.join(process.cwd(), '.planning', 'evolution');
49
+ const DIR_EVOL = path.join(raizProyecto(process.cwd()), '.planning', 'evolution');
49
50
  const LOG_PATH = path.join(DIR_EVOL, 'nudges.jsonl');
50
51
  const ALERT_PATH = path.join(DIR_EVOL, 'alertas-persistentes.json');
51
52
 
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  const fs = require('fs');
22
+ const { raizProyecto } = require('./raiz-proyecto');
22
23
  const path = require('path');
23
24
  const crypto = require('crypto');
24
25
  const http = require('http');
@@ -277,7 +278,7 @@ class OtlpLocalProcessor extends TracingProcessor {
277
278
  constructor(cwd) {
278
279
  super();
279
280
  /** @type {string} */
280
- this._cwd = String(cwd || process.cwd());
281
+ this._cwd = String(cwd || raizProyecto(process.cwd()));
281
282
  }
282
283
 
283
284
  /**
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  const fs = require('fs');
23
+ const { raizProyecto } = require('./raiz-proyecto');
23
24
  const path = require('path');
24
25
  const { execFileSync } = require('child_process');
25
26
 
@@ -209,7 +210,7 @@ const UMBRAL = {
209
210
  };
210
211
 
211
212
  function telemetriaPath(baseDir) {
212
- return path.join(baseDir || process.cwd(), ...TELE_PATH);
213
+ return path.join(baseDir || raizProyecto(process.cwd()), ...TELE_PATH);
213
214
  }
214
215
 
215
216
  function _catVacia() {
@@ -332,7 +333,7 @@ function main(argv) {
332
333
  for (const a of args) {
333
334
  if (a.startsWith('--rango=')) rango = a.slice('--rango='.length);
334
335
  }
335
- const baseDir = process.cwd();
336
+ const baseDir = raizProyecto(process.cwd());
336
337
  const { paths, diff } = _gitDiff(rango);
337
338
  const { señales } = evaluarSenales(paths, diff);
338
339
  registrarPropose(baseDir, señales);
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * raiz-proyecto.js — Resolución de la raíz del proyecto para hooks que
5
+ * escriben telemetría/memoria en `.planning/`.
6
+ *
7
+ * El CWD de arranque de una sesión NO es la frontera del proyecto: en un
8
+ * monorepo con sesiones abiertas en subdirectorios (`backend/`, `frontend/`),
9
+ * anclar `.planning/` a `process.cwd()` fragmenta la memoria del proyecto en
10
+ * árboles huérfanos (DT-HOOKS-RAIZ-GIT; misma clase que el fix de
11
+ * proteccion-rutas). La frontera correcta es la raíz del repositorio git,
12
+ * con una excepción: un sub-proyecto anidado que ya tiene su propio
13
+ * `.planning/` REAL (con marcadores de proyecto) conserva su árbol local.
14
+ *
15
+ * Reglas de resolución (en orden):
16
+ * 1. Ascendiendo desde `desde`, el primer directorio cuyo `.planning/`
17
+ * contiene un marcador de proyecto (PROYECTO.md, HOJA-RUTA.md,
18
+ * ESTADO.md) gana — respeta sub-proyectos legítimos en monorepos.
19
+ * Un `.planning/` sin marcadores (solo telemetría huérfana de hooks)
20
+ * NO cuenta: eso es el síntoma del bug, no un proyecto.
21
+ * 2. Si ningún nivel tiene `.planning/` real, gana la raíz del repo git
22
+ * (`.git` como directorio o como archivo — worktrees).
23
+ * 3. Sin `.git` alcanzable: fallback al propio `desde`.
24
+ *
25
+ * La ascensión se detiene en el HOME del usuario (sin incluirlo) cuando se
26
+ * llega ahí subiendo: un dotfiles-repo en `~` — o un `~/.planning` sembrado
27
+ * por `swl-ses doctor` en una instalación global — no debe capturar la
28
+ * memoria de un proyecto sin git que viva debajo. La comparación con el HOME
29
+ * se hace en forma CANÓNICA (realpath): en Windows `os.tmpdir()` puede
30
+ * devolver el nombre corto 8.3 (`C:\Users\SAUL~1.WAD`) mientras `os.homedir()`
31
+ * devuelve el largo (`C:\Users\saul.wade`); sin canonicalizar, la frontera no
32
+ * dispararía y capturaría `~/.planning` o `~/.git` (rompía 38 tests locales
33
+ * en Windows, verde en CI Linux porque `/tmp` no cuelga de `$HOME`).
34
+ *
35
+ * Zero-deps (regla de hooks/lib/). Consumidor inicial:
36
+ * hooks/extraccion-aprendizajes.js; la flota completa migró el 2026-07-10
37
+ * (cierre de DT-HOOKS-RAIZ-GIT).
38
+ *
39
+ * LÍMITE DE USO (hallazgo revisor-seguridad 2026-07-10): esta ancla es SOLO
40
+ * para DATOS (.planning/, instintos/, lecturas de config del proyecto).
41
+ * NUNCA resolver con ella EJECUTABLES ni módulos a require()/spawn(): la
42
+ * raíz resuelta pertenece al árbol del proyecto, y un repositorio no
43
+ * confiable puede plantar el script (marcador .planning/PROYECTO.md +
44
+ * scripts/x.js hostil = ejecución de código al disparar el hook). Los
45
+ * ejecutables de la distribución SWL se resuelven desde __dirname
46
+ * (ubicación instalada del hook). Test guardián:
47
+ * tests/hooks/ejecutables-desde-instalacion.test.js.
48
+ */
49
+
50
+ const fs = require('fs');
51
+ const path = require('path');
52
+ const os = require('os');
53
+
54
+ const MARCADORES_PROYECTO = ['PROYECTO.md', 'HOJA-RUTA.md', 'ESTADO.md'];
55
+ const MAX_NIVELES = 40;
56
+
57
+ /**
58
+ * ¿El directorio tiene un `.planning/` de proyecto real (no huérfano)?
59
+ * @param {string} dir
60
+ * @returns {boolean}
61
+ */
62
+ function tienePlanningReal(dir) {
63
+ try {
64
+ return MARCADORES_PROYECTO.some(m =>
65
+ fs.existsSync(path.join(dir, '.planning', m))
66
+ );
67
+ } catch (_) {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Canonicaliza un path resolviendo enlaces simbólicos y el nombre corto 8.3
74
+ * de Windows (`C:\Users\SAUL~1.WAD` → `C:\Users\saul.wade`). Solo se usa para
75
+ * la comparación de la frontera del HOME: el valor de RETORNO de raizProyecto
76
+ * conserva la forma del argumento recibido, no la canónica. Si el path no
77
+ * existe (no se puede resolver), cae a `path.resolve`.
78
+ * @param {string} p
79
+ * @returns {string}
80
+ */
81
+ function canonicalizar(p) {
82
+ try {
83
+ return fs.realpathSync.native(p);
84
+ } catch (_) {
85
+ return path.resolve(p);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Resuelve la raíz del proyecto a la que deben anclarse las escrituras
91
+ * de `.planning/`.
92
+ *
93
+ * @param {string} [desde] - Directorio de partida (default: process.cwd()).
94
+ * @returns {string} Ruta absoluta de la raíz del proyecto.
95
+ */
96
+ function raizProyecto(desde) {
97
+ const inicio = path.resolve(desde || process.cwd());
98
+ let actual = inicio;
99
+ let homedirCanon = null;
100
+ try {
101
+ homedirCanon = canonicalizar(path.resolve(os.homedir()));
102
+ } catch (_) { /* sin home resolvible — seguir sin el guard */ }
103
+
104
+ for (let i = 0; i < MAX_NIVELES; i++) {
105
+ // Frontera del HOME: si llegamos aquí ASCENDIENDO, no seguir — un
106
+ // dotfiles-repo (o un ~/.planning sembrado por doctor) en ~ no es la raíz
107
+ // de un proyecto que vive debajo. Comparación canónica: iguala el nombre
108
+ // corto 8.3 heredado de os.tmpdir() con el HOME largo de os.homedir().
109
+ if (homedirCanon && i > 0 && canonicalizar(actual) === homedirCanon) break;
110
+
111
+ if (tienePlanningReal(actual)) return actual;
112
+
113
+ try {
114
+ if (fs.existsSync(path.join(actual, '.git'))) return actual;
115
+ } catch (_) {
116
+ break;
117
+ }
118
+
119
+ const padre = path.dirname(actual);
120
+ if (padre === actual) break; // raíz del filesystem
121
+ actual = padre;
122
+ }
123
+
124
+ return inicio;
125
+ }
126
+
127
+ module.exports = { raizProyecto, tienePlanningReal, canonicalizar };
@@ -38,6 +38,7 @@
38
38
  */
39
39
 
40
40
  const fs = require('fs');
41
+ const { raizProyecto } = require('./raiz-proyecto');
41
42
  const path = require('path');
42
43
  const readline = require('readline');
43
44
  const os = require('os');
@@ -50,7 +51,7 @@ const os = require('os');
50
51
  * Directorio donde se almacenan los JSONL de runs.
51
52
  * Relativo al cwd del proceso (raíz del proyecto SWL).
52
53
  */
53
- const RUNS_DIR = path.join(process.cwd(), '.planning', 'runs');
54
+ const RUNS_DIR = path.join(raizProyecto(process.cwd()), '.planning', 'runs');
54
55
 
55
56
  /**
56
57
  * Número máximo de runs a mantener por directorio.