@saulwade/swl-ses 1.0.1 → 1.1.2
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 +8 -5
- package/README.md +3 -3
- package/agentes/accesibilidad-wcag-swl.md +5 -7
- package/agentes/arquitecto-swl.md +5 -3
- package/agentes/auto-evolucion-swl.md +42 -12
- package/agentes/backend-api-swl.md +5 -3
- package/agentes/backend-csharp-swl.md +5 -3
- package/agentes/backend-go-swl.md +5 -3
- package/agentes/backend-java-swl.md +5 -3
- package/agentes/backend-node-swl.md +5 -3
- package/agentes/backend-python-swl.md +5 -3
- package/agentes/backend-rust-swl.md +5 -3
- package/agentes/backend-workers-swl.md +5 -3
- package/agentes/cloud-infra-swl.md +5 -6
- package/agentes/consolidador-swl.md +5 -3
- package/agentes/datos-swl.md +5 -7
- package/agentes/depurador-swl.md +6 -3
- package/agentes/devops-ci-swl.md +5 -3
- package/agentes/disenador-ui-swl.md +5 -7
- package/agentes/documentador-swl.md +5 -3
- package/agentes/frontend-angular-swl.md +5 -11
- package/agentes/frontend-css-swl.md +5 -9
- package/agentes/frontend-react-swl.md +5 -9
- package/agentes/frontend-swl.md +5 -9
- package/agentes/frontend-tailwind-swl.md +5 -9
- package/agentes/implementador-swl.md +6 -3
- package/agentes/investigador-swl.md +5 -3
- package/agentes/investigador-ux-swl.md +5 -9
- package/agentes/llm-apps-swl.md +5 -3
- package/agentes/migrador-swl.md +6 -3
- package/agentes/mobile-android-swl.md +5 -3
- package/agentes/mobile-cross-swl.md +5 -3
- package/agentes/mobile-ios-swl.md +5 -3
- package/agentes/mobile-testing-swl.md +5 -3
- package/agentes/notificador-swl.md +5 -3
- package/agentes/observabilidad-swl.md +5 -3
- package/agentes/orquestador-swl.md +29 -8
- package/agentes/pagos-swl.md +5 -3
- package/agentes/perfilador-usuario-swl.md +4 -2
- package/agentes/planificador-swl.md +5 -3
- package/agentes/producto-prd-swl.md +5 -3
- package/agentes/red-team-swl.md +4 -2
- package/agentes/release-manager-swl.md +6 -8
- package/agentes/rendimiento-swl.md +5 -6
- package/agentes/resolutor-build-swl.md +5 -3
- package/agentes/revisor-angular-swl.md +5 -3
- package/agentes/revisor-codigo-swl.md +90 -4
- package/agentes/revisor-csharp-swl.md +5 -3
- package/agentes/revisor-go-swl.md +5 -3
- package/agentes/revisor-java-swl.md +5 -3
- package/agentes/revisor-kotlin-swl.md +5 -3
- package/agentes/revisor-nextjs-swl.md +5 -3
- package/agentes/revisor-php-swl.md +5 -3
- package/agentes/revisor-react-swl.md +5 -3
- package/agentes/revisor-rust-swl.md +5 -3
- package/agentes/revisor-seguridad-swl.md +5 -3
- package/agentes/revisor-swift-swl.md +5 -3
- package/agentes/revisor-typescript-swl.md +5 -3
- package/agentes/sre-swl.md +5 -3
- package/agentes/tdd-qa-swl.md +5 -3
- package/agentes/ux-disenador-swl.md +5 -9
- package/comandos/swl/evaluar-skill.md +18 -0
- package/comandos/swl/evolucion-estado.md +49 -0
- package/comandos/swl/release.md +77 -1
- package/comandos/swl/salud.md +23 -0
- package/habilidades/checklist-seguridad/SKILL.md +57 -1
- package/habilidades/extractor-de-aprendizajes/SKILL.md +15 -5
- package/habilidades/fastapi-experto/SKILL.md +10 -1
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/manejo-errores/SKILL.md +63 -4
- package/habilidades/patrones-python/SKILL.md +5 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/release-semver/SKILL.md +85 -1
- package/hooks/auto-evolucion.js +35 -1
- package/hooks/clasificador-mensajes.js +50 -3
- package/hooks/lib/agent-routing.js +107 -0
- package/hooks/lib/delegation-tracker.js +162 -44
- package/hooks/lib/evolution-tracker.js +12 -3
- package/hooks/lib/memory-search.js +59 -1
- package/hooks/lib/nudge-tracker.js +10 -1
- package/hooks/lib/provenance-tracker.js +11 -3
- package/hooks/lib/text-similarity.js +241 -0
- package/hooks/metricas-evolucion.js +168 -1
- package/hooks/monitor-contexto.js +54 -6
- package/hooks/preservar-estado-pre-compact.js +11 -1
- package/hooks/risk-scoring.js +10 -1
- package/hooks/tracking-costos.js +10 -1
- package/hooks/validar-formato-post-subagente.js +140 -0
- package/hooks/validar-memoria-hook.js +218 -0
- package/manifiestos/agent-output-schemas.json +57 -0
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +3 -0
- package/manifiestos/skills-lock.json +1065 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/reglas/arquitectura.md +20 -0
- package/reglas/fragmentos-compartidos.md +152 -0
- package/reglas/gobernanza.md +10 -1
- package/reglas/seguridad-agentes.md +12 -0
- package/reglas/skills-estandar.md +19 -0
- package/schemas/agent-frontmatter.schema.json +18 -0
- package/scripts/auditar-agentes-gaps.js +9 -1
- package/scripts/auditar-cobertura-frameworks.js +9 -1
- package/scripts/auditar-skills-gaps.js +9 -1
- package/scripts/bootstrap-instintos.js +11 -1
- package/scripts/generar-inventario.js +112 -9
- package/scripts/generar-matriz-lenguajes.js +271 -0
- package/scripts/generar-skills-lock.js +190 -0
- package/scripts/lib/estado.js +12 -2
- package/scripts/lib/gitignore-manifest.js +32 -2
- package/scripts/migrar-csv-a-array.js +168 -0
- package/scripts/migrar-fase-dominio.js +201 -0
- package/scripts/publicar.js +88 -18
|
@@ -10,13 +10,32 @@
|
|
|
10
10
|
* - Blocked toolsets (sin delegación recursiva, sin memoria compartida)
|
|
11
11
|
* - Reporte estructurado con duración y métricas
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Persistencia: estado serializado a `${TMPDIR}/swl-delegation-${sessionId}.json`.
|
|
14
|
+
* Como cada hook se invoca en proceso Node fresco, el estado en variables de
|
|
15
|
+
* módulo no sobrevive entre invocaciones — el archivo es la fuente de verdad.
|
|
16
|
+
*
|
|
17
|
+
* Reconciliación tras /compact, /clear o reinicio:
|
|
18
|
+
* - El cliente Claude Code emite un nuevo session_id en esos eventos.
|
|
19
|
+
* - Al pasar un sessionId nuevo a las funciones API, se lee/escribe un
|
|
20
|
+
* archivo distinto, descartando el estado anterior automáticamente.
|
|
21
|
+
* - `cleanupStaleSessions()` purga archivos cuya última escritura supera el
|
|
22
|
+
* TTL (default 1h) — protege contra leaks por sesiones que terminan sin
|
|
23
|
+
* señalizar fin.
|
|
24
|
+
*
|
|
25
|
+
* Compatibilidad: si no se pasa sessionId (modo legacy), se usa estado
|
|
26
|
+
* en memoria del proceso. Funciona para tests unitarios y para invocaciones
|
|
27
|
+
* dentro de un mismo proceso, NO para coordinación entre hooks distintos.
|
|
28
|
+
*
|
|
14
29
|
* Integración: risk-scoring.js consulta profundidad, tracking-costos.js
|
|
15
30
|
* captura métricas por subagente.
|
|
16
31
|
*
|
|
17
32
|
* @module hooks/lib/delegation-tracker
|
|
18
33
|
*/
|
|
19
34
|
|
|
35
|
+
const fs = require('fs');
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const os = require('os');
|
|
38
|
+
|
|
20
39
|
// ---------------------------------------------------------------------------
|
|
21
40
|
// Constantes
|
|
22
41
|
// ---------------------------------------------------------------------------
|
|
@@ -27,18 +46,96 @@ const MAX_DELEGATION_DEPTH = 2;
|
|
|
27
46
|
/** Máximo de subagentes ejecutándose en paralelo. */
|
|
28
47
|
const MAX_CONCURRENT_CHILDREN = 3;
|
|
29
48
|
|
|
49
|
+
/** TTL de archivos de estado huérfanos (ms). Default 1h. */
|
|
50
|
+
const STALE_TTL_MS = 60 * 60 * 1000;
|
|
51
|
+
|
|
30
52
|
// ---------------------------------------------------------------------------
|
|
31
|
-
//
|
|
53
|
+
// Persistencia file-based (por sessionId) y memoria (legacy / fallback)
|
|
32
54
|
// ---------------------------------------------------------------------------
|
|
33
55
|
|
|
34
|
-
/**
|
|
35
|
-
const
|
|
56
|
+
/** Estado en memoria del proceso (modo legacy, sin sessionId). */
|
|
57
|
+
const _memState = {
|
|
58
|
+
delegationStack: [],
|
|
59
|
+
activeChildren: 0,
|
|
60
|
+
completedDelegations: [],
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Path del archivo de estado para una sesión. */
|
|
64
|
+
function _statePath(sessionId) {
|
|
65
|
+
return path.join(os.tmpdir(), `swl-delegation-${sessionId}.json`);
|
|
66
|
+
}
|
|
36
67
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
68
|
+
/** Lee el estado de una sesión desde archivo, o devuelve estado vacío. */
|
|
69
|
+
function _readState(sessionId) {
|
|
70
|
+
if (!sessionId) return _memState;
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(_statePath(sessionId), 'utf8');
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
return {
|
|
75
|
+
delegationStack: Array.isArray(parsed.delegationStack) ? parsed.delegationStack : [],
|
|
76
|
+
activeChildren: Number.isInteger(parsed.activeChildren) ? parsed.activeChildren : 0,
|
|
77
|
+
completedDelegations: Array.isArray(parsed.completedDelegations) ? parsed.completedDelegations : [],
|
|
78
|
+
};
|
|
79
|
+
} catch (_) {
|
|
80
|
+
return { delegationStack: [], activeChildren: 0, completedDelegations: [] };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Escribe el estado de una sesión a archivo (atómico-ish: write + rename). */
|
|
85
|
+
function _writeState(sessionId, state) {
|
|
86
|
+
if (!sessionId) {
|
|
87
|
+
_memState.delegationStack = state.delegationStack;
|
|
88
|
+
_memState.activeChildren = state.activeChildren;
|
|
89
|
+
_memState.completedDelegations = state.completedDelegations;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const target = _statePath(sessionId);
|
|
94
|
+
const tmp = target + '.tmp';
|
|
95
|
+
fs.writeFileSync(tmp, JSON.stringify(state), 'utf8');
|
|
96
|
+
fs.renameSync(tmp, target);
|
|
97
|
+
} catch (_) {
|
|
98
|
+
// No bloquear el flujo si falla escritura — el módulo es advisory.
|
|
99
|
+
}
|
|
100
|
+
}
|
|
39
101
|
|
|
40
|
-
/**
|
|
41
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Purga archivos de estado de sesiones huérfanas.
|
|
104
|
+
* Una sesión "muerta" es la que no recibió update en STALE_TTL_MS.
|
|
105
|
+
*
|
|
106
|
+
* Llamar al iniciar trabajo nuevo o desde `/swl:salud` para evitar leaks.
|
|
107
|
+
*
|
|
108
|
+
* @param {number} [ttlMs=STALE_TTL_MS] - TTL en ms.
|
|
109
|
+
* @returns {{ removed: string[], kept: string[] }}
|
|
110
|
+
*/
|
|
111
|
+
function cleanupStaleSessions(ttlMs = STALE_TTL_MS) {
|
|
112
|
+
const tmpDir = os.tmpdir();
|
|
113
|
+
const removed = [];
|
|
114
|
+
const kept = [];
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
let entries;
|
|
117
|
+
try {
|
|
118
|
+
entries = fs.readdirSync(tmpDir);
|
|
119
|
+
} catch (_) {
|
|
120
|
+
return { removed, kept };
|
|
121
|
+
}
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
if (!/^swl-delegation-.+\.json$/.test(entry)) continue;
|
|
124
|
+
const fullPath = path.join(tmpDir, entry);
|
|
125
|
+
try {
|
|
126
|
+
const stat = fs.statSync(fullPath);
|
|
127
|
+
if (now - stat.mtimeMs > ttlMs) {
|
|
128
|
+
fs.unlinkSync(fullPath);
|
|
129
|
+
removed.push(entry);
|
|
130
|
+
} else {
|
|
131
|
+
kept.push(entry);
|
|
132
|
+
}
|
|
133
|
+
} catch (_) {
|
|
134
|
+
// Archivo desapareció en condición de carrera — ignorar.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { removed, kept };
|
|
138
|
+
}
|
|
42
139
|
|
|
43
140
|
// ---------------------------------------------------------------------------
|
|
44
141
|
// API pública
|
|
@@ -47,26 +144,29 @@ const _completedDelegations = [];
|
|
|
47
144
|
/**
|
|
48
145
|
* Verifica si una delegación es permitida.
|
|
49
146
|
*
|
|
50
|
-
* @param {string} agentName
|
|
147
|
+
* @param {string} agentName - Nombre del agente que solicita delegar.
|
|
148
|
+
* @param {string} [sessionId] - ID de sesión Claude Code. Si se omite, usa
|
|
149
|
+
* estado en memoria (modo legacy).
|
|
51
150
|
* @returns {{ allowed: boolean, reason?: string, depth?: number }}
|
|
52
151
|
*/
|
|
53
|
-
function canDelegate(agentName) {
|
|
54
|
-
const
|
|
152
|
+
function canDelegate(agentName, sessionId) {
|
|
153
|
+
const state = _readState(sessionId);
|
|
154
|
+
const currentDepth = state.delegationStack.length;
|
|
55
155
|
|
|
56
156
|
if (currentDepth >= MAX_DELEGATION_DEPTH) {
|
|
57
157
|
return {
|
|
58
158
|
allowed: false,
|
|
59
159
|
reason: `Profundidad máxima de delegación alcanzada (${MAX_DELEGATION_DEPTH}). ` +
|
|
60
|
-
`Stack actual: ${
|
|
160
|
+
`Stack actual: ${state.delegationStack.map(d => d.agentName).join(' → ')}. ` +
|
|
61
161
|
`No se permite delegación adicional para prevenir recursión.`,
|
|
62
162
|
};
|
|
63
163
|
}
|
|
64
164
|
|
|
65
|
-
if (
|
|
165
|
+
if (state.activeChildren >= MAX_CONCURRENT_CHILDREN) {
|
|
66
166
|
return {
|
|
67
167
|
allowed: false,
|
|
68
168
|
reason: `Máximo de subagentes concurrentes alcanzado (${MAX_CONCURRENT_CHILDREN}). ` +
|
|
69
|
-
`Activos: ${
|
|
169
|
+
`Activos: ${state.delegationStack.filter(d => d.active).map(d => d.agentName).join(', ')}. ` +
|
|
70
170
|
`Esperar a que terminen antes de delegar.`,
|
|
71
171
|
};
|
|
72
172
|
}
|
|
@@ -77,23 +177,26 @@ function canDelegate(agentName) {
|
|
|
77
177
|
/**
|
|
78
178
|
* Registra el inicio de una delegación.
|
|
79
179
|
*
|
|
80
|
-
* @param {string} agentName
|
|
81
|
-
* @param {string} [taskId]
|
|
180
|
+
* @param {string} agentName - Nombre del agente delegado.
|
|
181
|
+
* @param {string} [taskId] - ID de la tarea (para correlación).
|
|
182
|
+
* @param {string} [sessionId] - ID de sesión Claude Code.
|
|
82
183
|
* @returns {string} ID único de la delegación (para trackDelegationComplete).
|
|
83
184
|
*/
|
|
84
|
-
function trackDelegationStart(agentName, taskId) {
|
|
185
|
+
function trackDelegationStart(agentName, taskId, sessionId) {
|
|
186
|
+
const state = _readState(sessionId);
|
|
85
187
|
const delegationId = `del-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
86
188
|
|
|
87
|
-
|
|
189
|
+
state.delegationStack.push({
|
|
88
190
|
delegationId,
|
|
89
191
|
agentName,
|
|
90
192
|
taskId: taskId || delegationId,
|
|
91
|
-
depth:
|
|
193
|
+
depth: state.delegationStack.length,
|
|
92
194
|
startTime: Date.now(),
|
|
93
195
|
active: true,
|
|
94
196
|
});
|
|
95
197
|
|
|
96
|
-
|
|
198
|
+
state.activeChildren += 1;
|
|
199
|
+
_writeState(sessionId, state);
|
|
97
200
|
return delegationId;
|
|
98
201
|
}
|
|
99
202
|
|
|
@@ -105,18 +208,19 @@ function trackDelegationStart(agentName, taskId) {
|
|
|
105
208
|
* @param {string} [result.status='completed'] - Estado final.
|
|
106
209
|
* @param {number} [result.tokensUsed] - Tokens consumidos.
|
|
107
210
|
* @param {number} [result.toolCalls] - Herramientas invocadas.
|
|
211
|
+
* @param {string} [sessionId] - ID de sesión Claude Code.
|
|
108
212
|
* @returns {object|null} Métricas de la delegación, o null si no se encontró.
|
|
109
213
|
*/
|
|
110
|
-
function trackDelegationComplete(delegationId, result = {}) {
|
|
111
|
-
const
|
|
214
|
+
function trackDelegationComplete(delegationId, result = {}, sessionId) {
|
|
215
|
+
const state = _readState(sessionId);
|
|
216
|
+
const idx = state.delegationStack.findIndex(d => d.delegationId === delegationId);
|
|
112
217
|
if (idx < 0) return null;
|
|
113
218
|
|
|
114
|
-
const entry =
|
|
219
|
+
const entry = state.delegationStack[idx];
|
|
115
220
|
entry.active = false;
|
|
116
|
-
|
|
221
|
+
state.activeChildren = Math.max(0, state.activeChildren - 1);
|
|
117
222
|
|
|
118
|
-
|
|
119
|
-
_delegationStack.splice(idx, 1);
|
|
223
|
+
state.delegationStack.splice(idx, 1);
|
|
120
224
|
|
|
121
225
|
const completed = {
|
|
122
226
|
delegationId,
|
|
@@ -129,19 +233,17 @@ function trackDelegationComplete(delegationId, result = {}) {
|
|
|
129
233
|
toolCalls: result.toolCalls || 0,
|
|
130
234
|
};
|
|
131
235
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Mantener solo las últimas 50 delegaciones completadas
|
|
135
|
-
if (_completedDelegations.length > 50) {
|
|
136
|
-
_completedDelegations.shift();
|
|
137
|
-
}
|
|
236
|
+
state.completedDelegations.push(completed);
|
|
237
|
+
if (state.completedDelegations.length > 50) state.completedDelegations.shift();
|
|
138
238
|
|
|
239
|
+
_writeState(sessionId, state);
|
|
139
240
|
return completed;
|
|
140
241
|
}
|
|
141
242
|
|
|
142
243
|
/**
|
|
143
244
|
* Obtiene el estado actual de delegación.
|
|
144
245
|
*
|
|
246
|
+
* @param {string} [sessionId] - ID de sesión Claude Code.
|
|
145
247
|
* @returns {{
|
|
146
248
|
* currentDepth: number,
|
|
147
249
|
* activeChildren: number,
|
|
@@ -151,19 +253,20 @@ function trackDelegationComplete(delegationId, result = {}) {
|
|
|
151
253
|
* recentCompleted: object[]
|
|
152
254
|
* }}
|
|
153
255
|
*/
|
|
154
|
-
function getDelegationState() {
|
|
256
|
+
function getDelegationState(sessionId) {
|
|
257
|
+
const state = _readState(sessionId);
|
|
155
258
|
return {
|
|
156
|
-
currentDepth:
|
|
157
|
-
activeChildren:
|
|
259
|
+
currentDepth: state.delegationStack.length,
|
|
260
|
+
activeChildren: state.activeChildren,
|
|
158
261
|
maxDepth: MAX_DELEGATION_DEPTH,
|
|
159
262
|
maxConcurrent: MAX_CONCURRENT_CHILDREN,
|
|
160
|
-
stack:
|
|
263
|
+
stack: state.delegationStack.map(d => ({
|
|
161
264
|
agent: d.agentName,
|
|
162
265
|
depth: d.depth,
|
|
163
266
|
elapsedMs: Date.now() - d.startTime,
|
|
164
267
|
active: d.active,
|
|
165
268
|
})),
|
|
166
|
-
recentCompleted:
|
|
269
|
+
recentCompleted: state.completedDelegations.slice(-10),
|
|
167
270
|
};
|
|
168
271
|
}
|
|
169
272
|
|
|
@@ -171,19 +274,32 @@ function getDelegationState() {
|
|
|
171
274
|
* Calcula el factor de riesgo adicional por profundidad de delegación.
|
|
172
275
|
* Diseñado para integrarse con risk-scoring.js.
|
|
173
276
|
*
|
|
277
|
+
* @param {string} [sessionId] - ID de sesión Claude Code.
|
|
174
278
|
* @returns {number} Factor entre 0.0 y 0.30 (0.15 por nivel de profundidad).
|
|
175
279
|
*/
|
|
176
|
-
function getDelegationRiskFactor() {
|
|
177
|
-
|
|
280
|
+
function getDelegationRiskFactor(sessionId) {
|
|
281
|
+
const state = _readState(sessionId);
|
|
282
|
+
return Math.min(state.delegationStack.length * 0.15, 0.30);
|
|
178
283
|
}
|
|
179
284
|
|
|
180
285
|
/**
|
|
181
|
-
* Resetea el estado de delegación (para tests o
|
|
286
|
+
* Resetea el estado de delegación (para tests o reconciliación explícita).
|
|
287
|
+
*
|
|
288
|
+
* @param {string} [sessionId] - ID de sesión a resetear. Si se omite, resetea
|
|
289
|
+
* el estado en memoria (modo legacy).
|
|
182
290
|
*/
|
|
183
|
-
function reset() {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
291
|
+
function reset(sessionId) {
|
|
292
|
+
if (!sessionId) {
|
|
293
|
+
_memState.delegationStack.length = 0;
|
|
294
|
+
_memState.activeChildren = 0;
|
|
295
|
+
_memState.completedDelegations.length = 0;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
fs.unlinkSync(_statePath(sessionId));
|
|
300
|
+
} catch (_) {
|
|
301
|
+
// No existe → ya está reseteado.
|
|
302
|
+
}
|
|
187
303
|
}
|
|
188
304
|
|
|
189
305
|
module.exports = {
|
|
@@ -193,6 +309,8 @@ module.exports = {
|
|
|
193
309
|
getDelegationState,
|
|
194
310
|
getDelegationRiskFactor,
|
|
195
311
|
reset,
|
|
312
|
+
cleanupStaleSessions,
|
|
196
313
|
MAX_DELEGATION_DEPTH,
|
|
197
314
|
MAX_CONCURRENT_CHILDREN,
|
|
315
|
+
STALE_TTL_MS,
|
|
198
316
|
};
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
|
|
22
|
+
// Escritura atómica obligatoria (regla CLAUDE.md). Fallback defensivo.
|
|
23
|
+
let atomicWriteSync, atomicWriteJSON;
|
|
24
|
+
try {
|
|
25
|
+
({ atomicWriteSync, atomicWriteJSON } = require('./atomic-write'));
|
|
26
|
+
} catch {
|
|
27
|
+
atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
|
|
28
|
+
atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
// ---------------------------------------------------------------------------
|
|
23
32
|
// Constantes
|
|
24
33
|
// ---------------------------------------------------------------------------
|
|
@@ -167,7 +176,7 @@ function markAsEvolved(filePath, meta) {
|
|
|
167
176
|
frontmatter = frontmatter.trimEnd() + '\n' + newFields.join('\n');
|
|
168
177
|
|
|
169
178
|
const newContent = prefix + frontmatter + suffix + rest;
|
|
170
|
-
|
|
179
|
+
atomicWriteSync(filePath, newContent, 'utf8');
|
|
171
180
|
|
|
172
181
|
return { marked: true };
|
|
173
182
|
} catch (err) {
|
|
@@ -200,7 +209,7 @@ function _writeSidecar(filePath, meta) {
|
|
|
200
209
|
...(meta.note && { evolvedNote: meta.note }),
|
|
201
210
|
};
|
|
202
211
|
|
|
203
|
-
|
|
212
|
+
atomicWriteJSON(sidecarPath, data);
|
|
204
213
|
return { marked: true };
|
|
205
214
|
}
|
|
206
215
|
|
|
@@ -368,7 +377,7 @@ function mergeEvolved(destino, origen, versionNueva) {
|
|
|
368
377
|
]).flat(),
|
|
369
378
|
].join('\n');
|
|
370
379
|
|
|
371
|
-
|
|
380
|
+
atomicWriteSync(diffPath, diffContent, 'utf8');
|
|
372
381
|
|
|
373
382
|
return { merged: true, diffPath, diffsCount: diffs.length };
|
|
374
383
|
} catch (err) {
|
|
@@ -499,8 +499,66 @@ function fetch(baseDir, id) {
|
|
|
499
499
|
return null;
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// Instrumentación de utilidad — registra cada búsqueda a JSONL para que
|
|
504
|
+
// metricas-evolucion.js pueda calcular utilidad como proxy.
|
|
505
|
+
//
|
|
506
|
+
// Formato del registro:
|
|
507
|
+
// { ts, sessionId, op: 'search'|'timeline'|'fetch', query, resultsCount,
|
|
508
|
+
// baseDir }
|
|
509
|
+
//
|
|
510
|
+
// Opt-out: SWL_MEMORY_TELEMETRY=0 desactiva el logging.
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
|
|
513
|
+
function _logUsage(op, baseDir, query, resultsCount) {
|
|
514
|
+
if (process.env.SWL_MEMORY_TELEMETRY === '0') return;
|
|
515
|
+
try {
|
|
516
|
+
const dir = path.join(baseDir, '.planning', 'evolucion');
|
|
517
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
518
|
+
const entry = {
|
|
519
|
+
ts: new Date().toISOString(),
|
|
520
|
+
sessionId: String(process.env.SWL_SESSION_ID || process.env.CLAUDE_SESSION_ID || 'default'),
|
|
521
|
+
op,
|
|
522
|
+
query: typeof query === 'string' ? query.slice(0, 200) : String(query || '').slice(0, 200),
|
|
523
|
+
resultsCount: Number.isInteger(resultsCount) ? resultsCount : 0,
|
|
524
|
+
};
|
|
525
|
+
fs.appendFileSync(
|
|
526
|
+
path.join(dir, 'memory-usage.jsonl'),
|
|
527
|
+
JSON.stringify(entry) + '\n',
|
|
528
|
+
'utf8',
|
|
529
|
+
);
|
|
530
|
+
} catch { /* nunca bloquear por fallo de telemetría */ }
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const _searchInstrumented = (baseDir, query, filtros = {}) => {
|
|
534
|
+
const results = search(baseDir, query, filtros);
|
|
535
|
+
_logUsage('search', baseDir, query, Array.isArray(results) ? results.length : 0);
|
|
536
|
+
return results;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const _timelineInstrumented = (baseDir, ids) => {
|
|
540
|
+
const results = timeline(baseDir, ids);
|
|
541
|
+
const idsCount = Array.isArray(ids) ? ids.length : 0;
|
|
542
|
+
_logUsage('timeline', baseDir, `[${idsCount} ids]`, Array.isArray(results) ? results.length : 0);
|
|
543
|
+
return results;
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
const _fetchInstrumented = (baseDir, id) => {
|
|
547
|
+
const result = fetch(baseDir, id);
|
|
548
|
+
_logUsage('fetch', baseDir, String(id), result ? 1 : 0);
|
|
549
|
+
return result;
|
|
550
|
+
};
|
|
551
|
+
|
|
502
552
|
// ---------------------------------------------------------------------------
|
|
503
553
|
// Exports
|
|
504
554
|
// ---------------------------------------------------------------------------
|
|
505
555
|
|
|
506
|
-
module.exports = {
|
|
556
|
+
module.exports = {
|
|
557
|
+
search: _searchInstrumented,
|
|
558
|
+
timeline: _timelineInstrumented,
|
|
559
|
+
fetch: _fetchInstrumented,
|
|
560
|
+
// Versiones sin instrumentación, para tests internos.
|
|
561
|
+
_searchRaw: search,
|
|
562
|
+
_timelineRaw: timeline,
|
|
563
|
+
_fetchRaw: fetch,
|
|
564
|
+
};
|
|
@@ -23,6 +23,15 @@
|
|
|
23
23
|
|
|
24
24
|
const fs = require('fs');
|
|
25
25
|
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// Escritura atómica para reescritura completa del JSONL (marcar accionado).
|
|
28
|
+
// El append diario sigue usando appendFileSync (ver más abajo).
|
|
29
|
+
let atomicWriteSync;
|
|
30
|
+
try {
|
|
31
|
+
({ atomicWriteSync } = require('./atomic-write'));
|
|
32
|
+
} catch {
|
|
33
|
+
atomicWriteSync = (p, c, e) => fs.writeFileSync(p, c, e);
|
|
34
|
+
}
|
|
26
35
|
const crypto = require('crypto');
|
|
27
36
|
|
|
28
37
|
let atomicWriteJSON;
|
|
@@ -161,7 +170,7 @@ function markAccionado(id, by = 'desconocido') {
|
|
|
161
170
|
ensureDir();
|
|
162
171
|
const lines = entries.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
163
172
|
try {
|
|
164
|
-
|
|
173
|
+
atomicWriteSync(LOG_PATH, lines, 'utf8');
|
|
165
174
|
return true;
|
|
166
175
|
} catch {
|
|
167
176
|
return false;
|
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
const fs = require('fs');
|
|
30
30
|
const path = require('path');
|
|
31
31
|
|
|
32
|
+
// Escritura atómica obligatoria para meta.json (regla CLAUDE.md).
|
|
33
|
+
let atomicWriteJSON;
|
|
34
|
+
try {
|
|
35
|
+
({ atomicWriteJSON } = require('./atomic-write'));
|
|
36
|
+
} catch {
|
|
37
|
+
atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
// ---------------------------------------------------------------------------
|
|
33
41
|
// Constantes
|
|
34
42
|
// ---------------------------------------------------------------------------
|
|
@@ -93,13 +101,13 @@ function registrarProveniencia(componentDir, datos) {
|
|
|
93
101
|
Object.assign(existing, meta);
|
|
94
102
|
existing.history = existing.history.slice(-5); // Máximo 5 entradas de historial
|
|
95
103
|
|
|
96
|
-
|
|
104
|
+
atomicWriteJSON(metaPath, existing);
|
|
97
105
|
} catch {
|
|
98
106
|
// Error leyendo existente — sobrescribir
|
|
99
|
-
|
|
107
|
+
atomicWriteJSON(metaPath, meta);
|
|
100
108
|
}
|
|
101
109
|
} else {
|
|
102
|
-
|
|
110
|
+
atomicWriteJSON(metaPath, meta);
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
|