@saulwade/swl-ses 2.5.2 → 2.6.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.
- package/CLAUDE.md +194 -192
- package/README.md +600 -600
- package/agentes/auto-evolucion-swl.md +27 -3
- package/bin/swl-ses.js +32 -6
- package/comandos/swl/actualizar.md +174 -174
- package/comandos/swl/adoptar-proyecto.md +265 -265
- package/comandos/swl/aprender.md +836 -823
- package/comandos/swl/aprobar-plan.md +146 -146
- package/comandos/swl/auditar-deps.md +134 -134
- package/comandos/swl/autoresearch.md +264 -264
- package/comandos/swl/ayuda.md +224 -224
- package/comandos/swl/brainstorm.md +51 -51
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/checkpoint.md +325 -325
- package/comandos/swl/claudemd.md +234 -234
- package/comandos/swl/compactar.md +310 -310
- package/comandos/swl/configurar-ci.md +235 -235
- package/comandos/swl/contexto.md +110 -110
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/crear-skill.md +292 -292
- package/comandos/swl/cron.md +194 -194
- package/comandos/swl/deuda-codigo.md +97 -97
- package/comandos/swl/discutir-fase.md +169 -169
- package/comandos/swl/ejecutar-fase.md +233 -233
- package/comandos/swl/evaluar-skill.md +520 -505
- package/comandos/swl/evolucion-continua.md +73 -0
- package/comandos/swl/evolucionar.md +267 -254
- package/comandos/swl/exportar-vault.md +583 -583
- package/comandos/swl/fix.md +118 -118
- package/comandos/swl/gateway.md +158 -158
- package/comandos/swl/inbox.md +116 -116
- package/comandos/swl/instalar.md +220 -220
- package/comandos/swl/instintos.md +86 -86
- package/comandos/swl/mapear-codebase.md +312 -312
- package/comandos/swl/mcp-status.md +175 -175
- package/comandos/swl/modelo.md +100 -100
- package/comandos/swl/nemesis.md +433 -433
- package/comandos/swl/notificaciones.md +299 -299
- package/comandos/swl/nuevo-proyecto.md +251 -251
- package/comandos/swl/planear-fase.md +263 -263
- package/comandos/swl/plugins.md +256 -256
- package/comandos/swl/predecir.md +169 -169
- package/comandos/swl/reflect-skills.md +125 -125
- package/comandos/swl/release.md +450 -450
- package/comandos/swl/revisar-impacto.md +201 -201
- package/comandos/swl/revisar.md +330 -330
- package/comandos/swl/seguridad.md +189 -189
- package/comandos/swl/sesiones.md +200 -200
- package/comandos/swl/skill-search.md +113 -113
- package/comandos/swl/status.md +343 -343
- package/comandos/swl/verificar.md +817 -817
- package/comandos/swl/wiki.md +620 -620
- package/gateway/cron/jobs.example.json +12 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -276
- package/habilidades/autoresearch/SKILL.md +3 -2
- package/habilidades/benchmark-memoria/SKILL.md +7 -7
- package/habilidades/changelog-generator/SKILL.md +174 -174
- package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
- package/habilidades/checkpoints-verificacion/SKILL.md +6 -0
- package/habilidades/context-builder/SKILL.md +4 -0
- package/habilidades/doubt-driven-review/SKILL.md +207 -191
- package/habilidades/drift-detection/SKILL.md +6 -1
- package/habilidades/ejecutar-fase/SKILL.md +6 -6
- package/habilidades/eval-framework/SKILL.md +8 -3
- package/habilidades/harness-claude-code/SKILL.md +314 -308
- package/habilidades/infra-github-actions/SKILL.md +4 -3
- package/habilidades/instalar-sistema/SKILL.md +227 -223
- package/habilidades/memoria-busqueda/SKILL.md +31 -39
- package/habilidades/planear-fase/SKILL.md +358 -350
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +3 -2
- package/habilidades/release-semver/SKILL.md +4 -2
- package/habilidades/swl-claudemd/SKILL.md +6 -7
- package/habilidades/swl-dashboard/SKILL.md +11 -43
- package/habilidades/tdd-workflow/SKILL.md +749 -744
- 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/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 +193 -179
- 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/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 +102 -0
- package/hooks/lib/run-log.js +2 -1
- package/hooks/lib/singleton-guard.js +218 -27
- package/hooks/lib/telegram-cliente.js +17 -8
- 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/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +53 -0
- package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +372 -0
- package/instintos/perfil-usuario.yaml +506 -3
- package/instintos/proyecto.yaml +78 -0
- package/llms.txt +2 -2
- package/manifiestos/canonical-hashes.json +664 -2
- package/manifiestos/modulos.json +19 -14
- package/manifiestos/planning-paths.json +1 -0
- package/manifiestos/skills-lock.json +53 -53
- package/package.json +2 -3
- package/plugin.json +2 -2
- package/scripts/actualizar.js +3 -0
- package/scripts/auditar-clases-conocidas.js +32 -4
- package/scripts/benchmark-memoria.js +1 -0
- 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 +24 -10
- package/scripts/instalador.js +106 -7
- package/scripts/lib/activar-hooks-proyecto.js +116 -0
- 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 +1 -1
- package/scripts/lib/gitignore-manifest.js +8 -1
- package/scripts/lib/hooks-settings.js +45 -0
- 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 +403 -347
- package/scripts/validar.js +40 -1
|
@@ -67,11 +67,18 @@ function isPidAlive(pid) {
|
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
69
|
* Ruta del lock file.
|
|
70
|
+
*
|
|
70
71
|
* @param {string} baseDir - Raíz del proyecto.
|
|
72
|
+
* @param {object} [opts] - Opciones.
|
|
73
|
+
* @param {string} [opts.lockFilename] - Nombre de archivo alterno al default
|
|
74
|
+
* `.consolidation-lock`. Permite a otros consumidores (ej. el ciclo
|
|
75
|
+
* autónomo, Fase 21 T-02) usar un lockfile propio sin colisionar con el
|
|
76
|
+
* de auto-consolidación, reutilizando el mismo patrón PID+staleness.
|
|
71
77
|
* @returns {string}
|
|
72
78
|
*/
|
|
73
|
-
function lockPath(baseDir) {
|
|
74
|
-
|
|
79
|
+
function lockPath(baseDir, opts = {}) {
|
|
80
|
+
const filename = (opts && opts.lockFilename) || LOCK_FILENAME;
|
|
81
|
+
return path.join(baseDir, '.planning', filename);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
// ---------------------------------------------------------------------------
|
|
@@ -82,10 +89,11 @@ function lockPath(baseDir) {
|
|
|
82
89
|
* Obtiene el timestamp de la última consolidación exitosa.
|
|
83
90
|
*
|
|
84
91
|
* @param {string} baseDir - Raíz del proyecto.
|
|
92
|
+
* @param {object} [opts] - Ver {@link lockPath}.
|
|
85
93
|
* @returns {number} Epoch ms de la última consolidación, 0 si nunca.
|
|
86
94
|
*/
|
|
87
|
-
function getLastConsolidatedAt(baseDir) {
|
|
88
|
-
const lp = lockPath(baseDir);
|
|
95
|
+
function getLastConsolidatedAt(baseDir, opts = {}) {
|
|
96
|
+
const lp = lockPath(baseDir, opts);
|
|
89
97
|
try {
|
|
90
98
|
const stat = fs.statSync(lp);
|
|
91
99
|
return stat.mtimeMs;
|
|
@@ -150,12 +158,13 @@ function countNewSessions(baseDir) {
|
|
|
150
158
|
* Intenta adquirir el lock de consolidación.
|
|
151
159
|
*
|
|
152
160
|
* @param {string} baseDir
|
|
161
|
+
* @param {object} [opts] - Ver {@link lockPath}.
|
|
153
162
|
* @returns {{ acquired: boolean, priorMtime: number }}
|
|
154
163
|
* acquired: true si el lock fue adquirido
|
|
155
164
|
* priorMtime: mtime anterior (para rollback)
|
|
156
165
|
*/
|
|
157
|
-
function tryAcquireLock(baseDir) {
|
|
158
|
-
const lp = lockPath(baseDir);
|
|
166
|
+
function tryAcquireLock(baseDir, opts = {}) {
|
|
167
|
+
const lp = lockPath(baseDir, opts);
|
|
159
168
|
const dir = path.dirname(lp);
|
|
160
169
|
|
|
161
170
|
// Asegurar directorio
|
|
@@ -200,9 +209,10 @@ function tryAcquireLock(baseDir) {
|
|
|
200
209
|
* Libera el lock actualizando el mtime a "ahora" (consolidación exitosa).
|
|
201
210
|
*
|
|
202
211
|
* @param {string} baseDir
|
|
212
|
+
* @param {object} [opts] - Ver {@link lockPath}.
|
|
203
213
|
*/
|
|
204
|
-
function releaseLock(baseDir) {
|
|
205
|
-
const lp = lockPath(baseDir);
|
|
214
|
+
function releaseLock(baseDir, opts = {}) {
|
|
215
|
+
const lp = lockPath(baseDir, opts);
|
|
206
216
|
try {
|
|
207
217
|
// Actualizar mtime a ahora = "última consolidación fue ahora"
|
|
208
218
|
const now = Date.now() / 1000;
|
|
@@ -216,9 +226,10 @@ function releaseLock(baseDir) {
|
|
|
216
226
|
*
|
|
217
227
|
* @param {string} baseDir
|
|
218
228
|
* @param {number} priorMtime - Epoch ms del mtime anterior.
|
|
229
|
+
* @param {object} [opts] - Ver {@link lockPath}.
|
|
219
230
|
*/
|
|
220
|
-
function rollbackLock(baseDir, priorMtime) {
|
|
221
|
-
const lp = lockPath(baseDir);
|
|
231
|
+
function rollbackLock(baseDir, priorMtime, opts = {}) {
|
|
232
|
+
const lp = lockPath(baseDir, opts);
|
|
222
233
|
try {
|
|
223
234
|
if (priorMtime === 0) {
|
|
224
235
|
// No había lock antes → eliminar
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
42
|
const fs = require('fs');
|
|
43
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
43
44
|
const path = require('path');
|
|
44
45
|
|
|
45
46
|
// Escritura atómica obligatoria para estado mutable (regla CLAUDE.md).
|
|
@@ -74,9 +75,13 @@ try {
|
|
|
74
75
|
|
|
75
76
|
let driftDetector;
|
|
76
77
|
try {
|
|
77
|
-
driftDetector = require('../scripts/lib/drift-detector');
|
|
78
|
+
driftDetector = require('../scripts/lib/drift-detector'); // repo madre
|
|
78
79
|
} catch {
|
|
79
|
-
|
|
80
|
+
try {
|
|
81
|
+
driftDetector = require('./drift-detector'); // destino: scripts/lib → hooks/lib (siblings)
|
|
82
|
+
} catch {
|
|
83
|
+
driftDetector = null;
|
|
84
|
+
}
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
let agentRouting;
|
|
@@ -105,8 +110,9 @@ const VENTANA_DIAS = 14;
|
|
|
105
110
|
const MIN_TOOL_CALLS = 5; // por debajo se considera trivial
|
|
106
111
|
const MIN_INVOCACIONES_LOOP = 6; // mínimo para que el ratio de convergencia sea significativo
|
|
107
112
|
|
|
108
|
-
const
|
|
109
|
-
const
|
|
113
|
+
const RAIZ = raizProyecto(process.cwd()); // una sola resolución por proceso
|
|
114
|
+
const DIR_AUTOEVOL = path.join(RAIZ, '.planning', 'auto-evolution');
|
|
115
|
+
const DIR_TRACES = path.join(RAIZ, '.planning', 'traces');
|
|
110
116
|
const LOG_PATH = path.join(DIR_AUTOEVOL, 'agentes.jsonl');
|
|
111
117
|
const NUDGES_PATH = path.join(DIR_AUTOEVOL, 'nudges.json');
|
|
112
118
|
|
|
@@ -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
|
|
|
25
26
|
let atomicWriteJSON;
|
|
@@ -36,7 +37,7 @@ try {
|
|
|
36
37
|
nudgeTracker = null;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const CWD = process.cwd();
|
|
40
|
+
const CWD = raizProyecto(process.cwd());
|
|
40
41
|
const DIR_EVOL = path.join(CWD, '.planning', 'evolution');
|
|
41
42
|
const METRICAS_PATH = path.join(DIR_EVOL, 'metricas.json');
|
|
42
43
|
const INSTINTOS_PROYECTO = path.join(CWD, 'instintos', 'proyecto.yaml');
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
const fs = require('fs');
|
|
31
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
32
|
+
const { conLockRetry } = require('./singleton-guard');
|
|
31
33
|
const path = require('path');
|
|
32
34
|
|
|
33
35
|
let atomicWriteJSON;
|
|
@@ -60,11 +62,14 @@ try {
|
|
|
60
62
|
// ---------------------------------------------------------------------------
|
|
61
63
|
|
|
62
64
|
const UMBRAL_NUDGE = parseInt(process.env.SWL_PERFIL_UMBRAL || '3', 10);
|
|
63
|
-
|
|
65
|
+
// Una sola resolución de la raíz por carga del módulo — las 4 rutas comparten
|
|
66
|
+
// ancla; resolverla 4 veces repetía el walk ascendente con su I/O (nemesis S2-3).
|
|
67
|
+
const RAIZ = raizProyecto(process.cwd());
|
|
68
|
+
const DIR_PERFIL = path.join(RAIZ, '.planning', 'user-profile');
|
|
64
69
|
const DIRTY_PATH = path.join(DIR_PERFIL, 'dirty.json');
|
|
65
|
-
const PERFIL_PATH = path.join(
|
|
66
|
-
const APRENDIZAJES_PATH = path.join(
|
|
67
|
-
const INSTINTOS_PATH = path.join(
|
|
70
|
+
const PERFIL_PATH = path.join(RAIZ, 'instintos', 'perfil-usuario.yaml');
|
|
71
|
+
const APRENDIZAJES_PATH = path.join(RAIZ, '.planning', 'APRENDIZAJES.md');
|
|
72
|
+
const INSTINTOS_PATH = path.join(RAIZ, 'instintos', 'proyecto.yaml');
|
|
68
73
|
|
|
69
74
|
// Keywords de corrección (señal fuerte: el usuario está corrigiendo)
|
|
70
75
|
const KEYWORDS_CORRECCION = [
|
|
@@ -237,6 +242,17 @@ function extraerPromptsUsuario(inputRaw) {
|
|
|
237
242
|
* @param {string} inputRaw transcripción de la sesión (payload del hook Stop)
|
|
238
243
|
*/
|
|
239
244
|
function ejecutar(inputRaw) {
|
|
245
|
+
// El cuerpo hace read-modify-write sobre dirty.json (leerDirty → mutar →
|
|
246
|
+
// escribirDirty). Dos hooks Stop casi-simultáneos podían pisar el contador
|
|
247
|
+
// (nemesis S2-2). Serializar con lock; bajo contención se OMITE — perder un
|
|
248
|
+
// incremento de señal es aceptable (la próxima sesión re-dispara).
|
|
249
|
+
conLockRetry('etapa-perfil-usuario', () => _ejecutarInterno(inputRaw), {
|
|
250
|
+
maxWaitMs: 500,
|
|
251
|
+
siNoAdquiere: 'omitir',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function _ejecutarInterno(inputRaw) {
|
|
240
256
|
try {
|
|
241
257
|
const textoUsuario = extraerPromptsUsuario(inputRaw);
|
|
242
258
|
|
|
@@ -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
|
|
|
22
23
|
// Escritura atómica obligatoria (regla CLAUDE.md). Fallback defensivo.
|
|
@@ -248,7 +249,7 @@ function _readSidecar(filePath) {
|
|
|
248
249
|
* @returns {boolean}
|
|
249
250
|
*/
|
|
250
251
|
function isPackageRoot(cwd) {
|
|
251
|
-
const dir = cwd || process.cwd();
|
|
252
|
+
const dir = cwd || raizProyecto(process.cwd());
|
|
252
253
|
try {
|
|
253
254
|
const pkgPath = path.join(dir, 'package.json');
|
|
254
255
|
if (!fs.existsSync(pkgPath)) return false;
|
|
@@ -1,179 +1,193 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Gateway Notify — Helper para encolar notificaciones al gateway multi-plataforma.
|
|
5
|
-
*
|
|
6
|
-
* Escribe un archivo JSON en .planning/comms/ que el GatewayRunner consume
|
|
7
|
-
* mediante polling (ver gateway/index.js _pollComms). Zero-deps, escritura
|
|
8
|
-
* atómica, opt-in por configuración en manifiestos/gateway-config.json.
|
|
9
|
-
*
|
|
10
|
-
* Uso:
|
|
11
|
-
* const { notificarGateway } = require('./lib/gateway-notify');
|
|
12
|
-
* notificarGateway({ tipo: 'session-stop', titulo: 'Sesión terminada', texto: '...' });
|
|
13
|
-
*
|
|
14
|
-
* No bloquea ni lanza: si el gateway está deshabilitado o no hay comms dir,
|
|
15
|
-
* retorna silenciosamente.
|
|
16
|
-
*
|
|
17
|
-
* @module hooks/lib/gateway-notify
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const fs = require('fs');
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return true;
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gateway Notify — Helper para encolar notificaciones al gateway multi-plataforma.
|
|
5
|
+
*
|
|
6
|
+
* Escribe un archivo JSON en .planning/comms/ que el GatewayRunner consume
|
|
7
|
+
* mediante polling (ver gateway/index.js _pollComms). Zero-deps, escritura
|
|
8
|
+
* atómica, opt-in por configuración en manifiestos/gateway-config.json.
|
|
9
|
+
*
|
|
10
|
+
* Uso:
|
|
11
|
+
* const { notificarGateway } = require('./lib/gateway-notify');
|
|
12
|
+
* notificarGateway({ tipo: 'session-stop', titulo: 'Sesión terminada', texto: '...' });
|
|
13
|
+
*
|
|
14
|
+
* No bloquea ni lanza: si el gateway está deshabilitado o no hay comms dir,
|
|
15
|
+
* retorna silenciosamente.
|
|
16
|
+
*
|
|
17
|
+
* @module hooks/lib/gateway-notify
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { atomicWriteJSON } = require('./atomic-write');
|
|
24
|
+
|
|
25
|
+
// Deep links opt-in (ADR-0029). Si el módulo no carga (instalación incompleta),
|
|
26
|
+
// el enriquecimiento se omite silenciosamente — comportamiento default sin
|
|
27
|
+
// cambios respecto a versiones previas.
|
|
28
|
+
let construirDeepLink, formatearEnlace;
|
|
29
|
+
try {
|
|
30
|
+
({ construirDeepLink, formatearEnlace } = require('./deep-links'));
|
|
31
|
+
} catch {
|
|
32
|
+
construirDeepLink = () => null;
|
|
33
|
+
formatearEnlace = (_, etiqueta) => etiqueta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const COMMS_DIR = '.planning/comms';
|
|
37
|
+
const CONFIG_PATH = 'manifiestos/gateway-config.json';
|
|
38
|
+
|
|
39
|
+
// Memo de la raíz del proyecto POR cwd: una notificación consulta config +
|
|
40
|
+
// comms en la misma invocación — sin memo el walk ascendente de raizProyecto
|
|
41
|
+
// se repetiría 3 veces con su I/O de existsSync. La clave por cwd evita
|
|
42
|
+
// congelar la raíz cuando el proceso cambia de directorio (tests, daemons).
|
|
43
|
+
let _raizCache = null;
|
|
44
|
+
function raizBase() {
|
|
45
|
+
const cwd = process.cwd();
|
|
46
|
+
if (!_raizCache || _raizCache.cwd !== cwd) {
|
|
47
|
+
_raizCache = { cwd, raiz: raizProyecto(cwd) };
|
|
48
|
+
}
|
|
49
|
+
return _raizCache.raiz;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Receptores reconocidos para formato de deep link. */
|
|
53
|
+
const RECEPTORES = new Set(['telegram', 'discord', 'slack', 'email', 'plain']);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Verifica si el gateway está habilitado en configuración.
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
function gatewayHabilitado() {
|
|
60
|
+
try {
|
|
61
|
+
const cfgPath = path.join(raizBase(), CONFIG_PATH);
|
|
62
|
+
if (!fs.existsSync(cfgPath)) return false;
|
|
63
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
64
|
+
return cfg && cfg.enabled === true;
|
|
65
|
+
} catch (_) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Verifica si un tipo de notificación está habilitado.
|
|
72
|
+
* Lee el mapa notifications de gateway-config.json.
|
|
73
|
+
* @param {string} tipo - session-stop, checkpoint, error, release, build-fail
|
|
74
|
+
* @returns {boolean}
|
|
75
|
+
*/
|
|
76
|
+
function tipoHabilitado(tipo) {
|
|
77
|
+
try {
|
|
78
|
+
const cfgPath = path.join(raizBase(), CONFIG_PATH);
|
|
79
|
+
if (!fs.existsSync(cfgPath)) return true;
|
|
80
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
81
|
+
const map = cfg.notifications || {};
|
|
82
|
+
const claves = {
|
|
83
|
+
'session-stop': 'onSessionComplete',
|
|
84
|
+
'checkpoint': 'onCheckpoint',
|
|
85
|
+
'error': 'onError',
|
|
86
|
+
'release': 'onRelease',
|
|
87
|
+
'build-fail': 'onBuildFail',
|
|
88
|
+
};
|
|
89
|
+
const clave = claves[tipo];
|
|
90
|
+
if (!clave) return true;
|
|
91
|
+
return map[clave] !== false;
|
|
92
|
+
} catch (_) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Si el payload incluye `fileRef` + `idePreferido`, retorna el enlace
|
|
99
|
+
* formateado al receptor; en otro caso retorna null. No modifica el payload.
|
|
100
|
+
*
|
|
101
|
+
* Estructura esperada del payload (opt-in, ADR-0029):
|
|
102
|
+
* payload.fileRef = {
|
|
103
|
+
* archivo: '/abs/path/al/archivo.js',
|
|
104
|
+
* linea?: 42,
|
|
105
|
+
* columna?: 8,
|
|
106
|
+
* etiqueta?: 'Abrir archivo.js:42', // default: archivo:linea
|
|
107
|
+
* proyecto?: 'mi-proyecto', // requerido solo para JetBrains
|
|
108
|
+
* }
|
|
109
|
+
* payload.idePreferido = 'vscode' | 'cursor' | ...
|
|
110
|
+
*
|
|
111
|
+
* @param {object} payload
|
|
112
|
+
* @param {string} receptor - 'telegram' | 'discord' | 'slack' | 'email' | 'plain'
|
|
113
|
+
* @returns {string|null}
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
function _construirEnlaceFileRef(payload, receptor) {
|
|
117
|
+
if (!payload || !payload.fileRef || !payload.idePreferido) return null;
|
|
118
|
+
const { archivo, linea, columna, etiqueta, proyecto } = payload.fileRef;
|
|
119
|
+
if (!archivo) return null;
|
|
120
|
+
|
|
121
|
+
const url = construirDeepLink({
|
|
122
|
+
ide: payload.idePreferido,
|
|
123
|
+
rutaAbsoluta: archivo,
|
|
124
|
+
linea,
|
|
125
|
+
columna,
|
|
126
|
+
proyecto,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const label = etiqueta || (linea ? `${path.basename(archivo)}:${linea}` : path.basename(archivo));
|
|
130
|
+
const formato = RECEPTORES.has(receptor) ? receptor : 'plain';
|
|
131
|
+
return formatearEnlace(url, label, formato);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Encola una notificación para el gateway.
|
|
136
|
+
* No bloquea ni lanza. Retorna true si se encoló, false si fue descartada.
|
|
137
|
+
*
|
|
138
|
+
* Enriquecimiento opt-in (ADR-0029): si `params.payload.fileRef` +
|
|
139
|
+
* `params.payload.idePreferido` están presentes, el mensaje agrega el campo
|
|
140
|
+
* `enlace` con un deep link formateado al receptor. Sin esos campos, el
|
|
141
|
+
* comportamiento es idéntico al previo (compatibilidad hacia atrás).
|
|
142
|
+
*
|
|
143
|
+
* @param {object} params
|
|
144
|
+
* @param {string} params.tipo - session-stop, checkpoint, error, release, build-fail, custom
|
|
145
|
+
* @param {string} [params.titulo] - Título corto.
|
|
146
|
+
* @param {string} [params.texto] - Texto del mensaje (soporta Markdown).
|
|
147
|
+
* @param {string} [params.to='all'] - Destino: 'all', 'telegram', 'discord', etc.
|
|
148
|
+
* @param {object} [params.payload] - Payload adicional para el adaptador.
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
function notificarGateway(params) {
|
|
152
|
+
try {
|
|
153
|
+
if (!gatewayHabilitado()) return false;
|
|
154
|
+
if (!tipoHabilitado(params.tipo)) return false;
|
|
155
|
+
|
|
156
|
+
const commsDir = path.join(raizBase(), COMMS_DIR);
|
|
157
|
+
if (!fs.existsSync(commsDir)) {
|
|
158
|
+
fs.mkdirSync(commsDir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const id = `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
162
|
+
const receptor = params.to || 'all';
|
|
163
|
+
const payloadBase = {
|
|
164
|
+
tipo: params.tipo,
|
|
165
|
+
titulo: params.titulo || '',
|
|
166
|
+
texto: params.texto || '',
|
|
167
|
+
...(params.payload || {}),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Enriquecimiento opt-in con deep link (ADR-0029).
|
|
171
|
+
const enlace = _construirEnlaceFileRef(payloadBase, receptor);
|
|
172
|
+
if (enlace) payloadBase.enlace = enlace;
|
|
173
|
+
|
|
174
|
+
const msg = {
|
|
175
|
+
id,
|
|
176
|
+
type: 'gateway_notification',
|
|
177
|
+
from: 'swl-system',
|
|
178
|
+
to: receptor,
|
|
179
|
+
payload: payloadBase,
|
|
180
|
+
text: params.texto || '',
|
|
181
|
+
timestamp: new Date().toISOString(),
|
|
182
|
+
status: 'pending',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const filePath = path.join(commsDir, `${id}.json`);
|
|
186
|
+
atomicWriteJSON(filePath, msg);
|
|
187
|
+
return true;
|
|
188
|
+
} catch (_) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { notificarGateway, gatewayHabilitado, tipoHabilitado };
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
const fs = require('fs');
|
|
32
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
32
33
|
const path = require('path');
|
|
33
34
|
const { atomicWriteSync, atomicWriteJSON } = require('./atomic-write');
|
|
34
35
|
|
|
@@ -60,11 +61,11 @@ function timestampCompacto(fecha) {
|
|
|
60
61
|
* @param {string} opciones.tipo - Identificador del loop (ej: 'autoresearch', 'nemesis', 'verificar').
|
|
61
62
|
* @param {string} [opciones.direccion='higher_is_better'] - Dirección de la métrica.
|
|
62
63
|
* @param {string[]} [opciones.columnas] - Columnas del TSV (default COLUMNAS_DEFAULT).
|
|
63
|
-
* @param {string} [opciones.raiz=process.cwd()] - Raíz del proyecto.
|
|
64
|
+
* @param {string} [opciones.raiz=raizProyecto(process.cwd())] - Raíz del proyecto.
|
|
64
65
|
* @param {object} [opciones.config={}] - Config inicial persistida en meta.json.
|
|
65
66
|
* @returns {{dir: string, tsvPath: string}}
|
|
66
67
|
*/
|
|
67
|
-
function iniciarCorrida({ tipo, direccion = 'higher_is_better', columnas, raiz = process.cwd(), config = {} }) {
|
|
68
|
+
function iniciarCorrida({ tipo, direccion = 'higher_is_better', columnas, raiz = raizProyecto(process.cwd()), config = {} }) {
|
|
68
69
|
if (!tipo || !/^[a-z0-9-]+$/.test(tipo)) {
|
|
69
70
|
throw new TypeError(`loop-telemetry: tipo inválido "${tipo}" — usar kebab-case (ej: "autoresearch")`);
|
|
70
71
|
}
|
|
@@ -269,12 +270,12 @@ function leerHandoff(dir) {
|
|
|
269
270
|
* Encuentra la corrida activa más reciente: directorio en .planning/loops/
|
|
270
271
|
* cuyo iteraciones.tsv fue modificado hace menos de maxEdadMin minutos.
|
|
271
272
|
*
|
|
272
|
-
* @param {string} [raiz=process.cwd()]
|
|
273
|
+
* @param {string} [raiz=raizProyecto(process.cwd())]
|
|
273
274
|
* @param {object} [opciones]
|
|
274
275
|
* @param {number} [opciones.maxEdadMin=30]
|
|
275
276
|
* @returns {{dir: string, tipo: string, ultimasFilas: object[], totalFilas: number}|null}
|
|
276
277
|
*/
|
|
277
|
-
function corridaActiva(raiz = process.cwd(), { maxEdadMin = 30 } = {}) {
|
|
278
|
+
function corridaActiva(raiz = raizProyecto(process.cwd()), { maxEdadMin = 30 } = {}) {
|
|
278
279
|
const base = path.join(raiz, DIR_LOOPS);
|
|
279
280
|
let candidatos;
|
|
280
281
|
try {
|
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
|
|