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/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
- 'sh', ['-c', fullCmd + ' 2>&1'],
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