agentic-kdd 3.3.1 → 3.5.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/.cursorrules +169 -0
- package/AGENTS.md +173 -0
- package/CLAUDE.md +375 -0
- package/autonomous-decision.cjs +180 -0
- package/bin/akdd.js +31 -0
- package/kdd-memory.cjs +579 -0
- package/knowledge-validator.cjs +408 -0
- package/package.json +1 -1
- package/telemetry.cjs +400 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic KDD — Knowledge Validator v1.0
|
|
3
|
+
* Brecha (d): Validación de conocimiento
|
|
4
|
+
*
|
|
5
|
+
* Problema: la memoria puede volverse obsoleta o corrompida.
|
|
6
|
+
* - Obsolescencia: un patrón correcto hace 6 meses ya no aplica
|
|
7
|
+
* - Memory poisoning: MINJA logra >95% de inyección vía recuperación
|
|
8
|
+
*
|
|
9
|
+
* Solución (patrón SSGM + OWASP):
|
|
10
|
+
* 1. Frontmatter YAML por entrada: fecha, última_validación, estado, hash_contexto
|
|
11
|
+
* 2. hash_contexto = hash de los archivos a los que aplica la entrada
|
|
12
|
+
* 3. Si esos archivos cambiaron desde última_validación → marcar SOSPECHOSO
|
|
13
|
+
* 4. Temporal decay: entradas > 60 días sin validación pierden peso
|
|
14
|
+
* 5. validate_knowledge() MCP tool que agentes llaman antes de aplicar un patrón
|
|
15
|
+
*
|
|
16
|
+
* Estados posibles:
|
|
17
|
+
* ACTIVO → válido, confiable
|
|
18
|
+
* SOSPECHOSO → archivos de referencia cambiaron — revisar antes de aplicar
|
|
19
|
+
* OBSOLETO → no validado en > 90 días Y decay < 10%
|
|
20
|
+
* HISTORICO → invalidado explícitamente, preservado para auditoría
|
|
21
|
+
*
|
|
22
|
+
* Uso:
|
|
23
|
+
* node knowledge-validator.cjs scan — escanear toda la memoria
|
|
24
|
+
* node knowledge-validator.cjs validate <id> — validar una entrada
|
|
25
|
+
* node knowledge-validator.cjs report — reporte de estado
|
|
26
|
+
* node knowledge-validator.cjs revalidate <id> — marcar como revalidado
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const path = require('path');
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const crypto = require('crypto');
|
|
34
|
+
|
|
35
|
+
const DECAY_LAMBDA = 0.05; // mismo que kdd-memory
|
|
36
|
+
const SUSPECT_DAYS = 30; // días sin validar → SOSPECHOSO
|
|
37
|
+
const OBSOLETE_DAYS = 90; // días sin validar → OBSOLETO
|
|
38
|
+
const OBSOLETE_DECAY = 0.10; // si decay < 10% → candidato a OBSOLETO
|
|
39
|
+
const POISON_SIMILARITY = 0.95; // Jaccard para detectar entradas inyectadas
|
|
40
|
+
|
|
41
|
+
// ─── DB ───────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function openDB(projectRoot) {
|
|
44
|
+
const dbPath = path.join(projectRoot, '.agentic/memoria.db');
|
|
45
|
+
let db;
|
|
46
|
+
try { db = new (require('better-sqlite3'))(dbPath); }
|
|
47
|
+
catch { try { const { DatabaseSync } = require('node:sqlite'); db = new DatabaseSync(dbPath); } catch { return null; } }
|
|
48
|
+
|
|
49
|
+
// Migrar schema para campos de validación
|
|
50
|
+
try {
|
|
51
|
+
db.exec(`ALTER TABLE nodos ADD COLUMN hash_contexto TEXT`);
|
|
52
|
+
} catch {}
|
|
53
|
+
try {
|
|
54
|
+
db.exec(`ALTER TABLE nodos ADD COLUMN ultima_validacion TEXT`);
|
|
55
|
+
} catch {}
|
|
56
|
+
try {
|
|
57
|
+
db.exec(`ALTER TABLE nodos ADD COLUMN archivos_aplica TEXT DEFAULT '[]'`);
|
|
58
|
+
} catch {}
|
|
59
|
+
try {
|
|
60
|
+
db.exec(`ALTER TABLE nodos ADD COLUMN validation_score REAL DEFAULT 1.0`);
|
|
61
|
+
} catch {}
|
|
62
|
+
try {
|
|
63
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_nodos_vigencia ON nodos(vigencia_tipo)`);
|
|
64
|
+
} catch {}
|
|
65
|
+
|
|
66
|
+
return db;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const safe = (fn, fallback = null) => { try { return fn(); } catch { return fallback; } };
|
|
70
|
+
|
|
71
|
+
// ─── HASH DE CONTEXTO ─────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Genera hash de los archivos referenciados por una entrada.
|
|
74
|
+
* Si los archivos cambian, el hash cambia → SOSPECHOSO.
|
|
75
|
+
*/
|
|
76
|
+
function computeContextHash(archivosAplica, projectRoot) {
|
|
77
|
+
if (!archivosAplica || archivosAplica.length === 0) return null;
|
|
78
|
+
|
|
79
|
+
const hasher = crypto.createHash('sha256');
|
|
80
|
+
let anyFound = false;
|
|
81
|
+
|
|
82
|
+
archivosAplica.forEach(filePath => {
|
|
83
|
+
const fullPath = path.isAbsolute(filePath)
|
|
84
|
+
? filePath
|
|
85
|
+
: path.join(projectRoot, filePath);
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(fullPath)) {
|
|
88
|
+
try {
|
|
89
|
+
const stat = fs.statSync(fullPath);
|
|
90
|
+
hasher.update(`${filePath}:${stat.size}:${stat.mtimeMs}`);
|
|
91
|
+
anyFound = true;
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return anyFound ? hasher.digest('hex').substring(0, 16) : null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── TEMPORAL DECAY ──────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
function computeDecay(ultimaValidacion, fechaCreacion) {
|
|
102
|
+
const dateStr = ultimaValidacion || fechaCreacion;
|
|
103
|
+
if (!dateStr) return 0.5;
|
|
104
|
+
const deltaDays = (Date.now() - new Date(dateStr).getTime()) / (1000 * 60 * 60 * 24);
|
|
105
|
+
return Math.exp(-DECAY_LAMBDA * deltaDays);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── DETECTAR POSIBLE MEMORY POISONING ───────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* Detecta entradas sospechosas por similitud extrema con otras entradas.
|
|
111
|
+
* Similitud > 95% Jaccard entre entradas de la misma área = posible inyección.
|
|
112
|
+
* Patrón MINJA/MemoryGraft: inyección de variantes de entradas existentes.
|
|
113
|
+
*/
|
|
114
|
+
function detectPoisoning(db) {
|
|
115
|
+
const suspicious = [];
|
|
116
|
+
|
|
117
|
+
const nodes = safe(() =>
|
|
118
|
+
db.prepare(`
|
|
119
|
+
SELECT id, titulo, contenido, area, tipo, fecha_creacion
|
|
120
|
+
FROM nodos
|
|
121
|
+
WHERE estado = 'ACTIVO' AND tipo IN ('patron','error','decision')
|
|
122
|
+
ORDER BY area, tipo
|
|
123
|
+
`).all()
|
|
124
|
+
) || [];
|
|
125
|
+
|
|
126
|
+
const jaccardSim = (a, b) => {
|
|
127
|
+
const setA = new Set((a || '').toLowerCase().split(/\W+/).filter(Boolean));
|
|
128
|
+
const setB = new Set((b || '').toLowerCase().split(/\W+/).filter(Boolean));
|
|
129
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
130
|
+
const inter = new Set([...setA].filter(x => setB.has(x)));
|
|
131
|
+
const union = new Set([...setA, ...setB]);
|
|
132
|
+
return inter.size / union.size;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Comparar por área y tipo para reducir complejidad
|
|
136
|
+
const byAreaType = {};
|
|
137
|
+
nodes.forEach(n => {
|
|
138
|
+
const key = `${n.area}:${n.tipo}`;
|
|
139
|
+
if (!byAreaType[key]) byAreaType[key] = [];
|
|
140
|
+
byAreaType[key].push(n);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
Object.values(byAreaType).forEach(group => {
|
|
144
|
+
for (let i = 0; i < group.length; i++) {
|
|
145
|
+
for (let j = i + 1; j < group.length; j++) {
|
|
146
|
+
const textA = `${group[i].titulo} ${group[i].contenido}`;
|
|
147
|
+
const textB = `${group[j].titulo} ${group[j].contenido}`;
|
|
148
|
+
const sim = jaccardSim(textA, textB);
|
|
149
|
+
|
|
150
|
+
if (sim >= POISON_SIMILARITY) {
|
|
151
|
+
// La más reciente es la sospechosa
|
|
152
|
+
const newer = new Date(group[i].fecha_creacion) > new Date(group[j].fecha_creacion)
|
|
153
|
+
? group[i] : group[j];
|
|
154
|
+
suspicious.push({
|
|
155
|
+
id: newer.id,
|
|
156
|
+
similarity: Math.round(sim * 100),
|
|
157
|
+
reason: `Near-duplicate of existing entry (${Math.round(sim*100)}% similarity) — possible memory injection`,
|
|
158
|
+
compared_to: newer === group[i] ? group[j].id : group[i].id,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return suspicious;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── VALIDAR UNA ENTRADA ─────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
function validateEntry(db, nodeId, projectRoot) {
|
|
171
|
+
const node = safe(() =>
|
|
172
|
+
db.prepare(`
|
|
173
|
+
SELECT id, titulo, contenido, area, tipo, confianza, vigencia_tipo,
|
|
174
|
+
hash_contexto, ultima_validacion, archivos_aplica, fecha_creacion, fecha_update
|
|
175
|
+
FROM nodos WHERE id = ?
|
|
176
|
+
`).get(nodeId)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!node) return { ok: false, reason: 'not_found' };
|
|
180
|
+
|
|
181
|
+
const result = {
|
|
182
|
+
id: node.id,
|
|
183
|
+
titulo: node.titulo,
|
|
184
|
+
area: node.area,
|
|
185
|
+
tipo: node.tipo,
|
|
186
|
+
status: 'ACTIVO',
|
|
187
|
+
decay: 0,
|
|
188
|
+
issues: [],
|
|
189
|
+
recommendation:'',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// 1. Temporal decay
|
|
193
|
+
result.decay = computeDecay(node.ultima_validacion, node.fecha_creacion);
|
|
194
|
+
|
|
195
|
+
const daysSinceValidation = node.ultima_validacion
|
|
196
|
+
? (Date.now() - new Date(node.ultima_validacion).getTime()) / (1000 * 60 * 60 * 24)
|
|
197
|
+
: (Date.now() - new Date(node.fecha_creacion).getTime()) / (1000 * 60 * 60 * 24);
|
|
198
|
+
|
|
199
|
+
// 2. Verificar hash de archivos referenciados
|
|
200
|
+
let archivos = [];
|
|
201
|
+
try { archivos = JSON.parse(node.archivos_aplica || '[]'); } catch {}
|
|
202
|
+
|
|
203
|
+
if (archivos.length > 0) {
|
|
204
|
+
const currentHash = computeContextHash(archivos, projectRoot);
|
|
205
|
+
if (currentHash && node.hash_contexto && currentHash !== node.hash_contexto) {
|
|
206
|
+
result.issues.push({
|
|
207
|
+
type: 'stale_context',
|
|
208
|
+
message: `Referenced files changed since last validation. Hash: ${node.hash_contexto} → ${currentHash}`,
|
|
209
|
+
files: archivos,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 3. Determinar estado
|
|
215
|
+
if (daysSinceValidation > OBSOLETE_DAYS && result.decay < OBSOLETE_DECAY) {
|
|
216
|
+
result.status = 'OBSOLETO';
|
|
217
|
+
result.issues.push({ type: 'obsolete', message: `Not validated in ${Math.round(daysSinceValidation)} days and decay < 10%` });
|
|
218
|
+
} else if (daysSinceValidation > SUSPECT_DAYS || result.issues.some(i => i.type === 'stale_context')) {
|
|
219
|
+
result.status = 'SOSPECHOSO';
|
|
220
|
+
if (daysSinceValidation > SUSPECT_DAYS) {
|
|
221
|
+
result.issues.push({ type: 'stale', message: `Not validated in ${Math.round(daysSinceValidation)} days` });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 4. Score de validación (0-1)
|
|
226
|
+
const issuesPenalty = result.issues.reduce((p, issue) => {
|
|
227
|
+
return p * (issue.type === 'stale_context' ? 0.6 : issue.type === 'obsolete' ? 0.2 : 0.8);
|
|
228
|
+
}, 1.0);
|
|
229
|
+
|
|
230
|
+
result.validation_score = Math.max(0.05, result.decay * issuesPenalty);
|
|
231
|
+
|
|
232
|
+
// 5. Recomendación
|
|
233
|
+
result.recommendation = result.status === 'ACTIVO' ? 'Apply normally'
|
|
234
|
+
: result.status === 'SOSPECHOSO' ? 'Verify before applying — context may have changed'
|
|
235
|
+
: 'Do not apply — revalidate manually or mark as HISTORICO';
|
|
236
|
+
|
|
237
|
+
// 6. Actualizar estado en DB si cambió
|
|
238
|
+
if (result.status !== 'ACTIVO' && node.vigencia_tipo !== result.status) {
|
|
239
|
+
safe(() => db.prepare(`
|
|
240
|
+
UPDATE nodos SET
|
|
241
|
+
vigencia_tipo = ?,
|
|
242
|
+
validation_score = ?,
|
|
243
|
+
fecha_update = datetime('now')
|
|
244
|
+
WHERE id = ?
|
|
245
|
+
`).run(result.status, result.validation_score, nodeId));
|
|
246
|
+
} else {
|
|
247
|
+
safe(() => db.prepare(`UPDATE nodos SET validation_score = ? WHERE id = ?`)
|
|
248
|
+
.run(result.validation_score, nodeId));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── SCAN COMPLETO ────────────────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
function scanAll(projectRoot) {
|
|
257
|
+
projectRoot = projectRoot || process.cwd();
|
|
258
|
+
const db = openDB(projectRoot);
|
|
259
|
+
if (!db) return { error: 'DB unavailable' };
|
|
260
|
+
|
|
261
|
+
const nodes = safe(() =>
|
|
262
|
+
db.prepare(`
|
|
263
|
+
SELECT id FROM nodos
|
|
264
|
+
WHERE estado = 'ACTIVO'
|
|
265
|
+
AND tipo IN ('patron','error','decision','regla')
|
|
266
|
+
ORDER BY fecha_update ASC
|
|
267
|
+
LIMIT 500
|
|
268
|
+
`).all()
|
|
269
|
+
) || [];
|
|
270
|
+
|
|
271
|
+
const results = { total: nodes.length, activo: 0, sospechoso: 0, obsoleto: 0, poison_candidates: 0 };
|
|
272
|
+
|
|
273
|
+
nodes.forEach(n => {
|
|
274
|
+
const r = validateEntry(db, n.id, projectRoot);
|
|
275
|
+
if (r.status === 'ACTIVO') results.activo++;
|
|
276
|
+
else if (r.status === 'SOSPECHOSO') results.sospechoso++;
|
|
277
|
+
else if (r.status === 'OBSOLETO') results.obsoleto++;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Detectar posible memory poisoning
|
|
281
|
+
const poisonCandidates = detectPoisoning(db);
|
|
282
|
+
results.poison_candidates = poisonCandidates.length;
|
|
283
|
+
|
|
284
|
+
// Marcar candidatos sospechosos de poisoning
|
|
285
|
+
poisonCandidates.forEach(p => {
|
|
286
|
+
safe(() => db.prepare(`
|
|
287
|
+
UPDATE nodos SET vigencia_tipo = 'SOSPECHOSO', fecha_update = datetime('now')
|
|
288
|
+
WHERE id = ? AND vigencia_tipo = 'VIGENTE'
|
|
289
|
+
`).run(p.id));
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
results.poison_suspects = poisonCandidates.slice(0, 5);
|
|
293
|
+
db.close();
|
|
294
|
+
return results;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── VALIDATE_KNOWLEDGE — MCP TOOL ───────────────────────────────────────────
|
|
298
|
+
/**
|
|
299
|
+
* El agente Analista llama esto antes de aplicar un patrón de memoria.
|
|
300
|
+
* Retorna si el patrón es confiable o debe ser revisado.
|
|
301
|
+
*/
|
|
302
|
+
function validateKnowledge(nodeId, projectRoot) {
|
|
303
|
+
projectRoot = projectRoot || process.cwd();
|
|
304
|
+
const db = openDB(projectRoot);
|
|
305
|
+
if (!db) return { trusted: true, reason: 'DB unavailable — proceeding' };
|
|
306
|
+
|
|
307
|
+
const result = validateEntry(db, nodeId, projectRoot);
|
|
308
|
+
db.close();
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
trusted: result.status === 'ACTIVO',
|
|
312
|
+
status: result.status,
|
|
313
|
+
validation_score: result.validation_score,
|
|
314
|
+
decay: Math.round(result.decay * 100) / 100,
|
|
315
|
+
issues: result.issues,
|
|
316
|
+
recommendation: result.recommendation,
|
|
317
|
+
apply: result.status !== 'OBSOLETO',
|
|
318
|
+
warn: result.status === 'SOSPECHOSO',
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ─── REVALIDAR ────────────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
function revalidate(nodeId, projectRoot) {
|
|
325
|
+
projectRoot = projectRoot || process.cwd();
|
|
326
|
+
const db = openDB(projectRoot);
|
|
327
|
+
if (!db) return { ok: false };
|
|
328
|
+
|
|
329
|
+
const archivos = safe(() => {
|
|
330
|
+
const n = db.prepare("SELECT archivos_aplica FROM nodos WHERE id = ?").get(nodeId);
|
|
331
|
+
return JSON.parse(n?.archivos_aplica || '[]');
|
|
332
|
+
}) || [];
|
|
333
|
+
|
|
334
|
+
const newHash = computeContextHash(archivos, projectRoot);
|
|
335
|
+
|
|
336
|
+
safe(() => db.prepare(`
|
|
337
|
+
UPDATE nodos SET
|
|
338
|
+
vigencia_tipo = 'VIGENTE',
|
|
339
|
+
ultima_validacion = datetime('now'),
|
|
340
|
+
hash_contexto = ?,
|
|
341
|
+
validation_score = 1.0,
|
|
342
|
+
fecha_update = datetime('now')
|
|
343
|
+
WHERE id = ?
|
|
344
|
+
`).run(newHash, nodeId));
|
|
345
|
+
|
|
346
|
+
db.close();
|
|
347
|
+
return { ok: true, id: nodeId, new_hash: newHash, revalidated_at: new Date().toISOString() };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
if (require.main === module) {
|
|
353
|
+
const [,, cmd, arg] = process.argv;
|
|
354
|
+
const projectRoot = process.cwd();
|
|
355
|
+
|
|
356
|
+
switch (cmd) {
|
|
357
|
+
case 'scan': {
|
|
358
|
+
console.log('\n[VALIDATOR] Scanning memory...');
|
|
359
|
+
const r = scanAll(projectRoot);
|
|
360
|
+
if (r.error) { console.log(`❌ ${r.error}`); break; }
|
|
361
|
+
console.log(`\n Knowledge Validation Report`);
|
|
362
|
+
console.log(` Total scanned: ${r.total}`);
|
|
363
|
+
console.log(` ✅ ACTIVO: ${r.activo}`);
|
|
364
|
+
console.log(` ⚠️ SOSPECHOSO: ${r.sospechoso}`);
|
|
365
|
+
console.log(` ❌ OBSOLETO: ${r.obsoleto}`);
|
|
366
|
+
console.log(` 🔍 Poison suspects: ${r.poison_candidates}`);
|
|
367
|
+
if (r.poison_suspects?.length > 0) {
|
|
368
|
+
console.log('\n Possible injections:');
|
|
369
|
+
r.poison_suspects.forEach(p => console.log(` - ${p.id}: ${p.reason}`));
|
|
370
|
+
}
|
|
371
|
+
console.log('');
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
case 'validate': {
|
|
376
|
+
if (!arg) { console.log('Uso: knowledge-validator.cjs validate <node_id>'); break; }
|
|
377
|
+
const r = validateKnowledge(arg, projectRoot);
|
|
378
|
+
console.log(`\n ID: ${arg}`);
|
|
379
|
+
console.log(` Status: ${r.status} | Score: ${r.validation_score}`);
|
|
380
|
+
console.log(` Trusted: ${r.trusted} | Apply: ${r.apply}`);
|
|
381
|
+
console.log(` → ${r.recommendation}\n`);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
case 'revalidate': {
|
|
386
|
+
if (!arg) { console.log('Uso: knowledge-validator.cjs revalidate <node_id>'); break; }
|
|
387
|
+
const r = revalidate(arg, projectRoot);
|
|
388
|
+
console.log(r.ok ? `✅ Revalidated: ${arg}` : `❌ Failed`);
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
case 'report': {
|
|
393
|
+
const r = scanAll(projectRoot);
|
|
394
|
+
const health = r.total > 0 ? Math.round((r.activo / r.total) * 100) : 0;
|
|
395
|
+
console.log(`\n Memory Health: ${health}% (${r.activo}/${r.total} entries valid)`);
|
|
396
|
+
if (r.sospechoso > 0) console.log(` ⚠️ ${r.sospechoso} entries need review`);
|
|
397
|
+
if (r.obsoleto > 0) console.log(` ❌ ${r.obsoleto} entries are obsolete`);
|
|
398
|
+
if (r.poison_candidates > 0) console.log(` 🔍 ${r.poison_candidates} possible injections detected`);
|
|
399
|
+
console.log('');
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
default:
|
|
404
|
+
console.log('Uso: node knowledge-validator.cjs [scan | validate <id> | revalidate <id> | report]');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
module.exports = { validateKnowledge, validateEntry, revalidate, scanAll, detectPoisoning, computeContextHash };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-kdd",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "Autonomous development pipeline — aa: · ag: · audit: · AST graph · Harness · Specs · Impact analysis · Decision trail · Metrics · MCP server. Works with Cursor and Claude Code.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|