agentic-kdd 3.5.2 → 3.5.4
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 +81 -0
- package/contract-guard.cjs +50 -0
- package/mcp-server.cjs +970 -0
- package/package.json +1 -1
- package/regression-guard.cjs +514 -0
- package/security-gate.cjs +169 -0
- package/spec-gate.cjs +179 -0
- package/src/mcp-setup.js +1 -0
- package/tdd-gate.cjs +51 -2
package/spec-gate.cjs
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec Gate — Step 2 of the enhanced aa: pipeline
|
|
3
|
+
*
|
|
4
|
+
* Brecha 1 cerrada: antes de implementar cualquier cambio,
|
|
5
|
+
* verifica si el prompt pide modificar valores que existen
|
|
6
|
+
* en memoria como reglas de negocio HIGH confidence.
|
|
7
|
+
*
|
|
8
|
+
* Ejemplo: "cambia trial_days de 14 a 7"
|
|
9
|
+
* → Memoria tiene: "trial_days = 14 SPEC (HIGH)"
|
|
10
|
+
* → Spec Gate detecta la contradicción → STOP con explicación
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
|
|
18
|
+
const safe = (fn, fb = null) => { try { return fn(); } catch { return fb; } };
|
|
19
|
+
|
|
20
|
+
function openDB(projectRoot) {
|
|
21
|
+
const dbPath = path.join(projectRoot, '.agentic/memoria.db');
|
|
22
|
+
if (!fs.existsSync(dbPath)) return null;
|
|
23
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch { return null; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extrae valores numéricos y strings del prompt.
|
|
28
|
+
* Detecta patrones como "cambia X de 14 a 7", "set X to 7", "X = 7"
|
|
29
|
+
*/
|
|
30
|
+
function extractValuesFromPrompt(prompt) {
|
|
31
|
+
const changes = [];
|
|
32
|
+
|
|
33
|
+
// "cambia X de N a M" / "change X from N to M"
|
|
34
|
+
const fromTo = prompt.matchAll(/(?:cambia|change|update|modify)\s+(\w+)\s+(?:de|from)\s+([\d.]+)\s+(?:a|to)\s+([\d.]+)/gi);
|
|
35
|
+
for (const m of fromTo) {
|
|
36
|
+
changes.push({ field: m[1], from: m[2], to: m[3] });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// "X = N" / "X: N"
|
|
40
|
+
const assignments = prompt.matchAll(/(\w+(?:_\w+)*)\s*[=:]\s*([\d.]+)/g);
|
|
41
|
+
for (const m of assignments) {
|
|
42
|
+
changes.push({ field: m[1], value: m[2] });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Numbers in context of known business fields
|
|
46
|
+
const businessFields = [
|
|
47
|
+
'trial_days', 'trial_period', 'trial',
|
|
48
|
+
'discount', 'yearly_discount',
|
|
49
|
+
'password', 'min_password', 'password_min',
|
|
50
|
+
'invoice_prefix', 'invoice_number',
|
|
51
|
+
'max_users', 'max_api_calls', 'max_storage',
|
|
52
|
+
'rate_limit', 'timeout', 'retries',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
businessFields.forEach(field => {
|
|
56
|
+
const regex = new RegExp(`${field}[^\\d]*(\\d+)`, 'gi');
|
|
57
|
+
const matches = prompt.matchAll(regex);
|
|
58
|
+
for (const m of matches) {
|
|
59
|
+
changes.push({ field, value: m[1] });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return changes;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Verifica si el cambio pedido contradice una regla en memoria.
|
|
68
|
+
*/
|
|
69
|
+
function checkAgainstMemory(db, changes) {
|
|
70
|
+
const violations = [];
|
|
71
|
+
|
|
72
|
+
changes.forEach(change => {
|
|
73
|
+
const fieldLower = (change.field || '').toLowerCase();
|
|
74
|
+
|
|
75
|
+
// Search for HIGH/MEDIA confidence nodes related to this field
|
|
76
|
+
const nodes = safe(() =>
|
|
77
|
+
db.prepare(`
|
|
78
|
+
SELECT id, titulo, contenido, confianza, area
|
|
79
|
+
FROM nodos
|
|
80
|
+
WHERE estado = 'ACTIVO'
|
|
81
|
+
AND confianza IN ('ALTA', 'MEDIA')
|
|
82
|
+
AND (
|
|
83
|
+
titulo LIKE ? OR contenido LIKE ?
|
|
84
|
+
OR titulo LIKE ? OR contenido LIKE ?
|
|
85
|
+
)
|
|
86
|
+
LIMIT 5
|
|
87
|
+
`).all(
|
|
88
|
+
`%${fieldLower}%`, `%${fieldLower}%`,
|
|
89
|
+
`%${change.field}%`, `%${change.field}%`
|
|
90
|
+
)
|
|
91
|
+
) || [];
|
|
92
|
+
|
|
93
|
+
nodes.forEach(node => {
|
|
94
|
+
const content = (node.titulo + ' ' + node.contenido).toLowerCase();
|
|
95
|
+
|
|
96
|
+
// Check if memory contains a specific value for this field
|
|
97
|
+
const valueInMemory = content.match(new RegExp(`${fieldLower}[^\\d]*(\\d+)`, 'i'));
|
|
98
|
+
if (valueInMemory && change.to && valueInMemory[1] !== change.to) {
|
|
99
|
+
violations.push({
|
|
100
|
+
field: change.field,
|
|
101
|
+
from: change.from,
|
|
102
|
+
to: change.to,
|
|
103
|
+
memory_says: valueInMemory[1],
|
|
104
|
+
node_id: node.id,
|
|
105
|
+
confidence: node.confianza,
|
|
106
|
+
rule: node.titulo.substring(0, 80),
|
|
107
|
+
severity: node.confianza === 'ALTA' ? 'STOP' : 'WARN',
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return violations;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Main: run spec gate check against a prompt.
|
|
118
|
+
*/
|
|
119
|
+
function runSpecGate(prompt, projectRoot) {
|
|
120
|
+
projectRoot = projectRoot || process.cwd();
|
|
121
|
+
const db = openDB(projectRoot);
|
|
122
|
+
|
|
123
|
+
if (!db) return { passed: true, reason: 'No DB — skipping spec gate' };
|
|
124
|
+
|
|
125
|
+
const changes = extractValuesFromPrompt(prompt);
|
|
126
|
+
if (changes.length === 0) return { passed: true, reason: 'No value changes detected in prompt' };
|
|
127
|
+
|
|
128
|
+
const violations = checkAgainstMemory(db, changes);
|
|
129
|
+
db.close();
|
|
130
|
+
|
|
131
|
+
if (violations.length === 0) {
|
|
132
|
+
return { passed: true, changes_checked: changes.length };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const stops = violations.filter(v => v.severity === 'STOP');
|
|
136
|
+
const warns = violations.filter(v => v.severity === 'WARN');
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
passed: stops.length === 0,
|
|
140
|
+
violations,
|
|
141
|
+
stops,
|
|
142
|
+
warns,
|
|
143
|
+
message: stops.length > 0
|
|
144
|
+
? `SPEC GATE STOP: ${stops.map(v =>
|
|
145
|
+
`"${v.field}" in memory = ${v.memory_says} but prompt requests ${v.to} — contradicts ${v.confidence} confidence rule: "${v.rule}"`
|
|
146
|
+
).join('; ')}`
|
|
147
|
+
: `SPEC GATE WARN: ${warns.map(v =>
|
|
148
|
+
`"${v.field}" change may contradict memory rule: "${v.rule}"`
|
|
149
|
+
).join('; ')}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (require.main === module) {
|
|
154
|
+
const prompt = process.argv.slice(2).join(' ');
|
|
155
|
+
if (!prompt) {
|
|
156
|
+
console.log('Usage: node spec-gate.cjs "your prompt here"');
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
const result = runSpecGate(prompt, process.cwd());
|
|
160
|
+
if (!result.passed) {
|
|
161
|
+
console.log('\n🛑 SPEC GATE STOP');
|
|
162
|
+
console.log(result.message);
|
|
163
|
+
console.log('\nViolations:');
|
|
164
|
+
result.violations.forEach(v => {
|
|
165
|
+
console.log(` ${v.severity} — ${v.field}: memory says ${v.memory_says}, prompt says ${v.to}`);
|
|
166
|
+
console.log(` Rule: "${v.rule}" (${v.confidence})`);
|
|
167
|
+
});
|
|
168
|
+
process.exit(1);
|
|
169
|
+
} else if (result.warns?.length > 0) {
|
|
170
|
+
console.log('\n⚠️ SPEC GATE WARN');
|
|
171
|
+
console.log(result.message);
|
|
172
|
+
process.exit(0);
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`✅ SPEC GATE PASS (checked ${result.changes_checked || 0} value changes)`);
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = { runSpecGate, extractValuesFromPrompt };
|
package/src/mcp-setup.js
CHANGED
|
@@ -59,6 +59,7 @@ async function mcpSetup(projectPath, opts = {}) {
|
|
|
59
59
|
cursorConfig.mcpServers['agentic-kdd'] = {
|
|
60
60
|
command: 'node',
|
|
61
61
|
args: [serverPath],
|
|
62
|
+
env: { PROJECT_ROOT: path.resolve(projectPath) },
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
fs.writeFileSync(cursorMcpFile, JSON.stringify(cursorConfig, null, 2));
|
package/tdd-gate.cjs
CHANGED
|
@@ -93,11 +93,17 @@ function runTests(command, projectRoot, testFile = null) {
|
|
|
93
93
|
let exitCode = 0;
|
|
94
94
|
|
|
95
95
|
try {
|
|
96
|
+
// Windows: use cmd.exe, Unix: use sh
|
|
97
|
+
const isWin = process.platform === 'win32';
|
|
98
|
+
const shell = isWin ? 'cmd.exe' : 'sh';
|
|
99
|
+
const shellFlag = isWin ? '/c' : '-c';
|
|
100
|
+
const shellCmd = isWin ? fullCmd : fullCmd + ' 2>&1';
|
|
101
|
+
|
|
96
102
|
const result = spawnSync(
|
|
97
|
-
|
|
103
|
+
shell, [shellFlag, shellCmd],
|
|
98
104
|
{ cwd: projectRoot, timeout: 120000, stdio: 'pipe', encoding: 'utf8' }
|
|
99
105
|
);
|
|
100
|
-
output = result.stdout || '';
|
|
106
|
+
output = (result.stdout || '') + (isWin ? (result.stderr || '') : '');
|
|
101
107
|
errorOutput = result.stderr || '';
|
|
102
108
|
exitCode = result.status ?? 1;
|
|
103
109
|
} catch (err) {
|
|
@@ -116,6 +122,9 @@ function runTests(command, projectRoot, testFile = null) {
|
|
|
116
122
|
* Soporta: jest, vitest, mocha, jasmine, tap, pytest (output básico).
|
|
117
123
|
*/
|
|
118
124
|
function parseTestOutput(raw, exitCode) {
|
|
125
|
+
// Strip ANSI color codes — Vitest adds them and break regex matching
|
|
126
|
+
raw = (raw || '').replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
|
|
127
|
+
|
|
119
128
|
const result = {
|
|
120
129
|
allPassed: exitCode === 0,
|
|
121
130
|
total: 0, passed: 0, failed: 0,
|
|
@@ -341,6 +350,46 @@ function runSelfHealingLoop(opts) {
|
|
|
341
350
|
|
|
342
351
|
if (result.allPassed) {
|
|
343
352
|
console.log(`\n[TDD-GATE] ✅ PASS en iteración ${iteration}`);
|
|
353
|
+
|
|
354
|
+
// Auto-register passing tests as contract candidates
|
|
355
|
+
try {
|
|
356
|
+
const contractGuardPath = require('path').join(__dirname, 'contract-guard.cjs');
|
|
357
|
+
const cg = require(contractGuardPath);
|
|
358
|
+
const dbPath = require('path').join(projectRoot || process.cwd(), '.agentic/memoria.db');
|
|
359
|
+
const DB = (() => {
|
|
360
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch { return null; }
|
|
361
|
+
})();
|
|
362
|
+
if (DB && cg && typeof cg.registerPassingTests === 'function') {
|
|
363
|
+
cg.registerPassingTests(DB, { passed: result.passed, total: result.total, area: area || 'global', command });
|
|
364
|
+
console.log(`[TDD-GATE] 📋 Contracts: ${result.passed} tests → candidates`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Auto-register protected behavior snapshot
|
|
368
|
+
try {
|
|
369
|
+
const rgPath = require('path').join(__dirname, 'regression-guard.cjs');
|
|
370
|
+
if (require('fs').existsSync(rgPath)) {
|
|
371
|
+
const rg = require(rgPath);
|
|
372
|
+
const DB2 = (() => {
|
|
373
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch { return null; }
|
|
374
|
+
})();
|
|
375
|
+
if (DB2) {
|
|
376
|
+
const behavior = rg.registerBehavior(DB2, {
|
|
377
|
+
module: area || 'global',
|
|
378
|
+
files: [],
|
|
379
|
+
testFiles: [],
|
|
380
|
+
projectRoot: projectRoot || process.cwd(),
|
|
381
|
+
});
|
|
382
|
+
if (behavior) {
|
|
383
|
+
console.log(`[TDD-GATE] 🛡️ Behavior: [${behavior.module}] ${behavior.confidence} (cycle ${behavior.pass_count})`);
|
|
384
|
+
}
|
|
385
|
+
DB2.close();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
} catch(e) { /* regression guard optional */ }
|
|
389
|
+
|
|
390
|
+
if (DB) DB.close();
|
|
391
|
+
} catch(e) { /* contract guard optional */ }
|
|
392
|
+
|
|
344
393
|
break;
|
|
345
394
|
}
|
|
346
395
|
|