agentic-kdd 3.1.1 → 3.2.2

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.
@@ -9,13 +9,26 @@ const { execSync } = require('child_process');
9
9
  const PORT = 3847;
10
10
  const projectPath = process.cwd();
11
11
  const dbPath = path.join(projectPath, '.agentic', 'memoria.db');
12
- const grafoPath = path.join(projectPath, '.agentic', 'grafo', 'grafo.js');
12
+ const grafoPath = fs.existsSync(path.join(projectPath, '.agentic', 'grafo', 'grafo.cjs'))
13
+ ? path.join(projectPath, '.agentic', 'grafo', 'grafo.cjs')
14
+ : path.join(projectPath, '.agentic', 'grafo', 'grafo.js');
13
15
  const configPath = path.join(projectPath, '.agentic', 'config.md');
14
16
  const memoriaPath = path.join(projectPath, '.agentic', 'memoria');
15
17
 
16
18
  if (!fs.existsSync(configPath)) { console.log('\n Agentic KDD not installed.\n'); process.exit(1); }
17
19
  if (fs.existsSync(grafoPath)) { try { process.stdout.write(' Syncing... '); execSync(`node "${grafoPath}" sync`, { stdio: 'pipe', cwd: projectPath }); console.log('✓'); } catch {} }
18
20
 
