@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.
- package/CLAUDE.md +9 -9
- package/README.md +37 -37
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/accesibilidad-wcag-swl.md +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +932 -908
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +218 -218
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/tdd-qa-swl.md +393 -393
- package/bin/swl-ses.js +32 -7
- package/comandos/swl/actualizar.md +3 -3
- package/comandos/swl/aprender.md +13 -0
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/evaluar-skill.md +18 -3
- package/comandos/swl/evolucion-continua.md +73 -0
- package/comandos/swl/evolucionar.md +13 -0
- package/comandos/swl/instalar.md +4 -4
- package/comandos/swl/notificaciones.md +1 -1
- package/comandos/swl/status.md +2 -2
- package/gateway/cron/jobs.example.json +12 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +19 -1
- package/habilidades/autoresearch/SKILL.md +3 -2
- package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
- package/habilidades/benchmark-memoria/SKILL.md +7 -7
- package/habilidades/changelog-generator/SKILL.md +1 -1
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
- package/habilidades/compactacion-contexto/SKILL.md +2 -1
- package/habilidades/contenedores-docker/SKILL.md +4 -2
- package/habilidades/context-builder/SKILL.md +4 -0
- package/habilidades/doubt-driven-review/SKILL.md +17 -1
- package/habilidades/drift-detection/SKILL.md +6 -1
- package/habilidades/ejecutar-fase/SKILL.md +6 -6
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/eval-framework/SKILL.md +8 -3
- package/habilidades/extractor-de-aprendizajes/SKILL.md +8 -2
- package/habilidades/git-worktrees-paralelo/SKILL.md +19 -1
- package/habilidades/harness-claude-code/SKILL.md +7 -3
- package/habilidades/infra-github-actions/SKILL.md +4 -3
- package/habilidades/instalar-sistema/SKILL.md +5 -1
- package/habilidades/memoria-busqueda/SKILL.md +31 -39
- package/habilidades/planear-fase/SKILL.md +9 -1
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -166
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -85
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
- package/habilidades/proceso-ingenieria-requerimientos/SKILL.md +147 -147
- package/habilidades/release-semver/SKILL.md +2 -2
- package/habilidades/swl-claudemd/SKILL.md +6 -7
- package/habilidades/swl-dashboard/SKILL.md +11 -43
- package/habilidades/tdd-workflow/SKILL.md +12 -7
- package/habilidades/validacion-ci-sistema/SKILL.md +1 -1
- package/hooks/agente-lifecycle.js +2 -1
- package/hooks/aiisms-detector.js +13 -4
- package/hooks/audit-trail.js +2 -1
- package/hooks/auto-consolidacion.js +2 -1
- package/hooks/captura-acciones-post.js +2 -1
- package/hooks/captura-acciones-session.js +2 -1
- package/hooks/captura-feedback-usuario.js +3 -2
- package/hooks/claudemd-bloat-detector.js +12 -3
- package/hooks/claudemd-duplicacion-detector.js +13 -3
- package/hooks/contexto-iteracion.js +2 -1
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/degradacion-instintos.js +2 -1
- package/hooks/extraccion-aprendizajes.js +109 -15
- package/hooks/grafo-contexto.js +2 -1
- package/hooks/guardrail-modelo.js +2 -1
- package/hooks/inbox-aviso.js +2 -1
- package/hooks/inyeccion-contexto.js +2 -1
- package/hooks/lib/agent-matcher.js +2 -1
- package/hooks/lib/agent-routing.js +2 -1
- package/hooks/lib/autonomia.js +5 -3
- package/hooks/lib/captura-acciones.js +2 -1
- package/hooks/lib/consolidation-lock.js +21 -10
- package/hooks/lib/etapa-auto-evolucion.js +10 -4
- package/hooks/lib/etapa-metricas.js +2 -1
- package/hooks/lib/etapa-perfil-usuario.js +20 -4
- package/hooks/lib/evolution-tracker.js +2 -1
- package/hooks/lib/gateway-notify.js +17 -3
- package/hooks/lib/loop-telemetry.js +5 -4
- package/hooks/lib/mcp-health.js +2 -1
- package/hooks/lib/memory-search.js +4 -0
- package/hooks/lib/merkle-audit.js +58 -6
- package/hooks/lib/notificacion-formato.js +58 -0
- package/hooks/lib/nudge-tracker.js +2 -1
- package/hooks/lib/otlp-exporter.js +2 -1
- package/hooks/lib/propose-step.js +3 -2
- package/hooks/lib/raiz-proyecto.js +127 -0
- package/hooks/lib/run-log.js +2 -1
- package/hooks/lib/singleton-guard.js +225 -27
- package/hooks/lib/telegram-cliente.js +28 -11
- package/hooks/notificacion-telegram.js +13 -3
- package/hooks/preservar-estado-pre-compact.js +2 -1
- package/hooks/proteccion-rutas.js +59 -3
- package/hooks/registro-turnos.js +2 -1
- package/hooks/resumen-sesion.js +2 -1
- package/hooks/risk-scoring.js +2 -1
- package/hooks/rotar-audit-auto.js +46 -20
- package/hooks/session-briefing.js +127 -1
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +6 -3
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/hooks/telemetria-agentes.js +2 -1
- package/hooks/telemetria-skill-routing.js +2 -1
- package/hooks/tracking-costos.js +4 -3
- package/hooks/validar-formato-post-subagente.js +2 -1
- package/hooks/validar-intent-spec.js +2 -1
- package/hooks/validar-memoria-hook.js +13 -3
- package/hooks/validar-planning-paths.js +2 -1
- package/instintos/perfil-usuario.yaml +506 -3
- package/instintos/proyecto.yaml +78 -0
- package/llms.txt +29 -29
- package/manifiestos/canonical-hashes.json +5588 -4925
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/modulos.json +1429 -1423
- package/manifiestos/planning-paths.json +1 -0
- package/manifiestos/skills-lock.json +1275 -1275
- package/package.json +94 -95
- package/plugin.json +369 -369
- package/scripts/actualizar.js +3 -0
- package/scripts/auditar-clases-conocidas.js +134 -106
- package/scripts/benchmark-memoria.js +1 -0
- package/scripts/bootstrap-instintos.js +85 -14
- package/scripts/canario-hooks.js +166 -166
- package/scripts/cli/autonomia.js +23 -0
- package/scripts/cli/benchmark-memoria.js +37 -0
- package/scripts/cli/ciclo-autonomo.js +73 -0
- package/scripts/cli/ciclo-fase-b.js +102 -0
- package/scripts/cli/guardrail-metrics.js +39 -0
- package/scripts/cli/loop-telemetry.js +4 -2
- package/scripts/cli/memoria-search.js +69 -0
- package/scripts/cli/nudge-accionar.js +39 -0
- package/scripts/cli/run-eval.js +38 -0
- package/scripts/cli/run-skill-evals.js +13 -2
- package/scripts/derivar-feature-list.js +15 -14
- package/scripts/desinstalar.js +11 -0
- package/scripts/doctor.js +50 -13
- package/scripts/evidencia-valor.js +101 -101
- package/scripts/field-report.js +16 -16
- package/scripts/instalador.js +98 -7
- package/scripts/lib/activar-hooks-proyecto.js +116 -104
- package/scripts/lib/auditar-invocaciones-comandos.js +96 -6
- package/scripts/lib/ciclo-autonomo/candidatos.js +174 -0
- package/scripts/lib/ciclo-autonomo/config.js +165 -0
- package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -0
- package/scripts/lib/ciclo-autonomo/fallback.js +77 -0
- package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -0
- package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -0
- package/scripts/lib/ciclo-autonomo/index.js +301 -0
- package/scripts/lib/ciclo-autonomo/lock.js +124 -0
- package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -0
- package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -0
- package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -0
- package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -0
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -0
- package/scripts/lib/estado.js +9 -0
- package/scripts/lib/evidencia-valor.js +228 -228
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/gitignore-manifest.js +8 -1
- package/scripts/lib/hooks-settings.js +45 -0
- package/scripts/lib/limpiar-basura-global.js +161 -0
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/rotar-audit-logs.js +48 -2
- package/scripts/run-eval.js +1 -0
- package/scripts/run-skill-evals.js +287 -8
- package/scripts/smoke-test.js +16 -8
- package/scripts/tui/pantallas/install-wizard.js +69 -13
- package/scripts/validar.js +40 -1
package/hooks/lib/mcp-health.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
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 =
|
|
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
|
-
|
|
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 = '
|
|
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 =
|
|
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 };
|
package/hooks/lib/run-log.js
CHANGED
|
@@ -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.
|