agentic-kdd 3.5.3 → 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/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
- 'sh', ['-c', fullCmd + ' 2>&1'],
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) {
@@ -344,6 +350,46 @@ function runSelfHealingLoop(opts) {
344
350
 
345
351
  if (result.allPassed) {
346
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
+
347
393
  break;
348
394
  }
349
395