21
+ function escHtml(str) {
22
+ if (!str) return '';
23
+ return String(str)
24
+ .replace(/&/g, '&')
25
+ .replace(/</g, '&lt;')
26
+ .replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;')
28
+ .replace(/`/g, '&#96;')
29
+ .replace(/\$/g, '&#36;');
30
+ }
31
+
19
32
  function readConfig() {
20
33
  try {
21
34
  const c = fs.readFileSync(configPath, 'utf8');
@@ -274,6 +287,69 @@ function getGraphData() {
274
287
  } catch { return { nodes: [], edges: [], ciclos: [], fases: [] }; }
275
288
  }
276
289
 
290
+
291
+ // ─── v3.3: CONTRACT GUARD DATA ────────────────────────────────────────────────
292
+ function getContractData() {
293
+ try {
294
+ if (!fs.existsSync(dbPath)) return { total:0, protected:0, verified:0, candidate:0, violations:0, recent:[] };
295
+ const BS3 = require('better-sqlite3');
296
+ const _db = new BS3(dbPath, { readonly: true });
297
+ let result = { total:0, protected:0, verified:0, candidate:0, invalidated:0, violations:0, recent:[] };
298
+ try {
299
+ result.total = _db.prepare("SELECT COUNT(*) as n FROM verified_contracts").get()?.n || 0;
300
+ result.protected = _db.prepare("SELECT COUNT(*) as n FROM verified_contracts WHERE status='protected'").get()?.n || 0;
301
+ result.verified = _db.prepare("SELECT COUNT(*) as n FROM verified_contracts WHERE status='verified'").get()?.n || 0;
302
+ result.candidate = _db.prepare("SELECT COUNT(*) as n FROM verified_contracts WHERE status='candidate'").get()?.n || 0;
303
+ result.invalidated= _db.prepare("SELECT COUNT(*) as n FROM verified_contracts WHERE status='invalidated'").get()?.n || 0;
304
+ result.violations= _db.prepare("SELECT COUNT(*) as n FROM contract_violations WHERE recovered=0").get()?.n || 0;
305
+ result.recent = _db.prepare("SELECT id, module, name, status, verification_count, failure_count FROM verified_contracts ORDER BY updated_at DESC LIMIT 8").all();
306
+ } catch {}
307
+ _db.close();
308
+ return result;
309
+ } catch { return { total:0, protected:0, verified:0, candidate:0, violations:0, recent:[] }; }
310
+ }
311
+
312
+ // ─── v3.3: CREATIVE ENGINE DATA ───────────────────────────────────────────────
313
+ function getCreativeData() {
314
+ try {
315
+ if (!fs.existsSync(dbPath)) return { level:1, suggestions:0, wins:0, auto_applicable:0, recent_suggestions:[] };
316
+ const BS3 = require('better-sqlite3');
317
+ const _db = new BS3(dbPath, { readonly: true });
318
+ let result = { level:1, suggestions:0, wins:0, auto_applicable:0, recent_suggestions:[] };
319
+ try {
320
+ result.suggestions = _db.prepare("SELECT COUNT(*) as n FROM creative_suggestions WHERE applied=0 AND dismissed=0").get()?.n || 0;
321
+ result.wins = _db.prepare("SELECT COUNT(*) as n FROM creative_wins").get()?.n || 0;
322
+ result.auto_applicable = _db.prepare("SELECT COUNT(*) as n FROM creative_suggestions WHERE auto_applicable=1 AND applied=0 AND dismissed=0").get()?.n || 0;
323
+ // Determine level from protected contracts
324
+ const protected_count = _db.prepare("SELECT COUNT(*) as n FROM verified_contracts WHERE status IN ('protected','verified')").get()?.n || 0;
325
+ result.level = protected_count >= 10 ? 2 : 1;
326
+ result.protected_for_level2 = protected_count;
327
+ result.recent_suggestions = _db.prepare("SELECT id, type, title, risk_level, module, auto_applicable FROM creative_suggestions WHERE applied=0 AND dismissed=0 ORDER BY created_at DESC LIMIT 5").all();
328
+ } catch {}
329
+ _db.close();
330
+ return result;
331
+ } catch { return { level:1, suggestions:0, wins:0, auto_applicable:0, recent_suggestions:[] }; }
332
+ }
333
+
334
+ // ─── v3.3: MEM CURATOR DATA ───────────────────────────────────────────────────
335
+ function getCuratorData() {
336
+ try {
337
+ const logPath = path.join(projectPath, '.agentic', 'curator.log');
338
+ let lastRun = 'nunca', actions = 0;
339
+ if (fs.existsSync(logPath)) {
340
+ const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n').filter(Boolean);
341
+ if (lines.length > 0) {
342
+ const last = lines[lines.length-1];
343
+ const match = last.match(/\[([^\]]+)\]/);
344
+ if (match) lastRun = match[1].split('T')[0];
345
+ actions = lines.filter(l => l.includes('mergeados') || l.includes('comprimidos') || l.includes('resueltos')).length;
346
+ }
347
+ }
348
+ return { lastRun, actions };
349
+ } catch { return { lastRun: 'nunca', actions: 0 }; }
350
+ }
351
+
352
+
277
353
  const { nodes, edges, ciclos: ciclosDB, fases: fasesDB } = getGraphData();
278
354
 
279
355
  // Calcular grado de conexiones por nodo (como Graphify — nodos divinos)
@@ -324,6 +400,9 @@ function parseModulos(text) {
324
400
  return results;
325
401
  }
326
402
 
403
+ const contractData = getContractData();
404
+ const creativeData = getCreativeData();
405
+ const curatorData = getCuratorData();
327
406
  const modulosImpl = parseModulos(config.implementados);
328
407
  const specsData = readSpecs();
329
408
  const logsData = readLogs();
@@ -526,7 +605,12 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
526
605
  .graph-legend{position:absolute;top:10px;left:10px;background:rgba(17,21,32,.9);border:1px solid var(--border);border-radius:8px;padding:8px 12px;display:flex;gap:10px;backdrop-filter:blur(4px)}
527
606
  .lg-item{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--text2)}
528
607
  .lg-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
529
- .graph-controls{position:absolute;bottom:12px;left:12px;display:flex;gap:6px}
608
+ .graph-controls{position:absolute;bottom:12px;left:12px;display:flex;gap:6px;flex-wrap:wrap;align-items:center;max-width:480px}
609
+ .gc-slider-wrap{display:flex;align-items:center;gap:4px;background:rgba(17,21,32,.9);border:1px solid var(--border);border-radius:6px;padding:3px 8px}
610
+ .gc-slider-label{font-size:13px;color:var(--text2)}
611
+ .gc-slider{-webkit-appearance:none;width:80px;height:3px;border-radius:2px;background:var(--border);outline:none;cursor:pointer}
612
+ .gc-slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:#8b5cf6;cursor:pointer}
613
+ .node-pinned{stroke:#ffffff !important;stroke-width:2px !important;stroke-dasharray:3,2}
530
614
  .gc-btn{background:rgba(17,21,32,.9);border:1px solid var(--border);color:var(--text2);border-radius:6px;padding:5px 9px;font-size:10px;cursor:pointer;backdrop-filter:blur(4px)}
531
615
  .gc-btn:hover{border-color:var(--purple);color:var(--pl)}
532
616
 
@@ -551,7 +635,7 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
551
635
 
552
636
  /* ════════ PROJECT DOCS MODE ════════ */
553
637
  #mode-docs{flex:1;display:none;overflow:hidden}
554
- .docs-layout{display:flex;height:100%;overflow:hidden}
638
+ .docs-layout{display:flex;height:100%;width:100%;overflow:hidden;flex:1;min-width:0}
555
639
  .docs-nav{width:210px;flex-shrink:0;background:var(--bg2);border-right:1px solid var(--border);overflow-y:auto;padding:12px}
556
640
  .docs-nav::-webkit-scrollbar{width:3px}
557
641
  .docs-nav::-webkit-scrollbar-thumb{background:var(--border)}
@@ -561,7 +645,7 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
561
645
  .nav-item:hover{background:var(--bg3);color:var(--text)}
562
646
  .nav-item.active{background:rgba(139,92,246,.12);color:var(--pl);border-left:2px solid var(--purple);padding-left:6px}
563
647
  .nav-count{font-size:10px;color:var(--text3);margin-left:auto;background:var(--bg3);border-radius:10px;padding:1px 5px}
564
- .docs-main{flex:1;overflow-y:auto;padding:24px 28px}
648
+ .docs-main{flex:1;min-width:0;overflow-y:auto;padding:24px 28px}
565
649
  .docs-main::-webkit-scrollbar{width:4px}
566
650
  .docs-main::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
567
651
  .docs-section{display:none}.docs-section.active{display:block}#doc-modules.active{display:flex}
@@ -766,6 +850,13 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
766
850
  <button class="gc-btn" onclick="resetGraph()" data-i="btn_reset">⟳ Reset</button>
767
851
  <button class="gc-btn" onclick="centerGraph()" data-i="btn_center">⊙ Center</button>
768
852
  <button class="gc-btn" onclick="toggleLabels()" id="label-btn" data-i="btn_labels">Labels OFF</button>
853
+ <button class="gc-btn" onclick="spreadGraph()" title="Spread nodes apart">⊹ Spread</button>
854
+ <button class="gc-btn" onclick="releaseAll()" title="Release all pinned nodes">⊠ Unpin all</button>
855
+ <div class="gc-slider-wrap" title="Node repulsion">
856
+ <span class="gc-slider-label">⊷</span>
857
+ <input type="range" class="gc-slider" id="repulsion-slider" min="50" max="800" value="320"
858
+ oninput="setRepulsion(this.value)" title="Repulsion force">
859
+ </div>
769
860
  </div>
770
861
  <div class="detail-panel" id="detail-panel">
771
862
  <div class="dp-header">
@@ -925,13 +1016,13 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
925
1016
  <div class="docs-section" id="doc-patterns">
926
1017
  <div class="docs-h1">Patterns</div>
927
1018
  <div class="docs-sub">Rules the system learned from this project. HIGH = permanent rule applied automatically.</div>
928
- ${patrones.length ? patrones.sort((a,b) => {const w={ALTA:3,MEDIA:2,BAJA:1}; return (w[b.confianza]||0)-(w[a.confianza]||0);}).map(p => {
1019
+ ${patrones.length ? patrones.filter(p => p.titulo && p.titulo !== 'Nombre del patrón' && p.titulo.length > 5).sort((a,b) => {const w={ALTA:3,MEDIA:2,BAJA:1}; return (w[b.confianza]||0)-(w[a.confianza]||0);}).map(p => {
929
1020
  const maxUse = Math.max(...patrones.map(x => x.aplicado), 1);
930
1021
  return `<div class="pattern-card ${p.confianza==='ALTA'?'high':''}">
931
1022
  <div class="pc-top">
932
- <div class="pc-title">${p.titulo}</div>
933
- <span class="mb c${p.confianza}">${p.confianza}</span>
934
- <span class="ab">${p.area}</span>
1023
+ <div class="pc-title">${escHtml(p.titulo)}</div>
1024
+ <span class="mb c${p.confianza}">${escHtml(p.confianza)}</span>
1025
+ <span class="ab">${escHtml(p.area)}</span>
935
1026
  </div>
936
1027
  ${p.aplicado > 0 ? `<div style="font-size:10px;color:var(--text3);margin-bottom:4px">Applied ${p.aplicado} times · ${p.util} useful</div><div class="usage-bar"><div class="usage-fill" style="width:${Math.round(p.aplicado/maxUse*100)}%"></div></div>` : ''}
937
1028
  </div>`;
@@ -949,8 +1040,8 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
949
1040
  <div class="docs-section" id="doc-errors">
950
1041
  <div class="docs-h1">Known Error Patterns</div>
951
1042
  <div class="docs-sub">Errors the system has already learned to avoid automatically.</div>
952
- ${errores.length ? errores.sort((a,b)=>b.aplicado-a.aplicado).map(e => `<div class="pattern-card" style="border-left:3px solid var(--red)">
953
- <div class="pc-top"><div class="pc-title">${e.titulo}</div><span class="mb c${e.confianza}">${e.confianza}</span><span class="ab">${e.area}</span></div>
1043
+ ${errores.length ? errores.filter(e => e.titulo && e.titulo !== 'Nombre del patrón' && e.titulo.length > 5).sort((a,b)=>b.aplicado-a.aplicado).map(e => `<div class="pattern-card" style="border-left:3px solid var(--red)">
1044
+ <div class="pc-top"><div class="pc-title">${escHtml(e.titulo)}</div><span class="mb c${e.confianza}">${e.confianza}</span><span class="ab">${escHtml(e.area)}</span></div>
954
1045
  ${e.aplicado > 0 ? `<div style="font-size:10px;color:var(--text3)">Resolved ${e.aplicado} times</div>` : ''}
955
1046
  </div>`).join('') : '<div class="empty-state">No errors recorded yet</div>'}
956
1047
  </div>
@@ -1172,19 +1263,6 @@ body{background:var(--bg);color:var(--text);font-family:-apple-system,BlinkMacSy
1172
1263
  </div>
1173
1264
 
1174
1265
  <script>
1175
- console.log('=== Agentic KDD Dashboard Debug ===');
1176
- console.log('M_NODES:', ${JSON.stringify(mNodes)}.length, 'nodes');
1177
- console.log('metricsData:', ${JSON.stringify(metricsData)});
1178
- console.log('onboardingData pct:', ${JSON.stringify(onboardingData.pct)});
1179
- console.log('specsData:', ${JSON.stringify(specsData)}.length, 'specs');
1180
- console.log('logsData:', ${JSON.stringify(logsData)}.length, 'logs');
1181
- document.addEventListener('DOMContentLoaded', function(){
1182
- console.log('Nav items:', document.querySelectorAll('.nav-item').length);
1183
- console.log('Doc sections:', document.querySelectorAll('.docs-section').length);
1184
- console.log('doc-metrics exists:', !!document.getElementById('doc-metrics'));
1185
- console.log('doc-timeline exists:', !!document.getElementById('doc-timeline'));
1186
- console.log('doc-onboarding exists:', !!document.getElementById('doc-onboarding'));
1187
- });
1188
1266
  const NODES = ${JSON.stringify(nodes)};
1189
1267
  const EDGES = ${JSON.stringify(edges)};
1190
1268
  const M_NODES = ${JSON.stringify(mNodes)};
@@ -1438,6 +1516,43 @@ function centerGraph(){
1438
1516
  simulation.force('center',d3.forceCenter(c.clientWidth/2,c.clientHeight/2)).alpha(0.3).restart();
1439
1517
  }
1440
1518
 
1519
+ // ─── Graph interaction helpers ────────────────────────────────
1520
+ function updatePinIndicator(el, pinned){
1521
+ if(!el)return;
1522
+ d3.select(el).classed('node-pinned', pinned);
1523
+ }
1524
+
1525
+ function unpinNode(d, el){
1526
+ d.fx=null; d.fy=null;
1527
+ if(el) d3.select(el).classed('node-pinned', false);
1528
+ simulation.alpha(0.2).restart();
1529
+ }
1530
+
1531
+ function releaseAll(){
1532
+ if(!nodeSel)return;
1533
+ NODES.forEach(n=>{n.fx=null;n.fy=null;});
1534
+ nodeSel.classed('node-pinned', false);
1535
+ simulation.alpha(0.5).restart();
1536
+ }
1537
+
1538
+ function spreadGraph(){
1539
+ if(!simulation)return;
1540
+ simulation.force('charge',d3.forceManyBody().strength(d=>(DEGREE_MAP[d.id]||0)>=GOD_THRESHOLD?-1200:-700));
1541
+ simulation.alpha(0.8).restart();
1542
+ setTimeout(()=>{
1543
+ const repVal = parseInt(document.getElementById('repulsion-slider')?.value||320);
1544
+ simulation.force('charge',d3.forceManyBody().strength(d=>(DEGREE_MAP[d.id]||0)>=GOD_THRESHOLD?-(repVal*2):-(repVal)));
1545
+ simulation.alpha(0.1).restart();
1546
+ }, 1800);
1547
+ }
1548
+
1549
+ function setRepulsion(val){
1550
+ val = parseInt(val);
1551
+ if(!simulation)return;
1552
+ simulation.force('charge',d3.forceManyBody().strength(d=>(DEGREE_MAP[d.id]||0)>=GOD_THRESHOLD?-(val*2):-(val)));
1553
+ simulation.alpha(0.3).restart();
1554
+ }
1555
+
1441
1556
  // ─── D3 Knowledge Graph ───────────────────────────────────────
1442
1557
  function renderGraph(){
1443
1558
  if(!NODES.length)return;
@@ -1466,7 +1581,7 @@ function renderGraph(){
1466
1581
  const sd=DEGREE_MAP[d.source.id]||0, td=DEGREE_MAP[d.target.id]||0;
1467
1582
  return sd>=GOD_THRESHOLD||td>=GOD_THRESHOLD?120:90;
1468
1583
  }))
1469
- .force('charge',d3.forceManyBody().strength(d=>(DEGREE_MAP[d.id]||0)>=GOD_THRESHOLD?-300:-150))
1584
+ .force('charge',d3.forceManyBody().strength(d=>(DEGREE_MAP[d.id]||0)>=GOD_THRESHOLD?-600:-320))
1470
1585
  .force('center',d3.forceCenter(W/2,H/2))
1471
1586
  .force('collision',d3.forceCollide(d=>getNodeRadius(d)+4));
1472
1587
 
@@ -1488,8 +1603,9 @@ function renderGraph(){
1488
1603
  .style('cursor','pointer')
1489
1604
  .call(d3.drag()
1490
1605
  .on('start',(ev,d)=>{if(!ev.active)simulation.alphaTarget(0.3).restart();d.fx=d.x;d.fy=d.y;})
1491
- .on('drag',(ev,d)=>{d.fx=ev.x;d.fy=ev.y;})
1492
- .on('end',(ev,d)=>{if(!ev.active)simulation.alphaTarget(0);d.fx=null;d.fy=null;}))
1606
+ .on('drag',(ev,d)=>{d.fx=ev.x;d.fy=ev.y;updatePinIndicator(ev.currentTarget,true);})
1607
+ .on('end',(ev,d)=>{if(!ev.active)simulation.alphaTarget(0);/* node stays PINNED — dblclick to release */}))
1608
+ .on('dblclick',(ev,d)=>{ev.stopPropagation();unpinNode(d,ev.currentTarget);})
1493
1609
  .on('click',(ev,d)=>{ev.stopPropagation();selectNode(d.id);})
1494
1610
  .on('mouseover',(ev,d)=>{
1495
1611
  const tt=document.getElementById('gtt');
@@ -1706,7 +1822,156 @@ function copyMarkdown(){
1706
1822
  renderNodeList();
1707
1823
  renderGraph();
1708
1824
  </script>
1709
- </body>
1825
+
1826
+ <!-- ───────────────────────────────────────────────────────────────────
1827
+ v3.3 PANELS: CONTRACT GUARD + CREATIVE ENGINE + CURATOR
1828
+ ─────────────────────────────────────────────────────────────────────── -->
1829
+
1830
+ <style>
1831
+ .v33-section { margin: 0 auto 32px; max-width: 1100px; padding: 0 24px; }
1832
+ .v33-title { font-size: 11px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: #8d99ae; margin: 0 0 14px; }
1833
+ .v33-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
1834
+ .v33-card { background: #1e2535; border: 1px solid #2e3550; border-radius: 12px; padding: 18px 20px; }
1835
+ .v33-card-header { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
1836
+ .v33-card-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
1837
+ .icon-purple { background: rgba(127,119,221,0.15); }
1838
+ .icon-green { background: rgba(29,158,117,0.15); }
1839
+ .icon-amber { background: rgba(239,159,39,0.15); }
1840
+ .v33-card-title { font-size: 14px; font-weight: 700; color: #e2e8f0; }
1841
+ .v33-card-sub { font-size: 11px; color: #64748b; margin-top: 1px; }
1842
+ .v33-stat-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 14px; }
1843
+ .v33-stat { background: rgba(255,255,255,0.03); border-radius: 8px; padding: 10px 12px; text-align: center; }
1844
+ .v33-stat-val { font-size: 22px; font-weight: 800; line-height: 1.1; }
1845
+ .v33-stat-label { font-size: 10px; color: #64748b; margin-top: 2px; }
1846
+ .val-purple { color: #9f99e8; }
1847
+ .val-green { color: #34d399; }
1848
+ .val-amber { color: #fbbf24; }
1849
+ .val-red { color: #f87171; }
1850
+ .val-gray { color: #94a3b8; }
1851
+ .contract-list { display: flex; flex-direction: column; gap: 6px; }
1852
+ .contract-row { display: flex; align-items: center; gap: 8px; padding: 7px 10px; background: rgba(255,255,255,0.03); border-radius: 7px; font-size: 12px; }
1853
+ .contract-badge { font-size: 9px; font-weight: 700; padding: 2px 6px; border-radius: 10px; white-space: nowrap; flex-shrink: 0; }
1854
+ .badge-protected { background: rgba(127,119,221,0.2); color: #9f99e8; }
1855
+ .badge-verified { background: rgba(29,158,117,0.2); color: #34d399; }
1856
+ .badge-candidate { background: rgba(239,159,39,0.2); color: #fbbf24; }
1857
+ .badge-invalid { background: rgba(248,113,113,0.2); color: #f87171; }
1858
+ .contract-name { flex: 1; color: #cbd5e1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1859
+ .contract-module { font-size: 10px; color: #475569; }
1860
+ .suggestion-row { display: flex; align-items: flex-start; gap: 8px; padding: 7px 10px; background: rgba(255,255,255,0.03); border-radius: 7px; font-size: 12px; margin-bottom: 5px; }
1861
+ .sug-type { font-size: 9px; font-weight: 700; padding: 2px 6px; border-radius: 10px; white-space: nowrap; flex-shrink: 0; background: rgba(239,159,39,0.15); color: #fbbf24; }
1862
+ .sug-auto { background: rgba(29,158,117,0.15); color: #34d399; }
1863
+ .sug-text { color: #94a3b8; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1864
+ .level-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
1865
+ .level-indicator { height: 6px; flex: 1; background: rgba(255,255,255,0.06); border-radius: 3px; overflow: hidden; }
1866
+ .level-fill { height: 100%; border-radius: 3px; transition: width .6s; }
1867
+ .curator-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 12px; }
1868
+ .curator-row:last-child { border-bottom: none; }
1869
+ .curator-key { color: #64748b; }
1870
+ .curator-val { color: #94a3b8; font-weight: 600; }
1871
+ .obsidian-panel { background: #1e2535; border: 1px solid #3730a3; border-radius: 12px; padding: 16px 20px; }
1872
+ .obsidian-header { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
1873
+ .obsidian-badge { font-size: 10px; padding: 2px 8px; border-radius: 10px; background: rgba(99,91,255,0.15); color: #818cf8; font-weight: 600; }
1874
+ .obsidian-text { font-size: 13px; color: #94a3b8; line-height: 1.5; }
1875
+ .obsidian-cmd { font-family: monospace; font-size: 11px; background: rgba(255,255,255,0.05); color: #a5b4fc; padding: 6px 10px; border-radius: 6px; margin-top: 8px; display: block; }
1876
+ @media(max-width:600px){ .v33-stat-row { grid-template-columns: repeat(2,1fr); } }
1877
+ </style>
1878
+
1879
+ <div class="v33-section">
1880
+ <div class="v33-title">Preservation Intelligence Layer — v3.3</div>
1881
+ <div class="v33-grid">
1882
+
1883
+ <!-- CONTRACT GUARD -->
1884
+ <div class="v33-card">
1885
+ <div class="v33-card-header">
1886
+ <div class="v33-card-icon icon-purple">🛡️</div>
1887
+ <div><div class="v33-card-title">Contract Guard</div><div class="v33-card-sub">Lo que no se puede romper</div></div>
1888
+ </div>
1889
+ <div class="v33-stat-row">
1890
+ <div class="v33-stat"><div class="v33-stat-val val-purple">${contractData.protected}</div><div class="v33-stat-label">Protected</div></div>
1891
+ <div class="v33-stat"><div class="v33-stat-val val-green">${contractData.verified}</div><div class="v33-stat-label">Verified</div></div>
1892
+ <div class="v33-stat"><div class="v33-stat-val val-amber">${contractData.candidate}</div><div class="v33-stat-label">Candidate</div></div>
1893
+ <div class="v33-stat"><div class="v33-stat-val ${contractData.violations > 0 ? 'val-red' : 'val-gray'}">${contractData.violations}</div><div class="v33-stat-label">Violations</div></div>
1894
+ </div>
1895
+ ${contractData.recent && contractData.recent.length > 0 ? `
1896
+ <div class="contract-list">
1897
+ ${contractData.recent.map(c => `
1898
+ <div class="contract-row">
1899
+ <span class="contract-badge badge-${c.status}">${c.status.toUpperCase()}</span>
1900
+ <span class="contract-name" title="${escHtml(c.name)}">${escHtml(c.name.substring(0,35))}</span>
1901
+ <span class="contract-module">${escHtml(c.module)}</span>
1902
+ </div>
1903
+ `).join('')}
1904
+ </div>` : `<div style="font-size:12px;color:#475569;text-align:center;padding:12px 0">Sin contratos todavía — corre más ciclos aa: para generarlos automáticamente</div>`}
1905
+ </div>
1906
+
1907
+ <!-- CREATIVE ENGINE -->
1908
+ <div class="v33-card">
1909
+ <div class="v33-card-header">
1910
+ <div class="v33-card-icon icon-amber">✨</div>
1911
+ <div><div class="v33-card-title">Creative Engine</div><div class="v33-card-sub">Autonomía creativa dirigida</div></div>
1912
+ </div>
1913
+ <div class="level-bar">
1914
+ <span style="font-size:11px;color:#64748b;white-space:nowrap">Nivel ${creativeData.level}</span>
1915
+ <div class="level-indicator"><div class="level-fill" style="width:${(creativeData.level / 3 * 100).toFixed(0)}%;background:${creativeData.level >= 2 ? '#34d399' : '#fbbf24'}"></div></div>
1916
+ <span style="font-size:11px;color:${creativeData.level >= 2 ? '#34d399' : '#fbbf24'};white-space:nowrap">${creativeData.level >= 2 ? 'CREATIVO' : 'ASISTIDO'}</span>
1917
+ </div>
1918
+ ${creativeData.level < 2 ? `<div style="font-size:11px;color:#475569;margin-bottom:10px">Faltan ${10 - (creativeData.protected_for_level2 || 0)} contratos verificados para Nivel 2</div>` : ''}
1919
+ <div class="v33-stat-row">
1920
+ <div class="v33-stat"><div class="v33-stat-val val-amber">${creativeData.suggestions}</div><div class="v33-stat-label">Pendientes</div></div>
1921
+ <div class="v33-stat"><div class="v33-stat-val val-green">${creativeData.wins}</div><div class="v33-stat-label">Aplicadas</div></div>
1922
+ </div>
1923
+ ${creativeData.recent_suggestions && creativeData.recent_suggestions.length > 0 ? `
1924
+ <div>
1925
+ ${creativeData.recent_suggestions.map(s => `
1926
+ <div class="suggestion-row">
1927
+ <span class="sug-type ${s.auto_applicable ? 'sug-auto' : ''}">${s.type}</span>
1928
+ <span class="sug-text" title="${escHtml(s.title)}">${escHtml(s.title.substring(0,50))}</span>
1929
+ </div>
1930
+ `).join('')}
1931
+ </div>` : `<div style="font-size:12px;color:#475569;text-align:center;padding:8px 0">Sin sugerencias — se generan automáticamente cada ciclo</div>`}
1932
+ </div>
1933
+
1934
+ <!-- MEM CURATOR -->
1935
+ <div class="v33-card">
1936
+ <div class="v33-card-icon icon-green" style="margin-bottom:14px;width:auto;height:auto;padding:0;display:flex;gap:10px;align-items:center;background:none">
1937
+ <div style="width:32px;height:32px;border-radius:8px;background:rgba(29,158,117,0.15);display:flex;align-items:center;justify-content:center;font-size:16px">🔬</div>
1938
+ <div><div class="v33-card-title">MemCurator</div><div class="v33-card-sub">Gobernanza autónoma de memoria</div></div>
1939
+ </div>
1940
+ <div class="curator-row"><span class="curator-key">Última curation</span><span class="curator-val">${curatorData.lastRun}</span></div>
1941
+ <div class="curator-row"><span class="curator-key">Ciclos para auto-run</span><span class="curator-val">cada 10</span></div>
1942
+ <div class="curator-row"><span class="curator-key">TTL episódico</span><span class="curator-val">30 días</span></div>
1943
+ <div class="curator-row"><span class="curator-key">Límite nodos activos</span><span class="curator-val">1,000</span></div>
1944
+ <div class="curator-row"><span class="curator-key">Dedup threshold</span><span class="curator-val">92% Jaccard</span></div>
1945
+ <div style="margin-top:12px;padding:10px;background:rgba(255,255,255,0.03);border-radius:8px;font-size:11px;color:#64748b">
1946
+ <code style="color:#a5b4fc">akdd cure</code> — curation manual<br>
1947
+ <code style="color:#a5b4fc">akdd cure report</code> — preview sin cambios
1948
+ </div>
1949
+ </div>
1950
+
1951
+ </div>
1952
+ </div>
1953
+
1954
+ <!-- OBSIDIAN MCP OPTIONAL CONNECTOR -->
1955
+ <div class="v33-section">
1956
+ <div class="v33-title">Conector opcional — Obsidian MCP</div>
1957
+ <div class="obsidian-panel">
1958
+ <div class="obsidian-header">
1959
+ <span style="font-size:20px">🗂️</span>
1960
+ <div style="flex:1">
1961
+ <span style="font-size:14px;font-weight:700;color:#e2e8f0">Obsidian como fuente humana</span>
1962
+ <span class="obsidian-badge" style="margin-left:8px">OPCIONAL</span>
1963
+ </div>
1964
+ </div>
1965
+ <div class="obsidian-text">
1966
+ Si usas Obsidian, el plugin MCP conecta tu vault directamente con Agentic KDD.<br>
1967
+ Tus notas personales, decisiones y gotchas fluyen al grafo automáticamente — sin hacer <code style="color:#a5b4fc">akdd knowledge</code> manual.
1968
+ </div>
1969
+ <code class="obsidian-cmd">1. Instalar plugin "Obsidian MCP Server" en Obsidian<br>2. En Claude Code/Cursor: agrega el servidor MCP del plugin<br>3. La herramienta obsidian_read_notes queda disponible en el chat</code>
1970
+ <div style="font-size:11px;color:#475569;margin-top:8px">Sin Obsidian instalado: usa <code style="color:#a5b4fc">akdd knowledge</code> normalmente — mismo resultado.</div>
1971
+ </div>
1972
+ </div>
1973
+
1974
+ </body>
1710
1975
  </html>`;
1711
1976
 
1712
1977
  const server = http.createServer((req, res) => {