@saulwade/swl-ses 2.6.0 → 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 +197 -197
- package/README.md +600 -600
- 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 -932
- 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/comandos/swl/actualizar.md +174 -174
- package/comandos/swl/adoptar-proyecto.md +265 -265
- package/comandos/swl/aprender.md +836 -836
- 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/discutir-fase.md +169 -169
- package/comandos/swl/ejecutar-fase.md +233 -233
- package/comandos/swl/evaluar-skill.md +520 -520
- package/comandos/swl/evolucion-continua.md +73 -73
- package/comandos/swl/evolucionar.md +267 -267
- 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 +345 -345
- package/comandos/swl/verificar.md +817 -817
- package/comandos/swl/wiki.md +620 -620
- package/gateway/cron/jobs.example.json +12 -12
- package/habilidades/auto-evolucion-protocolo/SKILL.md +294 -294
- package/habilidades/backend-async-postgres-testing/SKILL.md +2 -1
- package/habilidades/changelog-generator/SKILL.md +174 -174
- package/habilidades/compactacion-contexto/SKILL.md +2 -1
- package/habilidades/contenedores-docker/SKILL.md +4 -2
- package/habilidades/doubt-driven-review/SKILL.md +207 -207
- package/habilidades/drift-detection/SKILL.md +1 -1
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- 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 +314 -314
- package/habilidades/instalar-sistema/SKILL.md +227 -227
- package/habilidades/planear-fase/SKILL.md +358 -358
- 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-ingenieria-requerimientos/SKILL.md +147 -147
- package/habilidades/release-semver/SKILL.md +2 -2
- package/habilidades/tdd-workflow/SKILL.md +749 -749
- package/hooks/agente-lifecycle.js +1 -1
- package/hooks/audit-trail.js +1 -1
- package/hooks/auto-consolidacion.js +1 -1
- package/hooks/captura-acciones-post.js +1 -1
- package/hooks/captura-acciones-session.js +1 -1
- package/hooks/captura-feedback-usuario.js +1 -1
- package/hooks/contexto-iteracion.js +1 -1
- package/hooks/contexto-subagente.js +68 -68
- package/hooks/degradacion-instintos.js +1 -1
- package/hooks/grafo-contexto.js +1 -1
- package/hooks/guardrail-modelo.js +1 -1
- package/hooks/inbox-aviso.js +1 -1
- package/hooks/inyeccion-contexto.js +1 -1
- package/hooks/lib/agent-matcher.js +1 -1
- package/hooks/lib/agent-routing.js +1 -1
- package/hooks/lib/captura-acciones.js +1 -1
- package/hooks/lib/etapa-metricas.js +1 -1
- package/hooks/lib/evolution-tracker.js +1 -1
- package/hooks/lib/gateway-notify.js +193 -193
- package/hooks/lib/mcp-health.js +1 -1
- package/hooks/lib/notificacion-formato.js +58 -0
- package/hooks/lib/nudge-tracker.js +1 -1
- package/hooks/lib/otlp-exporter.js +1 -1
- package/hooks/lib/propose-step.js +1 -1
- package/hooks/lib/raiz-proyecto.js +127 -102
- package/hooks/lib/run-log.js +1 -1
- package/hooks/lib/singleton-guard.js +20 -13
- package/hooks/lib/telegram-cliente.js +11 -3
- package/hooks/notificacion-telegram.js +13 -3
- package/hooks/preservar-estado-pre-compact.js +1 -1
- package/hooks/registro-turnos.js +1 -1
- package/hooks/resumen-sesion.js +1 -1
- package/hooks/risk-scoring.js +1 -1
- package/hooks/session-briefing.js +1 -1
- package/hooks/spec-gate.js +1 -1
- package/hooks/sugerir-regenerar-inventario.js +1 -1
- package/hooks/tdd-gate.js +1 -1
- package/hooks/telemetria-agentes.js +1 -1
- package/hooks/telemetria-skill-routing.js +1 -1
- package/hooks/tracking-costos.js +1 -1
- package/hooks/validar-formato-post-subagente.js +1 -1
- package/hooks/validar-intent-spec.js +1 -1
- package/hooks/validar-planning-paths.js +1 -1
- package/llms.txt +29 -29
- package/manifiestos/canonical-hashes.json +5588 -5257
- package/manifiestos/hooks-config.json +469 -469
- package/manifiestos/invariantes-criticos.json +30 -30
- package/manifiestos/modulos.json +1429 -1428
- package/manifiestos/skills-lock.json +1275 -1275
- package/package.json +94 -94
- package/plugin.json +369 -369
- package/scripts/auditar-clases-conocidas.js +134 -134
- package/scripts/bootstrap-instintos.js +85 -14
- package/scripts/canario-hooks.js +166 -166
- package/scripts/cli/autonomia.js +23 -23
- package/scripts/cli/benchmark-memoria.js +37 -37
- package/scripts/cli/ciclo-autonomo.js +73 -73
- package/scripts/cli/ciclo-fase-b.js +102 -102
- package/scripts/cli/guardrail-metrics.js +39 -39
- package/scripts/cli/memoria-search.js +69 -69
- package/scripts/cli/nudge-accionar.js +39 -39
- package/scripts/cli/run-eval.js +38 -38
- package/scripts/doctor.js +26 -3
- package/scripts/evidencia-valor.js +101 -101
- package/scripts/field-report.js +16 -16
- package/scripts/instalador.js +13 -0
- package/scripts/lib/activar-hooks-proyecto.js +116 -116
- package/scripts/lib/ciclo-autonomo/candidatos.js +174 -174
- package/scripts/lib/ciclo-autonomo/config.js +165 -165
- package/scripts/lib/ciclo-autonomo/drenador-feedback.js +174 -174
- package/scripts/lib/ciclo-autonomo/fallback.js +77 -77
- package/scripts/lib/ciclo-autonomo/guard-convivencia.js +139 -139
- package/scripts/lib/ciclo-autonomo/higiene-nudges.js +112 -112
- package/scripts/lib/ciclo-autonomo/index.js +301 -301
- package/scripts/lib/ciclo-autonomo/lock.js +124 -124
- package/scripts/lib/ciclo-autonomo/presupuesto.js +122 -122
- package/scripts/lib/ciclo-autonomo/puente-degradacion.js +240 -240
- package/scripts/lib/ciclo-autonomo/runner-fase-b.js +248 -248
- package/scripts/lib/ciclo-autonomo/writer-instintos.js +190 -190
- package/scripts/lib/ciclo-autonomo/yaml-instintos.js +535 -535
- package/scripts/lib/evidencia-valor.js +228 -228
- package/scripts/lib/expandir-targets.js +71 -71
- 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/tui/pantallas/install-wizard.js +403 -403
- package/instintos/.backups/perfil-usuario.yaml.2026-07-10-165128.bak +0 -53
- package/instintos/.backups/proyecto.yaml.2026-07-10-165128.bak +0 -372
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* candidatos.js — Detector determinista de candidatos a Fase B del ciclo
|
|
5
|
-
* autónomo (Fase 22, T-02, ADR-0042 gate G-B, REQ-22-01).
|
|
6
|
-
*
|
|
7
|
-
* Tres fuentes deterministas, SIN invocar LLM:
|
|
8
|
-
* 1. Instintos con `candidato_global:true` en `instintos/proyecto.yaml`
|
|
9
|
-
* (via `yaml-instintos.leerInstintos`, tolerante a archivo ausente).
|
|
10
|
-
* 2. Nudges `kind:'auto-evolucion'` no accionados con evidencia
|
|
11
|
-
* (`data.diagnosis` o `data.razon` en {fallos, loop}) leídos de
|
|
12
|
-
* `evolution/nudges.jsonl`.
|
|
13
|
-
* 3. Skills fríos vía `require('../skill-metrics').skillsStale` (require
|
|
14
|
-
* tolerante → `[]` si la lib no resuelve en el destino de instalación).
|
|
15
|
-
*
|
|
16
|
-
* Persiste SOLO los candidatos nuevos (dedupe por `id`, releyendo el jsonl
|
|
17
|
-
* previo con tolerancia por línea) en `evolution/candidatos-fase-b.jsonl`.
|
|
18
|
-
* Una corrida sin candidatos nuevos NO escribe el archivo (ni lo crea si no
|
|
19
|
-
* existía). Zero-deps, CommonJS.
|
|
20
|
-
*
|
|
21
|
-
* @module scripts/lib/ciclo-autonomo/candidatos
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
const fs = require('fs');
|
|
25
|
-
const path = require('path');
|
|
26
|
-
const { leerInstintos } = require('./yaml-instintos');
|
|
27
|
-
|
|
28
|
-
const RUTA_INSTINTOS_RELATIVA = path.join('instintos', 'proyecto.yaml');
|
|
29
|
-
const RUTA_NUDGES_RELATIVA = path.join('.planning', 'evolution', 'nudges.jsonl');
|
|
30
|
-
const RUTA_CANDIDATOS_RELATIVA = path.join('.planning', 'evolution', 'candidatos-fase-b.jsonl');
|
|
31
|
-
|
|
32
|
-
/** Razones de nudge `auto-evolucion` que cuentan como evidencia suficiente. */
|
|
33
|
-
const RAZONES_CON_EVIDENCIA = new Set(['fallos', 'loop']);
|
|
34
|
-
|
|
35
|
-
/** Lee un JSONL con tolerancia por línea: línea corrupta se descarta, nunca crashea. */
|
|
36
|
-
function leerJsonlTolerante(ruta) {
|
|
37
|
-
let crudo;
|
|
38
|
-
try {
|
|
39
|
-
crudo = fs.readFileSync(ruta, 'utf8');
|
|
40
|
-
} catch (_) {
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
return crudo.split(/\r\n|\n/)
|
|
44
|
-
.filter((linea) => linea.trim() !== '')
|
|
45
|
-
.map((linea) => {
|
|
46
|
-
try {
|
|
47
|
-
return JSON.parse(linea);
|
|
48
|
-
} catch (_) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
.filter(Boolean);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Fuente 1: instintos con `candidato_global:true` en instintos/proyecto.yaml. */
|
|
56
|
-
function candidatosDeInstintos(baseDir, ts) {
|
|
57
|
-
const ruta = path.join(baseDir, RUTA_INSTINTOS_RELATIVA);
|
|
58
|
-
let datos;
|
|
59
|
-
try {
|
|
60
|
-
datos = leerInstintos(ruta);
|
|
61
|
-
} catch (_) {
|
|
62
|
-
return []; // archivo ausente o YAML corrupto: sin candidatos de esta fuente
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return (datos.instintos || [])
|
|
66
|
-
.filter((instinto) => instinto.candidato_global === true)
|
|
67
|
-
.map((instinto) => ({
|
|
68
|
-
id: `instinto:${instinto.id}`,
|
|
69
|
-
fuente: 'instinto-global',
|
|
70
|
-
ref: { id: instinto.id, pattern: instinto.pattern || null },
|
|
71
|
-
ts,
|
|
72
|
-
origen: 'ciclo-autonomo',
|
|
73
|
-
}));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Fuente 2: nudges `kind:'auto-evolucion'` no accionados con evidencia. */
|
|
77
|
-
function candidatosDeNudges(baseDir, ts) {
|
|
78
|
-
const ruta = path.join(baseDir, RUTA_NUDGES_RELATIVA);
|
|
79
|
-
|
|
80
|
-
return leerJsonlTolerante(ruta)
|
|
81
|
-
.filter((nudge) => nudge && nudge.kind === 'auto-evolucion' && nudge.accionado !== true)
|
|
82
|
-
.filter((nudge) => {
|
|
83
|
-
const data = nudge.data || {};
|
|
84
|
-
return Boolean(data.diagnosis) || RAZONES_CON_EVIDENCIA.has(data.razon);
|
|
85
|
-
})
|
|
86
|
-
.map((nudge) => ({
|
|
87
|
-
id: `nudge:${nudge.id}`,
|
|
88
|
-
fuente: 'evolucion-agente',
|
|
89
|
-
ref: {
|
|
90
|
-
target: nudge.target || null,
|
|
91
|
-
razon: (nudge.data || {}).razon || null,
|
|
92
|
-
tieneDiagnosis: Boolean((nudge.data || {}).diagnosis),
|
|
93
|
-
},
|
|
94
|
-
ts,
|
|
95
|
-
origen: 'ciclo-autonomo',
|
|
96
|
-
}));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** Fuente 3: skills fríos vía skill-metrics.skillsStale (require tolerante → []). */
|
|
100
|
-
function candidatosDeSkillsFrios(baseDir, ahora, ts) {
|
|
101
|
-
let skillsStale;
|
|
102
|
-
try {
|
|
103
|
-
({ skillsStale } = require('../skill-metrics'));
|
|
104
|
-
} catch (_) {
|
|
105
|
-
return []; // lib no distribuida en este destino: sin candidatos de esta fuente
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let frios;
|
|
109
|
-
try {
|
|
110
|
-
frios = skillsStale(baseDir, ahora);
|
|
111
|
-
} catch (_) {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return (frios || []).map((frio) => ({
|
|
116
|
-
id: `skill:${frio.skill}`,
|
|
117
|
-
fuente: 'skill-stale',
|
|
118
|
-
ref: { skill: frio.skill, diasInactivo: frio.diasInactivo },
|
|
119
|
-
ts,
|
|
120
|
-
origen: 'ciclo-autonomo',
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Ids ya persistidos en `candidatos-fase-b.jsonl` (dedupe, tolerante por línea). */
|
|
125
|
-
function idsConocidos(baseDir) {
|
|
126
|
-
const ruta = path.join(baseDir, RUTA_CANDIDATOS_RELATIVA);
|
|
127
|
-
return new Set(leerJsonlTolerante(ruta).map((entrada) => entrada.id).filter(Boolean));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Append de los candidatos nuevos (dedupe ya aplicado por el caller); no-op si viene vacío. */
|
|
131
|
-
function persistirNuevos(baseDir, nuevos) {
|
|
132
|
-
if (nuevos.length === 0) return;
|
|
133
|
-
|
|
134
|
-
const ruta = path.join(baseDir, RUTA_CANDIDATOS_RELATIVA);
|
|
135
|
-
const dir = path.dirname(ruta);
|
|
136
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
137
|
-
|
|
138
|
-
const lineas = nuevos.map((candidato) => JSON.stringify(candidato)).join('\n') + '\n';
|
|
139
|
-
fs.appendFileSync(ruta, lineas);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Detecta candidatos a Fase B desde las tres fuentes deterministas y
|
|
144
|
-
* persiste (dedupe por id) SOLO los nuevos en `evolution/candidatos-fase-b.jsonl`.
|
|
145
|
-
* Tolerante a la ausencia de cualquier fuente — nunca crashea.
|
|
146
|
-
*
|
|
147
|
-
* @param {string} baseDir - Directorio raíz del proyecto.
|
|
148
|
-
* @param {{ahora?: Date}} [opciones]
|
|
149
|
-
* @returns {{nuevos: number, total: number, candidatos: object[]}}
|
|
150
|
-
*/
|
|
151
|
-
function detectar(baseDir, { ahora } = {}) {
|
|
152
|
-
const momento = ahora || new Date();
|
|
153
|
-
const ts = momento.toISOString();
|
|
154
|
-
|
|
155
|
-
const candidatos = [
|
|
156
|
-
...candidatosDeInstintos(baseDir, ts),
|
|
157
|
-
...candidatosDeNudges(baseDir, ts),
|
|
158
|
-
...candidatosDeSkillsFrios(baseDir, momento, ts),
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
const conocidos = idsConocidos(baseDir);
|
|
162
|
-
const vistos = new Set();
|
|
163
|
-
const nuevosCandidatos = candidatos.filter((candidato) => {
|
|
164
|
-
if (conocidos.has(candidato.id) || vistos.has(candidato.id)) return false;
|
|
165
|
-
vistos.add(candidato.id);
|
|
166
|
-
return true;
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
persistirNuevos(baseDir, nuevosCandidatos);
|
|
170
|
-
|
|
171
|
-
return { nuevos: nuevosCandidatos.length, total: candidatos.length, candidatos };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
module.exports = { detectar };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* candidatos.js — Detector determinista de candidatos a Fase B del ciclo
|
|
5
|
+
* autónomo (Fase 22, T-02, ADR-0042 gate G-B, REQ-22-01).
|
|
6
|
+
*
|
|
7
|
+
* Tres fuentes deterministas, SIN invocar LLM:
|
|
8
|
+
* 1. Instintos con `candidato_global:true` en `instintos/proyecto.yaml`
|
|
9
|
+
* (via `yaml-instintos.leerInstintos`, tolerante a archivo ausente).
|
|
10
|
+
* 2. Nudges `kind:'auto-evolucion'` no accionados con evidencia
|
|
11
|
+
* (`data.diagnosis` o `data.razon` en {fallos, loop}) leídos de
|
|
12
|
+
* `evolution/nudges.jsonl`.
|
|
13
|
+
* 3. Skills fríos vía `require('../skill-metrics').skillsStale` (require
|
|
14
|
+
* tolerante → `[]` si la lib no resuelve en el destino de instalación).
|
|
15
|
+
*
|
|
16
|
+
* Persiste SOLO los candidatos nuevos (dedupe por `id`, releyendo el jsonl
|
|
17
|
+
* previo con tolerancia por línea) en `evolution/candidatos-fase-b.jsonl`.
|
|
18
|
+
* Una corrida sin candidatos nuevos NO escribe el archivo (ni lo crea si no
|
|
19
|
+
* existía). Zero-deps, CommonJS.
|
|
20
|
+
*
|
|
21
|
+
* @module scripts/lib/ciclo-autonomo/candidatos
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { leerInstintos } = require('./yaml-instintos');
|
|
27
|
+
|
|
28
|
+
const RUTA_INSTINTOS_RELATIVA = path.join('instintos', 'proyecto.yaml');
|
|
29
|
+
const RUTA_NUDGES_RELATIVA = path.join('.planning', 'evolution', 'nudges.jsonl');
|
|
30
|
+
const RUTA_CANDIDATOS_RELATIVA = path.join('.planning', 'evolution', 'candidatos-fase-b.jsonl');
|
|
31
|
+
|
|
32
|
+
/** Razones de nudge `auto-evolucion` que cuentan como evidencia suficiente. */
|
|
33
|
+
const RAZONES_CON_EVIDENCIA = new Set(['fallos', 'loop']);
|
|
34
|
+
|
|
35
|
+
/** Lee un JSONL con tolerancia por línea: línea corrupta se descarta, nunca crashea. */
|
|
36
|
+
function leerJsonlTolerante(ruta) {
|
|
37
|
+
let crudo;
|
|
38
|
+
try {
|
|
39
|
+
crudo = fs.readFileSync(ruta, 'utf8');
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
return crudo.split(/\r\n|\n/)
|
|
44
|
+
.filter((linea) => linea.trim() !== '')
|
|
45
|
+
.map((linea) => {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(linea);
|
|
48
|
+
} catch (_) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Fuente 1: instintos con `candidato_global:true` en instintos/proyecto.yaml. */
|
|
56
|
+
function candidatosDeInstintos(baseDir, ts) {
|
|
57
|
+
const ruta = path.join(baseDir, RUTA_INSTINTOS_RELATIVA);
|
|
58
|
+
let datos;
|
|
59
|
+
try {
|
|
60
|
+
datos = leerInstintos(ruta);
|
|
61
|
+
} catch (_) {
|
|
62
|
+
return []; // archivo ausente o YAML corrupto: sin candidatos de esta fuente
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (datos.instintos || [])
|
|
66
|
+
.filter((instinto) => instinto.candidato_global === true)
|
|
67
|
+
.map((instinto) => ({
|
|
68
|
+
id: `instinto:${instinto.id}`,
|
|
69
|
+
fuente: 'instinto-global',
|
|
70
|
+
ref: { id: instinto.id, pattern: instinto.pattern || null },
|
|
71
|
+
ts,
|
|
72
|
+
origen: 'ciclo-autonomo',
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Fuente 2: nudges `kind:'auto-evolucion'` no accionados con evidencia. */
|
|
77
|
+
function candidatosDeNudges(baseDir, ts) {
|
|
78
|
+
const ruta = path.join(baseDir, RUTA_NUDGES_RELATIVA);
|
|
79
|
+
|
|
80
|
+
return leerJsonlTolerante(ruta)
|
|
81
|
+
.filter((nudge) => nudge && nudge.kind === 'auto-evolucion' && nudge.accionado !== true)
|
|
82
|
+
.filter((nudge) => {
|
|
83
|
+
const data = nudge.data || {};
|
|
84
|
+
return Boolean(data.diagnosis) || RAZONES_CON_EVIDENCIA.has(data.razon);
|
|
85
|
+
})
|
|
86
|
+
.map((nudge) => ({
|
|
87
|
+
id: `nudge:${nudge.id}`,
|
|
88
|
+
fuente: 'evolucion-agente',
|
|
89
|
+
ref: {
|
|
90
|
+
target: nudge.target || null,
|
|
91
|
+
razon: (nudge.data || {}).razon || null,
|
|
92
|
+
tieneDiagnosis: Boolean((nudge.data || {}).diagnosis),
|
|
93
|
+
},
|
|
94
|
+
ts,
|
|
95
|
+
origen: 'ciclo-autonomo',
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Fuente 3: skills fríos vía skill-metrics.skillsStale (require tolerante → []). */
|
|
100
|
+
function candidatosDeSkillsFrios(baseDir, ahora, ts) {
|
|
101
|
+
let skillsStale;
|
|
102
|
+
try {
|
|
103
|
+
({ skillsStale } = require('../skill-metrics'));
|
|
104
|
+
} catch (_) {
|
|
105
|
+
return []; // lib no distribuida en este destino: sin candidatos de esta fuente
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let frios;
|
|
109
|
+
try {
|
|
110
|
+
frios = skillsStale(baseDir, ahora);
|
|
111
|
+
} catch (_) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (frios || []).map((frio) => ({
|
|
116
|
+
id: `skill:${frio.skill}`,
|
|
117
|
+
fuente: 'skill-stale',
|
|
118
|
+
ref: { skill: frio.skill, diasInactivo: frio.diasInactivo },
|
|
119
|
+
ts,
|
|
120
|
+
origen: 'ciclo-autonomo',
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Ids ya persistidos en `candidatos-fase-b.jsonl` (dedupe, tolerante por línea). */
|
|
125
|
+
function idsConocidos(baseDir) {
|
|
126
|
+
const ruta = path.join(baseDir, RUTA_CANDIDATOS_RELATIVA);
|
|
127
|
+
return new Set(leerJsonlTolerante(ruta).map((entrada) => entrada.id).filter(Boolean));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Append de los candidatos nuevos (dedupe ya aplicado por el caller); no-op si viene vacío. */
|
|
131
|
+
function persistirNuevos(baseDir, nuevos) {
|
|
132
|
+
if (nuevos.length === 0) return;
|
|
133
|
+
|
|
134
|
+
const ruta = path.join(baseDir, RUTA_CANDIDATOS_RELATIVA);
|
|
135
|
+
const dir = path.dirname(ruta);
|
|
136
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const lineas = nuevos.map((candidato) => JSON.stringify(candidato)).join('\n') + '\n';
|
|
139
|
+
fs.appendFileSync(ruta, lineas);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detecta candidatos a Fase B desde las tres fuentes deterministas y
|
|
144
|
+
* persiste (dedupe por id) SOLO los nuevos en `evolution/candidatos-fase-b.jsonl`.
|
|
145
|
+
* Tolerante a la ausencia de cualquier fuente — nunca crashea.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} baseDir - Directorio raíz del proyecto.
|
|
148
|
+
* @param {{ahora?: Date}} [opciones]
|
|
149
|
+
* @returns {{nuevos: number, total: number, candidatos: object[]}}
|
|
150
|
+
*/
|
|
151
|
+
function detectar(baseDir, { ahora } = {}) {
|
|
152
|
+
const momento = ahora || new Date();
|
|
153
|
+
const ts = momento.toISOString();
|
|
154
|
+
|
|
155
|
+
const candidatos = [
|
|
156
|
+
...candidatosDeInstintos(baseDir, ts),
|
|
157
|
+
...candidatosDeNudges(baseDir, ts),
|
|
158
|
+
...candidatosDeSkillsFrios(baseDir, momento, ts),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
const conocidos = idsConocidos(baseDir);
|
|
162
|
+
const vistos = new Set();
|
|
163
|
+
const nuevosCandidatos = candidatos.filter((candidato) => {
|
|
164
|
+
if (conocidos.has(candidato.id) || vistos.has(candidato.id)) return false;
|
|
165
|
+
vistos.add(candidato.id);
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
persistirNuevos(baseDir, nuevosCandidatos);
|
|
170
|
+
|
|
171
|
+
return { nuevos: nuevosCandidatos.length, total: candidatos.length, candidatos };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = { detectar };
|