agentic-kdd 3.5.3 → 3.5.5
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 +973 -0
- package/package.json +1 -1
- package/regression-guard.cjs +528 -0
- package/security-gate.cjs +169 -0
- package/spec-gate.cjs +188 -0
- package/src/init.js +16 -0
- package/src/mcp-setup.js +1 -0
- package/tdd-gate.cjs +52 -2
package/spec-gate.cjs
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
// SaaS Billing (original)
|
|
48
|
+
'trial_days', 'trial_period', 'trial',
|
|
49
|
+
'discount', 'yearly_discount',
|
|
50
|
+
'password', 'min_password', 'password_min',
|
|
51
|
+
'invoice_prefix', 'invoice_number',
|
|
52
|
+
'max_users', 'max_api_calls', 'max_storage',
|
|
53
|
+
'rate_limit', 'timeout', 'retries',
|
|
54
|
+
// Agency OS
|
|
55
|
+
'hourly_rate', 'hourly_rate_default',
|
|
56
|
+
'overtime_threshold', 'overtime_multiplier', 'overtime',
|
|
57
|
+
'invoice_due', 'invoice_due_days', 'due_days',
|
|
58
|
+
'budget_warning', 'campaign_budget_warning',
|
|
59
|
+
'tax_rate', 'tax_rate_default',
|
|
60
|
+
'password_min_length',
|
|
61
|
+
'retainer_billing_day', 'billing_day',
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
businessFields.forEach(field => {
|
|
65
|
+
const regex = new RegExp(`${field}[^\\d]*(\\d+)`, 'gi');
|
|
66
|
+
const matches = prompt.matchAll(regex);
|
|
67
|
+
for (const m of matches) {
|
|
68
|
+
changes.push({ field, value: m[1] });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return changes;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Verifica si el cambio pedido contradice una regla en memoria.
|
|
77
|
+
*/
|
|
78
|
+
function checkAgainstMemory(db, changes) {
|
|
79
|
+
const violations = [];
|
|
80
|
+
|
|
81
|
+
changes.forEach(change => {
|
|
82
|
+
const fieldLower = (change.field || '').toLowerCase();
|
|
83
|
+
|
|
84
|
+
// Search for HIGH/MEDIA confidence nodes related to this field
|
|
85
|
+
const nodes = safe(() =>
|
|
86
|
+
db.prepare(`
|
|
87
|
+
SELECT id, titulo, contenido, confianza, area
|
|
88
|
+
FROM nodos
|
|
89
|
+
WHERE estado = 'ACTIVO'
|
|
90
|
+
AND confianza IN ('ALTA', 'MEDIA')
|
|
91
|
+
AND (
|
|
92
|
+
titulo LIKE ? OR contenido LIKE ?
|
|
93
|
+
OR titulo LIKE ? OR contenido LIKE ?
|
|
94
|
+
)
|
|
95
|
+
LIMIT 5
|
|
96
|
+
`).all(
|
|
97
|
+
`%${fieldLower}%`, `%${fieldLower}%`,
|
|
98
|
+
`%${change.field}%`, `%${change.field}%`
|
|
99
|
+
)
|
|
100
|
+
) || [];
|
|
101
|
+
|
|
102
|
+
nodes.forEach(node => {
|
|
103
|
+
const content = (node.titulo + ' ' + node.contenido).toLowerCase();
|
|
104
|
+
|
|
105
|
+
// Check if memory contains a specific value for this field
|
|
106
|
+
const valueInMemory = content.match(new RegExp(`${fieldLower}[^\\d]*(\\d+)`, 'i'));
|
|
107
|
+
if (valueInMemory && change.to && valueInMemory[1] !== change.to) {
|
|
108
|
+
violations.push({
|
|
109
|
+
field: change.field,
|
|
110
|
+
from: change.from,
|
|
111
|
+
to: change.to,
|
|
112
|
+
memory_says: valueInMemory[1],
|
|
113
|
+
node_id: node.id,
|
|
114
|
+
confidence: node.confianza,
|
|
115
|
+
rule: node.titulo.substring(0, 80),
|
|
116
|
+
severity: node.confianza === 'ALTA' ? 'STOP' : 'WARN',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return violations;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Main: run spec gate check against a prompt.
|
|
127
|
+
*/
|
|
128
|
+
function runSpecGate(prompt, projectRoot) {
|
|
129
|
+
projectRoot = projectRoot || process.cwd();
|
|
130
|
+
const db = openDB(projectRoot);
|
|
131
|
+
|
|
132
|
+
if (!db) return { passed: true, reason: 'No DB — skipping spec gate' };
|
|
133
|
+
|
|
134
|
+
const changes = extractValuesFromPrompt(prompt);
|
|
135
|
+
if (changes.length === 0) return { passed: true, reason: 'No value changes detected in prompt' };
|
|
136
|
+
|
|
137
|
+
const violations = checkAgainstMemory(db, changes);
|
|
138
|
+
db.close();
|
|
139
|
+
|
|
140
|
+
if (violations.length === 0) {
|
|
141
|
+
return { passed: true, changes_checked: changes.length };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const stops = violations.filter(v => v.severity === 'STOP');
|
|
145
|
+
const warns = violations.filter(v => v.severity === 'WARN');
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
passed: stops.length === 0,
|
|
149
|
+
violations,
|
|
150
|
+
stops,
|
|
151
|
+
warns,
|
|
152
|
+
message: stops.length > 0
|
|
153
|
+
? `SPEC GATE STOP: ${stops.map(v =>
|
|
154
|
+
`"${v.field}" in memory = ${v.memory_says} but prompt requests ${v.to} — contradicts ${v.confidence} confidence rule: "${v.rule}"`
|
|
155
|
+
).join('; ')}`
|
|
156
|
+
: `SPEC GATE WARN: ${warns.map(v =>
|
|
157
|
+
`"${v.field}" change may contradict memory rule: "${v.rule}"`
|
|
158
|
+
).join('; ')}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (require.main === module) {
|
|
163
|
+
const prompt = process.argv.slice(2).join(' ');
|
|
164
|
+
if (!prompt) {
|
|
165
|
+
console.log('Usage: node spec-gate.cjs "your prompt here"');
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
const result = runSpecGate(prompt, process.cwd());
|
|
169
|
+
if (!result.passed) {
|
|
170
|
+
console.log('\n🛑 SPEC GATE STOP');
|
|
171
|
+
console.log(result.message);
|
|
172
|
+
console.log('\nViolations:');
|
|
173
|
+
result.violations.forEach(v => {
|
|
174
|
+
console.log(` ${v.severity} — ${v.field}: memory says ${v.memory_says}, prompt says ${v.to}`);
|
|
175
|
+
console.log(` Rule: "${v.rule}" (${v.confidence})`);
|
|
176
|
+
});
|
|
177
|
+
process.exit(1);
|
|
178
|
+
} else if (result.warns?.length > 0) {
|
|
179
|
+
console.log('\n⚠️ SPEC GATE WARN');
|
|
180
|
+
console.log(result.message);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(`✅ SPEC GATE PASS (checked ${result.changes_checked || 0} value changes)`);
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = { runSpecGate, extractValuesFromPrompt };
|
package/src/init.js
CHANGED
|
@@ -222,6 +222,22 @@ async function init() {
|
|
|
222
222
|
process.exit(1);
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
// ── Agregar dev:kdd al package.json si es proyecto Node ────
|
|
226
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
227
|
+
if (fs.existsSync(pkgPath) && stack.language !== 'Python') {
|
|
228
|
+
try {
|
|
229
|
+
const pkg = fs.readJsonSync(pkgPath, { throws: false }) || {};
|
|
230
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
231
|
+
if (!pkg.scripts['dev:kdd']) {
|
|
232
|
+
pkg.scripts['dev:kdd'] = 'node .agentic/grafo/watch-errors.cjs';
|
|
233
|
+
fs.writeJsonSync(pkgPath, pkg, { spaces: 2 });
|
|
234
|
+
console.log(chalk.green(' ✓ Script dev:kdd agregado al package.json'));
|
|
235
|
+
}
|
|
236
|
+
} catch(e) {
|
|
237
|
+
console.log(chalk.yellow(' ⚠ No se pudo agregar dev:kdd al package.json — agrégalo manualmente'));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
225
241
|
// ── PREGUNTA 3: Docs (DESPUÉS de crear carpetas) ───────────
|
|
226
242
|
const { hasDocs } = await inquirer.prompt([{
|
|
227
243
|
type: 'confirm', name: 'hasDocs',
|
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
|
@@ -32,6 +32,10 @@ const TEST_COMMANDS = [
|
|
|
32
32
|
'npx jest --passWithNoTests',
|
|
33
33
|
'npx vitest run',
|
|
34
34
|
'npx jest',
|
|
35
|
+
// Python projects
|
|
36
|
+
'pytest',
|
|
37
|
+
'python -m pytest',
|
|
38
|
+
'python3 -m pytest',
|
|
35
39
|
];
|
|
36
40
|
|
|
37
41
|
// ─── TEST RUNNER ──────────────────────────────────────────────────────────────
|
|
@@ -93,11 +97,17 @@ function runTests(command, projectRoot, testFile = null) {
|
|
|
93
97
|
let exitCode = 0;
|
|
94
98
|
|
|
95
99
|
try {
|
|
100
|
+
// Windows: use cmd.exe, Unix: use sh
|
|
101
|
+
const isWin = process.platform === 'win32';
|
|
102
|
+
const shell = isWin ? 'cmd.exe' : 'sh';
|
|
103
|
+
const shellFlag = isWin ? '/c' : '-c';
|
|
104
|
+
const shellCmd = isWin ? fullCmd : fullCmd + ' 2>&1';
|
|
105
|
+
|
|
96
106
|
const result = spawnSync(
|
|
97
|
-
|
|
107
|
+
shell, [shellFlag, shellCmd],
|
|
98
108
|
{ cwd: projectRoot, timeout: 120000, stdio: 'pipe', encoding: 'utf8' }
|
|
99
109
|
);
|
|
100
|
-
output = result.stdout || '';
|
|
110
|
+
output = (result.stdout || '') + (isWin ? (result.stderr || '') : '');
|
|
101
111
|
errorOutput = result.stderr || '';
|
|
102
112
|
exitCode = result.status ?? 1;
|
|
103
113
|
} catch (err) {
|
|
@@ -344,6 +354,46 @@ function runSelfHealingLoop(opts) {
|
|
|
344
354
|
|
|
345
355
|
if (result.allPassed) {
|
|
346
356
|
console.log(`\n[TDD-GATE] ✅ PASS en iteración ${iteration}`);
|
|
357
|
+
|
|
358
|
+
// Auto-register passing tests as contract candidates
|
|
359
|
+
try {
|
|
360
|
+
const contractGuardPath = require('path').join(__dirname, 'contract-guard.cjs');
|
|
361
|
+
const cg = require(contractGuardPath);
|
|
362
|
+
const dbPath = require('path').join(projectRoot || process.cwd(), '.agentic/memoria.db');
|
|
363
|
+
const DB = (() => {
|
|
364
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch { return null; }
|
|
365
|
+
})();
|
|
366
|
+
if (DB && cg && typeof cg.registerPassingTests === 'function') {
|
|
367
|
+
cg.registerPassingTests(DB, { passed: result.passed, total: result.total, area: area || 'global', command });
|
|
368
|
+
console.log(`[TDD-GATE] 📋 Contracts: ${result.passed} tests → candidates`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Auto-register protected behavior snapshot
|
|
372
|
+
try {
|
|
373
|
+
const rgPath = require('path').join(__dirname, 'regression-guard.cjs');
|
|
374
|
+
if (require('fs').existsSync(rgPath)) {
|
|
375
|
+
const rg = require(rgPath);
|
|
376
|
+
const DB2 = (() => {
|
|
377
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch { return null; }
|
|
378
|
+
})();
|
|
379
|
+
if (DB2) {
|
|
380
|
+
const behavior = rg.registerBehavior(DB2, {
|
|
381
|
+
module: area || 'global',
|
|
382
|
+
files: [],
|
|
383
|
+
testFiles: [],
|
|
384
|
+
projectRoot: projectRoot || process.cwd(),
|
|
385
|
+
});
|
|
386
|
+
if (behavior) {
|
|
387
|
+
console.log(`[TDD-GATE] 🛡️ Behavior: [${behavior.module}] ${behavior.confidence} (cycle ${behavior.pass_count})`);
|
|
388
|
+
}
|
|
389
|
+
DB2.close();
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch(e) { /* regression guard optional */ }
|
|
393
|
+
|
|
394
|
+
if (DB) DB.close();
|
|
395
|
+
} catch(e) { /* contract guard optional */ }
|
|
396
|
+
|
|
347
397
|
break;
|
|
348
398
|
}
|
|
349
399
|
|