@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
|
@@ -31,6 +31,8 @@ const path = require('path');
|
|
|
31
31
|
const os = require('os');
|
|
32
32
|
|
|
33
33
|
const { atomicWriteSync } = require('./lib/atomic-write');
|
|
34
|
+
const { raizProyecto } = require('./lib/raiz-proyecto');
|
|
35
|
+
const { conLockRetry } = require('./lib/singleton-guard');
|
|
34
36
|
|
|
35
37
|
// ---------------------------------------------------------------------------
|
|
36
38
|
// Constantes de configuración
|
|
@@ -38,9 +40,13 @@ const { atomicWriteSync } = require('./lib/atomic-write');
|
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Ruta del archivo donde se acumulan los aprendizajes.
|
|
41
|
-
* Se resuelve
|
|
43
|
+
* Se re-resuelve en el entrypoint con la RAÍZ DEL PROYECTO (raizProyecto:
|
|
44
|
+
* primer .planning/ real ascendiendo, o raíz del repo git, o fallback cwd) —
|
|
45
|
+
* NUNCA con el CWD crudo de la sesión: en monorepos con sesión abierta en un
|
|
46
|
+
* subdirectorio eso fragmentaba la memoria en árboles .planning/ huérfanos
|
|
47
|
+
* (DT-HOOKS-RAIZ-GIT, reportes de campo sigm + oic 2026-07-10).
|
|
42
48
|
*/
|
|
43
|
-
|
|
49
|
+
let RUTA_APRENDIZAJES = path.join(raizProyecto(process.cwd()), '.planning', 'APRENDIZAJES.md');
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
52
|
* Número de líneas de contexto a extraer alrededor del keyword detectado.
|
|
@@ -364,9 +370,15 @@ function extraerTextoYAgente(data) {
|
|
|
364
370
|
* 4. Clasificar por el tipo de keyword que disparó la detección.
|
|
365
371
|
*
|
|
366
372
|
* @param {string} texto
|
|
373
|
+
* @param {object} [opts]
|
|
374
|
+
* @param {boolean} [opts.descartarFragmentos=false] - Descarta candidatos cuya
|
|
375
|
+
* descripción quedó TRUNCADA a media frase (termina en '…' sin oración
|
|
376
|
+
* completa). Obligatorio para fuentes Write/Edit: los comentarios de código
|
|
377
|
+
* de dominio largos producían entradas cortadas a medias que no son
|
|
378
|
+
* aprendizajes (reporte de campo sigm 2026-07-10).
|
|
367
379
|
* @returns {Aprendizaje|null}
|
|
368
380
|
*/
|
|
369
|
-
function detectarAprendizaje(texto) {
|
|
381
|
+
function detectarAprendizaje(texto, opts = {}) {
|
|
370
382
|
if (!texto || texto.trim().length < MIN_CHARS_DESCRIPCION) return null;
|
|
371
383
|
|
|
372
384
|
const lineas = texto.split('\n');
|
|
@@ -395,12 +407,28 @@ function detectarAprendizaje(texto) {
|
|
|
395
407
|
// Descripción: la línea del keyword, limpiada de símbolos de código.
|
|
396
408
|
// Preferir cortar al final de la primera oración si cabe en 120 chars;
|
|
397
409
|
// si no, al último espacio antes del límite.
|
|
398
|
-
const
|
|
410
|
+
const limpiarLinea = l => l
|
|
399
411
|
.trim()
|
|
400
412
|
.replace(/^[#*\/\-–•]+\s*/, '') // Quitar prefijos de comentario/lista
|
|
401
413
|
.replace(/\*\*/g, '') // Quitar marcadores de negrita Markdown
|
|
402
414
|
.replace(/`+/g, ''); // Quitar backticks
|
|
403
415
|
|
|
416
|
+
let lineaLimpia = limpiarLinea(lineas[i]);
|
|
417
|
+
|
|
418
|
+
// Wrap del editor ≠ fin del pensamiento: si la línea del keyword no
|
|
419
|
+
// cierra oración, unir hasta 2 líneas siguientes antes de buscar el
|
|
420
|
+
// corte — los comentarios largos envuelven la frase y cortar en el
|
|
421
|
+
// wrap producía títulos a media frase (ticket de campo sigm
|
|
422
|
+
// 2026-07-10: "...pueden cerrar la 'corrección' apuntando a").
|
|
423
|
+
if (!/[.!?]/.test(lineaLimpia)) {
|
|
424
|
+
for (let j = i + 1; j < Math.min(i + 3, lineas.length); j++) {
|
|
425
|
+
const continuacion = limpiarLinea(lineas[j]);
|
|
426
|
+
if (!continuacion) break;
|
|
427
|
+
lineaLimpia += ' ' + continuacion;
|
|
428
|
+
if (/[.!?]/.test(continuacion)) break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
404
432
|
// Si la línea tiene un punto temprano, usarlo como fin natural
|
|
405
433
|
const matchOracion = lineaLimpia.match(/^[^.!?]*[.!?]/);
|
|
406
434
|
const descripcionBase = matchOracion
|
|
@@ -411,6 +439,12 @@ function detectarAprendizaje(texto) {
|
|
|
411
439
|
|
|
412
440
|
if (descripcion.length < 10) continue;
|
|
413
441
|
|
|
442
|
+
// Fragmento a media frase: ni la línea ni sus continuaciones cierran
|
|
443
|
+
// oración Y hubo que truncar — para fuentes de escritura de código
|
|
444
|
+
// eso es ruido. Una oración completa truncada elegante (corte en
|
|
445
|
+
// frontera de cláusula con '…') SÍ es título legítimo.
|
|
446
|
+
if (opts.descartarFragmentos && !matchOracion && descripcion.endsWith('…')) continue;
|
|
447
|
+
|
|
414
448
|
return { tipo, contexto: contextoFinal, descripcion };
|
|
415
449
|
}
|
|
416
450
|
}
|
|
@@ -540,6 +574,27 @@ function formatearEntrada(fecha, tipo, descripcion, agente, contexto) {
|
|
|
540
574
|
* @param {string} agente
|
|
541
575
|
*/
|
|
542
576
|
function escribirAprendizaje(aprendizaje, agente) {
|
|
577
|
+
// Read-modify-write sobre APRENDIZAJES.md (leer todo → dedup → reescribir
|
|
578
|
+
// todo). Dos hooks concurrentes leían la misma base y el segundo write
|
|
579
|
+
// pisaba la entrada del primero → aprendizaje perdido (nemesis S2-2).
|
|
580
|
+
// El lock O_EXCL serializa el CASO COMÚN (secciones críticas de <<1s): dos
|
|
581
|
+
// hooks casi-simultáneos se ordenan, sin pérdida. La lectura ocurre DENTRO de
|
|
582
|
+
// la sección crítica para ver base fresca.
|
|
583
|
+
//
|
|
584
|
+
// Límite honesto (re-auditoría nemesis iter-2): siNoAdquiere:'ejecutar'
|
|
585
|
+
// significa que si tras maxWaitMs NO se adquiere el lock —solo posible con un
|
|
586
|
+
// holder VIVO ocupando el lock >1s, patológico para un archivo de KB (un
|
|
587
|
+
// holder muerto se reclama al instante vía pidVivo)— se corre igual SIN lock.
|
|
588
|
+
// En ese caso extremo el rewrite-whole-file PUEDE pisar la entrada de otro
|
|
589
|
+
// proceso. Se prefiere ese riesgo casi-inalcanzable a OMITIR y descartar el
|
|
590
|
+
// aprendizaje actual con certeza.
|
|
591
|
+
conLockRetry('extraccion-aprendizajes-md', () => _escribirAprendizajeInterno(aprendizaje, agente), {
|
|
592
|
+
maxWaitMs: 1000,
|
|
593
|
+
siNoAdquiere: 'ejecutar',
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function _escribirAprendizajeInterno(aprendizaje, agente) {
|
|
543
598
|
asegurarDirectorio();
|
|
544
599
|
|
|
545
600
|
const { contenidoExistente, descripciones } = leerAprendizajesExistentes();
|
|
@@ -676,6 +731,11 @@ const PATRONES_ARCHIVO_SWL_EXCLUIDO = [
|
|
|
676
731
|
// "rules" (inglés). Detectado 2026-06-11: fragmentos de una edición a
|
|
677
732
|
// verificar-citas-normativas.md (global) se promovieron como "patrón".
|
|
678
733
|
/(?:^|[\\/])\.claude[\\/]rules[\\/]/,
|
|
734
|
+
// Workflows y config de CI (.github/): sus comentarios YAML describen el
|
|
735
|
+
// proceso con keywords narrativas ("decisión", "aprobación") que el hook
|
|
736
|
+
// confunde con descubrimientos. Detectado 2026-07-10: el comentario del
|
|
737
|
+
// environment de publish-npm.yml se promovió como entrada "decisión".
|
|
738
|
+
/(?:^|[\\/])\.github[\\/]/,
|
|
679
739
|
];
|
|
680
740
|
|
|
681
741
|
/**
|
|
@@ -715,6 +775,16 @@ function pareceContenidoEstructuralSwl(contexto) {
|
|
|
715
775
|
return true;
|
|
716
776
|
}
|
|
717
777
|
|
|
778
|
+
// Bold-labels de docs sin cuerpo: líneas que son SOLO una etiqueta en
|
|
779
|
+
// negritas ("**Uso:**", "**¿Cuándo usarlo?**"), con o sin valor corto de
|
|
780
|
+
// código a continuación. Junto con los headings (abajo) cubren fragmentos
|
|
781
|
+
// estructurales de manuales/skills que el hook confundía con aprendizajes
|
|
782
|
+
// (detectado 2026-07-10: headings y labels de MANUAL_USO promovidos como
|
|
783
|
+
// "gotcha"/"patrón").
|
|
784
|
+
const esLineaEstructural = l =>
|
|
785
|
+
/^(>\s*)?#{1,6}\s+\S/.test(l) ||
|
|
786
|
+
/^(>\s*)?\*\*[^*]{1,60}\*\*:?\s*(`[^`]*`\s*)?$/.test(l);
|
|
787
|
+
|
|
718
788
|
// Enumeración de encabezados sin cuerpo sustantivo
|
|
719
789
|
// (3+ líneas tipo "## [fecha] tipo — descripción" juntas)
|
|
720
790
|
const encabezadosFecha = lineas.filter(l => /^##\s+\[\d{4}-\d{2}-\d{2}\]/.test(l));
|
|
@@ -722,15 +792,15 @@ function pareceContenidoEstructuralSwl(contexto) {
|
|
|
722
792
|
return true;
|
|
723
793
|
}
|
|
724
794
|
|
|
725
|
-
// Contexto compuesto mayoritariamente de headings sin cuerpo.
|
|
726
|
-
// placeholders auto-generados al hacer Write de archivos meta
|
|
727
|
-
// RESUMEN, PLAN)
|
|
728
|
-
//
|
|
729
|
-
// como
|
|
730
|
-
// el contexto con `> ` antes de evaluar
|
|
795
|
+
// Contexto compuesto mayoritariamente de headings/bold-labels sin cuerpo.
|
|
796
|
+
// Detecta placeholders auto-generados al hacer Write de archivos meta
|
|
797
|
+
// (RELEASE-NOTES, RESUMEN, PLAN) y fragmentos de docs de comandos cuya
|
|
798
|
+
// estructura es solo `## Sección`, `**Uso:**`... sin párrafos sustantivos.
|
|
799
|
+
// Acepta tanto líneas desnudas como indentadas como blockquote (`> ## X`)
|
|
800
|
+
// porque el hook envuelve el contexto con `> ` antes de evaluar.
|
|
731
801
|
if (noVacias.length >= 3) {
|
|
732
|
-
const
|
|
733
|
-
if (
|
|
802
|
+
const estructurales = noVacias.filter(esLineaEstructural);
|
|
803
|
+
if (estructurales.length / noVacias.length > 0.6) {
|
|
734
804
|
return true;
|
|
735
805
|
}
|
|
736
806
|
}
|
|
@@ -781,15 +851,36 @@ process.stdin.on('end', () => {
|
|
|
781
851
|
return;
|
|
782
852
|
}
|
|
783
853
|
|
|
854
|
+
// Anclar la memoria a la RAÍZ DEL PROYECTO, no al CWD de la sesión
|
|
855
|
+
// (DT-HOOKS-RAIZ-GIT): una sesión abierta en backend/ de un monorepo
|
|
856
|
+
// escribía backend/.planning/APRENDIZAJES.md huérfano.
|
|
857
|
+
const baseCwd = String(data.cwd || process.cwd());
|
|
858
|
+
const raiz = raizProyecto(baseCwd);
|
|
859
|
+
RUTA_APRENDIZAJES = path.join(raiz, '.planning', 'APRENDIZAJES.md');
|
|
860
|
+
|
|
861
|
+
const esFuenteEscritura =
|
|
862
|
+
toolName === 'Write' || toolName === 'Edit' || toolName === 'MultiEdit';
|
|
863
|
+
|
|
784
864
|
// Filtrar Write/Edit sobre archivos del sistema SWL (auto-referencia).
|
|
785
865
|
// Editar habilidades/, agentes/, reglas/, CHANGELOG, CLAUDE.md, etc. es
|
|
786
866
|
// mantenimiento del sistema, no descubrimiento en tiempo de ejecución.
|
|
787
|
-
if (
|
|
867
|
+
if (esFuenteEscritura) {
|
|
788
868
|
const toolInput = data.tool_input || data.tool?.input || {};
|
|
789
869
|
const filePath = String(toolInput.file_path || toolInput.path || '');
|
|
790
870
|
if (esArchivoSwlExcluido(filePath)) {
|
|
791
871
|
return;
|
|
792
872
|
}
|
|
873
|
+
// Escrituras FUERA de la raíz del proyecto (scratchpad del harness,
|
|
874
|
+
// temp, otros repos) no son descubrimiento de ESTE proyecto — su
|
|
875
|
+
// contenido meta (borradores, docs en construcción) generaba entradas
|
|
876
|
+
// espurias (detectado 2026-07-10 con archivos de trabajo en scratchpad).
|
|
877
|
+
if (filePath) {
|
|
878
|
+
const abs = path.resolve(baseCwd, filePath);
|
|
879
|
+
const rel = path.relative(raiz, abs);
|
|
880
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
793
884
|
}
|
|
794
885
|
|
|
795
886
|
// Extraer texto y nombre del agente del payload
|
|
@@ -800,8 +891,11 @@ process.stdin.on('end', () => {
|
|
|
800
891
|
return;
|
|
801
892
|
}
|
|
802
893
|
|
|
803
|
-
// Detectar aprendizaje en el texto
|
|
804
|
-
|
|
894
|
+
// Detectar aprendizaje en el texto. Las fuentes de escritura de código
|
|
895
|
+
// exigen descripción con oración completa (sin fragmentos truncados).
|
|
896
|
+
const aprendizaje = detectarAprendizaje(texto, {
|
|
897
|
+
descartarFragmentos: esFuenteEscritura,
|
|
898
|
+
});
|
|
805
899
|
|
|
806
900
|
if (!aprendizaje) {
|
|
807
901
|
// Sin aprendizaje detectado — salir silenciosamente
|
package/hooks/grafo-contexto.js
CHANGED
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
const fs = require('fs');
|
|
22
|
+
const { raizProyecto } = require('./lib/raiz-proyecto');
|
|
22
23
|
const path = require('path');
|
|
23
24
|
|
|
24
|
-
const CWD = process.env.PWD || process.cwd();
|
|
25
|
+
const CWD = raizProyecto(process.env.PWD || process.cwd());
|
|
25
26
|
const REPORT_FILE = path.join(CWD, '.planning', 'REPORTE-GRAFO.md');
|
|
26
27
|
const GRAPH_FILE = path.join(CWD, '.planning', 'graph.json');
|
|
27
28
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
const fs = require('fs');
|
|
26
|
+
const { raizProyecto } = require('./lib/raiz-proyecto');
|
|
26
27
|
const path = require('path');
|
|
27
28
|
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
@@ -42,7 +43,7 @@ if (ES_ENTRYPOINT && !process.env.SWL_GUARDRAIL_MODELO) {
|
|
|
42
43
|
// Rutas
|
|
43
44
|
// ---------------------------------------------------------------------------
|
|
44
45
|
|
|
45
|
-
const CWD = process.cwd();
|
|
46
|
+
const CWD = raizProyecto(process.cwd());
|
|
46
47
|
const DIR_EVOL = path.join(CWD, '.planning', 'evolution');
|
|
47
48
|
const GUARDRAIL_LOG = path.join(DIR_EVOL, 'guardrail-observaciones.jsonl');
|
|
48
49
|
|
package/hooks/inbox-aviso.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
|
+
const { raizProyecto } = require('./lib/raiz-proyecto');
|
|
19
20
|
const path = require('path');
|
|
20
21
|
|
|
21
22
|
const INBOX_DIR = '.planning/inbox';
|
|
@@ -51,7 +52,7 @@ process.stdin.on('end', () => {
|
|
|
51
52
|
try {
|
|
52
53
|
if (process.env.SWL_INBOX_AVISO === '0') return;
|
|
53
54
|
|
|
54
|
-
const { total, preview } = contarPendientes(process.cwd());
|
|
55
|
+
const { total, preview } = contarPendientes(raizProyecto(process.cwd()));
|
|
55
56
|
if (total === 0) return;
|
|
56
57
|
|
|
57
58
|
const lineas = [];
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
const fs = require('fs');
|
|
26
|
+
const { raizProyecto } = require('./lib/raiz-proyecto');
|
|
26
27
|
const path = require('path');
|
|
27
28
|
const os = require('os');
|
|
28
29
|
|
|
@@ -205,7 +206,7 @@ process.stdin.on('end', () => {
|
|
|
205
206
|
return;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
const cwd = process.cwd();
|
|
209
|
+
const cwd = raizProyecto(process.cwd());
|
|
209
210
|
const sesion = leerSesionAnterior(cwd);
|
|
210
211
|
|
|
211
212
|
// Solo inyectar si la sesion anterior tiene contenido util
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const fs = require('fs');
|
|
19
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
19
20
|
const path = require('path');
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
@@ -123,7 +124,7 @@ function loadAgents(agentesDir) {
|
|
|
123
124
|
* @returns {Array<{name: string, score: number, skills: string[]}>} Ordenado por score descendente.
|
|
124
125
|
*/
|
|
125
126
|
function matchAgents(taskText, agentesDir) {
|
|
126
|
-
const dir = agentesDir || path.join(process.cwd(), 'agentes');
|
|
127
|
+
const dir = agentesDir || path.join(raizProyecto(process.cwd()), 'agentes');
|
|
127
128
|
const agents = loadAgents(dir);
|
|
128
129
|
const taskKeywords = extractKeywords(taskText);
|
|
129
130
|
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
const fs = require('fs');
|
|
28
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
28
29
|
const path = require('path');
|
|
29
30
|
|
|
30
31
|
const _cache = Object.create(null);
|
|
@@ -38,7 +39,7 @@ const _cache = Object.create(null);
|
|
|
38
39
|
* @returns {string|null} - Ruta absoluta o null si no existe
|
|
39
40
|
*/
|
|
40
41
|
function resolveAgentPath(agentName, cwd) {
|
|
41
|
-
const baseDir = cwd || process.cwd();
|
|
42
|
+
const baseDir = cwd || raizProyecto(process.cwd());
|
|
42
43
|
const candidates = [
|
|
43
44
|
path.join(baseDir, 'agentes', `${agentName}.md`),
|
|
44
45
|
path.join(baseDir, '.claude', 'agents', `${agentName}.md`),
|
package/hooks/lib/autonomia.js
CHANGED
|
@@ -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
|
const { execFileSync } = require('child_process');
|
|
27
28
|
|
|
@@ -89,7 +90,7 @@ function _normalizarNivel(valor) {
|
|
|
89
90
|
* @returns {{version?:string, defaults?:string, clases:Object}}
|
|
90
91
|
*/
|
|
91
92
|
function cargarDial(baseDir) {
|
|
92
|
-
const ruta = path.join(baseDir || process.cwd(), ...YAML_PATH);
|
|
93
|
+
const ruta = path.join(baseDir || raizProyecto(process.cwd()), ...YAML_PATH);
|
|
93
94
|
let parsed = {};
|
|
94
95
|
try {
|
|
95
96
|
parsed = _parsearYamlPlano(fs.readFileSync(ruta, 'utf8'));
|
|
@@ -153,7 +154,7 @@ function _archivosModificados(baseDir) {
|
|
|
153
154
|
const MAX_ACCION_LEN = 512; // tope para que el JSONL no crezca sin límite
|
|
154
155
|
|
|
155
156
|
function autoCheckpoint(baseDir, accion, tsISO) {
|
|
156
|
-
const base = baseDir || process.cwd();
|
|
157
|
+
const base = baseDir || raizProyecto(process.cwd());
|
|
157
158
|
const registro = {
|
|
158
159
|
ts: tsISO || new Date().toISOString(),
|
|
159
160
|
accion: String(accion || '').slice(0, MAX_ACCION_LEN),
|
|
@@ -186,7 +187,7 @@ function _parseAccion(args) {
|
|
|
186
187
|
|
|
187
188
|
function main(argv) {
|
|
188
189
|
const accion = _parseAccion(argv.slice(2));
|
|
189
|
-
const baseDir = process.cwd();
|
|
190
|
+
const baseDir = raizProyecto(process.cwd());
|
|
190
191
|
const dial = cargarDial(baseDir);
|
|
191
192
|
if (requiereAutoCheckpoint(dial, 'cambio_reversible')) {
|
|
192
193
|
const reg = autoCheckpoint(baseDir, accion || '(sin descripción)');
|
|
@@ -204,5 +205,6 @@ module.exports = {
|
|
|
204
205
|
cargarDial,
|
|
205
206
|
requiereAutoCheckpoint,
|
|
206
207
|
autoCheckpoint,
|
|
208
|
+
main,
|
|
207
209
|
_parsearYamlPlano,
|
|
208
210
|
};
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
const fs = require('fs');
|
|
30
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
30
31
|
const path = require('path');
|
|
31
32
|
const crypto = require('crypto');
|
|
32
33
|
|
|
@@ -384,7 +385,7 @@ module.exports = {
|
|
|
384
385
|
if (require.main === module) {
|
|
385
386
|
try {
|
|
386
387
|
if (process.argv.includes('--conteo')) {
|
|
387
|
-
process.stdout.write(JSON.stringify(contarPorSubtipo(process.cwd()), null, 2) + '\n');
|
|
388
|
+
process.stdout.write(JSON.stringify(contarPorSubtipo(raizProyecto(process.cwd())), null, 2) + '\n');
|
|
388
389
|
}
|
|
389
390
|
} catch (_) {
|
|
390
391
|
/* exit 0 siempre */
|
|
@@ -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;
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const fs = require('fs');
|
|
21
|
+
const { raizProyecto } = require('./raiz-proyecto');
|
|
21
22
|
const path = require('path');
|
|
22
23
|
const { atomicWriteJSON } = require('./atomic-write');
|
|
23
24
|
|
|
@@ -35,6 +36,19 @@ try {
|
|
|
35
36
|
const COMMS_DIR = '.planning/comms';
|
|
36
37
|
const CONFIG_PATH = 'manifiestos/gateway-config.json';
|
|
37
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
|
+
|
|
38
52
|
/** Receptores reconocidos para formato de deep link. */
|
|
39
53
|
const RECEPTORES = new Set(['telegram', 'discord', 'slack', 'email', 'plain']);
|
|
40
54
|
|
|
@@ -44,7 +58,7 @@ const RECEPTORES = new Set(['telegram', 'discord', 'slack', 'email', 'plain']);
|
|
|
44
58
|
*/
|
|
45
59
|
function gatewayHabilitado() {
|
|
46
60
|
try {
|
|
47
|
-
const cfgPath = path.join(
|
|
61
|
+
const cfgPath = path.join(raizBase(), CONFIG_PATH);
|
|
48
62
|
if (!fs.existsSync(cfgPath)) return false;
|
|
49
63
|
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
50
64
|
return cfg && cfg.enabled === true;
|
|
@@ -61,7 +75,7 @@ function gatewayHabilitado() {
|
|
|
61
75
|
*/
|
|
62
76
|
function tipoHabilitado(tipo) {
|
|
63
77
|
try {
|
|
64
|
-
const cfgPath = path.join(
|
|
78
|
+
const cfgPath = path.join(raizBase(), CONFIG_PATH);
|
|
65
79
|
if (!fs.existsSync(cfgPath)) return true;
|
|
66
80
|
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
67
81
|
const map = cfg.notifications || {};
|
|
@@ -139,7 +153,7 @@ function notificarGateway(params) {
|
|
|
139
153
|
if (!gatewayHabilitado()) return false;
|
|
140
154
|
if (!tipoHabilitado(params.tipo)) return false;
|
|
141
155
|
|
|
142
|
-
const commsDir = path.join(
|
|
156
|
+
const commsDir = path.join(raizBase(), COMMS_DIR);
|
|
143
157
|
if (!fs.existsSync(commsDir)) {
|
|
144
158
|
fs.mkdirSync(commsDir, { recursive: true });
|
|
145
159
|
}
|
|
@@ -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 {
|