thumbgate 1.30.0 → 1.34.0

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.
Files changed (92) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.well-known/mcp/server-card.json +1 -1
  3. package/README.md +54 -16
  4. package/adapters/claude/.mcp.json +2 -2
  5. package/adapters/forge/forge.yaml +3 -3
  6. package/adapters/mcp/server-stdio.js +105 -10
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/bench/observability-eval-suite.json +2 -2
  9. package/bin/cli.js +168 -31
  10. package/config/evals/generation-quality-golden.json +95 -0
  11. package/config/evals/rag-answer-quality-golden.json +91 -0
  12. package/config/evals/retrieval-hybrid-ablation.json +66 -0
  13. package/config/evals/retrieval-ranking-golden.json +522 -0
  14. package/config/gates/claim-verifiers.example.json +42 -0
  15. package/config/gates/claim-verifiers.json +25 -0
  16. package/config/gates/default.json +217 -50
  17. package/config/mcp-allowlists.json +233 -206
  18. package/config/model-tiers.json +7 -2
  19. package/glama.json +6 -0
  20. package/hooks/hooks.json +1 -1
  21. package/package.json +69 -12
  22. package/public/assets/diagrams/before-after.svg +17 -16
  23. package/public/assets/diagrams/hero-thumbs.svg +68 -0
  24. package/public/assets/diagrams/loop.svg +19 -13
  25. package/public/assets/diagrams/self-improving-thumbs-loop.svg +105 -0
  26. package/public/compare.html +1 -0
  27. package/public/dashboard.html +126 -28
  28. package/public/evaluations.html +1 -1
  29. package/public/index.html +142 -13
  30. package/public/numbers.html +3 -2
  31. package/public/pricing.html +143 -30
  32. package/scripts/a-plus-evidence-scorecard.js +303 -0
  33. package/scripts/agent-readiness.js +110 -0
  34. package/scripts/async-eval-observability.js +36 -11
  35. package/scripts/audit-trail.js +37 -1
  36. package/scripts/auto-promote-gates.js +149 -34
  37. package/scripts/auto-wire-hooks.js +20 -8
  38. package/scripts/cli-schema.js +14 -0
  39. package/scripts/colbert-style-maxsim.js +236 -0
  40. package/scripts/cross-encoder-reranker.js +356 -126
  41. package/scripts/dashboard-chat.js +350 -17
  42. package/scripts/document-intake.js +283 -7
  43. package/scripts/eval-quality-suite.js +204 -0
  44. package/scripts/feedback-loop.js +115 -7
  45. package/scripts/feedback-paths.js +32 -13
  46. package/scripts/feedback-quality.js +53 -0
  47. package/scripts/feedback-schema.js +3 -0
  48. package/scripts/file-ledger-lock.js +130 -0
  49. package/scripts/filesystem-search.js +17 -7
  50. package/scripts/financial-control-plane.js +1514 -0
  51. package/scripts/gates-engine.js +202 -7
  52. package/scripts/gemini-embedding-policy.js +1 -0
  53. package/scripts/harness-tool-names.js +70 -0
  54. package/scripts/hook-runtime.js +15 -3
  55. package/scripts/hook-stop-anti-claim.js +63 -3
  56. package/scripts/human-escalation.js +353 -41
  57. package/scripts/lesson-db.js +16 -5
  58. package/scripts/lesson-embedding-index.js +67 -20
  59. package/scripts/lesson-embedding-maintenance.js +177 -0
  60. package/scripts/lesson-reranker.js +55 -9
  61. package/scripts/lesson-retrieval.js +305 -29
  62. package/scripts/lesson-search.js +22 -8
  63. package/scripts/llm-client.js +304 -15
  64. package/scripts/model-tier-router.js +593 -0
  65. package/scripts/pragmatic-hybrid-search.js +379 -0
  66. package/scripts/provider-action-normalizer.js +11 -4
  67. package/scripts/rag-document-pipeline.js +461 -0
  68. package/scripts/rag-structured-output.js +441 -0
  69. package/scripts/ragas-style-metrics.js +351 -0
  70. package/scripts/request-envelope.js +178 -0
  71. package/scripts/rerank-pipeline.js +370 -0
  72. package/scripts/rerank-quality-eval.js +155 -0
  73. package/scripts/retrieval-hybrid-ablation.js +120 -0
  74. package/scripts/retrieval-quality-tier.js +118 -0
  75. package/scripts/secret-scanner.js +395 -4
  76. package/scripts/self-distill-agent.js +7 -1
  77. package/scripts/self-healing-check.js +25 -0
  78. package/scripts/skill-packs.js +183 -0
  79. package/scripts/slow-loop.js +72 -0
  80. package/scripts/statusline-links.js +1 -1
  81. package/scripts/statusline.sh +8 -1
  82. package/scripts/telemetry-analytics.js +13 -1
  83. package/scripts/thumbgate-search.js +98 -6
  84. package/scripts/tier-budget-guard.js +186 -0
  85. package/scripts/tool-registry.js +141 -5
  86. package/scripts/universal-claim-evaluator.js +767 -0
  87. package/scripts/vector-store.js +154 -17
  88. package/scripts/verify-marketing-pages-deployed.js +85 -3
  89. package/scripts/workflow-sentinel.js +77 -11
  90. package/server.json +44 -0
  91. package/smithery.yaml +17 -0
  92. package/src/api/server.js +196 -13
@@ -286,7 +286,7 @@
286
286
  }
287
287
  </style>
288
288
  </head>
289
- <body>
289
+ <body data-revenue-assist="off">
290
290
 
291
291
  <nav>
292
292
  <div class="container">
@@ -1013,6 +1013,12 @@ async function checkLocalLlmReachable(url) {
1013
1013
  }
1014
1014
 
1015
1015
  function disconnectKey() {
1016
+ pauseDashboardHydration();
1017
+ dashboardDataPromise = null;
1018
+ if (__liveEventsController) {
1019
+ try { __liveEventsController.abort(); } catch (_) { /* already aborted */ }
1020
+ __liveEventsController = null;
1021
+ }
1016
1022
  API_KEY = '';
1017
1023
  isDemo = false;
1018
1024
  var input = document.getElementById('apiKey');
@@ -1094,7 +1100,12 @@ async function connect(options) {
1094
1100
  }
1095
1101
  renderStats(data);
1096
1102
  setSelectedCard('all');
1097
- await loadDashboardData();
1103
+ var deepLinkedTab = getDeepLinkTab();
1104
+ if (deepLinkedTab && deepLinkedTab !== 'search') {
1105
+ switchTab(deepLinkedTab);
1106
+ } else {
1107
+ scheduleDashboardHydration(10000);
1108
+ }
1098
1109
  // Open a live-events stream so feedback/rule-regen events surface
1099
1110
  // without a manual refresh. Non-fatal: if the browser doesn't support
1100
1111
  // ReadableStream over fetch (very old), we simply stay on the polled path.
@@ -1115,6 +1126,22 @@ async function connect(options) {
1115
1126
  }
1116
1127
  }
1117
1128
 
1129
+ function resolveActiveGateCount(data) {
1130
+ if (!data || typeof data !== 'object') return null;
1131
+ if (data.activeGateCount != null && data.activeGateCount !== '') {
1132
+ var fromCount = Number(data.activeGateCount);
1133
+ if (Number.isFinite(fromCount)) return fromCount;
1134
+ }
1135
+ var gateStats = data.gateStats || {};
1136
+ if (gateStats.totalGates != null && gateStats.totalGates !== '') {
1137
+ var fromStats = Number(gateStats.totalGates);
1138
+ if (Number.isFinite(fromStats)) return fromStats;
1139
+ }
1140
+ if (Array.isArray(data.gates)) return data.gates.length;
1141
+ if (Array.isArray(data.activeGates)) return data.activeGates.length;
1142
+ return null;
1143
+ }
1144
+
1118
1145
  function renderStats(data) {
1119
1146
  const total = (data.totalFeedback || data.total || 0);
1120
1147
  const pos = (data.totalPositive || data.positive || data.thumbsUp || 0);
@@ -1122,6 +1149,14 @@ function renderStats(data) {
1122
1149
  document.getElementById('statTotal').textContent = total;
1123
1150
  document.getElementById('statPositive').textContent = pos;
1124
1151
  document.getElementById('statNegative').textContent = neg;
1152
+ // Active Gates used to stay on the HTML placeholder "—" because only
1153
+ // renderDashboardData(/v1/dashboard) set it. Connect + live refresh only call
1154
+ // /v1/feedback/stats → renderStats. Prefer activeGateCount from stats; fall
1155
+ // back to gateStats/gates when present.
1156
+ var gateCount = resolveActiveGateCount(data);
1157
+ if (gateCount != null) {
1158
+ document.getElementById('statGates').textContent = gateCount;
1159
+ }
1125
1160
  }
1126
1161
 
1127
1162
  function setSelectedCard(action) {
@@ -1151,6 +1186,7 @@ async function search() {
1151
1186
  return;
1152
1187
  }
1153
1188
 
1189
+ pauseDashboardHydration();
1154
1190
  try {
1155
1191
  const res = await fetch('/v1/search', {
1156
1192
  method: 'POST',
@@ -1167,6 +1203,8 @@ async function search() {
1167
1203
  container.innerHTML = results.map(renderResult).join('');
1168
1204
  } catch (e) {
1169
1205
  setMessageState(container, 'empty', e && e.message ? e.message : 'Search failed');
1206
+ } finally {
1207
+ scheduleDashboardHydration(3000);
1170
1208
  }
1171
1209
  }
1172
1210
 
@@ -1352,6 +1390,20 @@ function switchTab(name) {
1352
1390
  history.replaceState(null, '', '#' + name);
1353
1391
  }
1354
1392
  } catch (e) { /* ignore: older browsers or sandboxed frames */ }
1393
+ if (API_KEY && !isDemo) {
1394
+ if (name === 'ai-inventory') {
1395
+ loadAiInventory();
1396
+ } else if (name === 'enterprise') {
1397
+ loadEnterpriseDataChatStatus();
1398
+ } else if (name !== 'search' && name !== 'export') {
1399
+ ensureDashboardDataLoaded().then(function() {
1400
+ if (name === 'generated') loadGeneratedView(currentGeneratedView);
1401
+ if (name === 'insights') requestAnimationFrame(renderPendingInsightsCharts);
1402
+ });
1403
+ }
1404
+ } else if (name === 'insights') {
1405
+ requestAnimationFrame(renderPendingInsightsCharts);
1406
+ }
1355
1407
  }
1356
1408
 
1357
1409
  /**
@@ -1390,6 +1442,13 @@ async function loadGates() {
1390
1442
  const data = await res.json();
1391
1443
  renderDashboardData(data);
1392
1444
  } catch (e) {
1445
+ // Do not leave Active Gates stuck on the HTML placeholder "—" when the
1446
+ // full dashboard payload fails — feedback stats may already have painted a
1447
+ // real count; only fall back to 0 if still unset.
1448
+ var gatesEl = document.getElementById('statGates');
1449
+ if (gatesEl && (gatesEl.textContent === '—' || gatesEl.textContent === '' || gatesEl.textContent == null)) {
1450
+ gatesEl.textContent = '0';
1451
+ }
1393
1452
  setMessageState(document.getElementById('gatesList'), 'empty', e && e.message ? e.message : 'Failed to load gates');
1394
1453
  setMessageState(document.getElementById('teamSummaryCards'), 'empty', e && e.message ? e.message : 'Failed to load team metrics');
1395
1454
  setMessageState(document.getElementById('predictiveSummaryCards'), 'empty', e && e.message ? e.message : 'Failed to load predictive insights');
@@ -1402,15 +1461,36 @@ async function loadGates() {
1402
1461
 
1403
1462
  async function loadDashboardData() {
1404
1463
  await loadGates();
1405
- await loadGeneratedView(currentGeneratedView);
1406
- // Fetch full dashboard for insights charts
1407
- try {
1408
- var res = await fetch('/v1/dashboard', { headers: getHeaders() });
1409
- if (res.ok) {
1410
- var data = await res.json();
1411
- renderDashboardData(data);
1412
- }
1413
- } catch (e) { /* insights degrade gracefully */ }
1464
+ }
1465
+
1466
+ var dashboardDataPromise = null;
1467
+ var dashboardHydrationTimer = null;
1468
+
1469
+ function pauseDashboardHydration() {
1470
+ if (dashboardHydrationTimer) {
1471
+ clearTimeout(dashboardHydrationTimer);
1472
+ dashboardHydrationTimer = null;
1473
+ }
1474
+ }
1475
+
1476
+ function ensureDashboardDataLoaded() {
1477
+ pauseDashboardHydration();
1478
+ if (!dashboardDataPromise) {
1479
+ dashboardDataPromise = loadDashboardData().catch(function(error) {
1480
+ dashboardDataPromise = null;
1481
+ throw error;
1482
+ });
1483
+ }
1484
+ return dashboardDataPromise;
1485
+ }
1486
+
1487
+ function scheduleDashboardHydration(delayMs) {
1488
+ if (!API_KEY || isDemo || dashboardDataPromise) return;
1489
+ pauseDashboardHydration();
1490
+ dashboardHydrationTimer = setTimeout(function() {
1491
+ dashboardHydrationTimer = null;
1492
+ ensureDashboardDataLoaded();
1493
+ }, delayMs);
1414
1494
  }
1415
1495
 
1416
1496
  // Live-events SSE subscription. We use fetch() + ReadableStream instead of
@@ -1552,7 +1632,11 @@ function renderDashboardData(data) {
1552
1632
  if (reviewButton) reviewButton.disabled = false;
1553
1633
  const gates = data.gates || data.activeGates || [];
1554
1634
  const gateStats = data.gateStats || {};
1555
- document.getElementById('statGates').textContent = gates.length || gateStats.totalGates || 0;
1635
+ // Prefer explicit count; never leave the hero card on the "—" placeholder.
1636
+ // Note: `gates.length || totalGates` is wrong when length is 0 but totalGates
1637
+ // is also 0 — use resolveActiveGateCount so 0 still renders as "0".
1638
+ var gateCount = resolveActiveGateCount(data);
1639
+ document.getElementById('statGates').textContent = gateCount != null ? gateCount : 0;
1556
1640
  if (!gates.length) {
1557
1641
  document.getElementById('gatesList').innerHTML = '<div class="empty">No active checks</div>';
1558
1642
  } else {
@@ -1573,8 +1657,6 @@ function renderDashboardData(data) {
1573
1657
  renderRegulatedProof(data.regulatedProof || {});
1574
1658
  renderTemplates(data.templateLibrary || {});
1575
1659
  renderInsights(data);
1576
- loadAiInventory();
1577
- loadEnterpriseDataChatStatus();
1578
1660
  }
1579
1661
 
1580
1662
  async function loadAiInventory() {
@@ -2466,17 +2548,23 @@ async function exportDpo() {
2466
2548
  const status = document.getElementById('exportStatus');
2467
2549
  status.textContent = 'Exporting...';
2468
2550
  try {
2469
- const res = await fetch('/v1/dpo/export', { headers: getHeaders() });
2470
- if (!res.ok) throw new Error('Export failed');
2551
+ const res = await fetch('/v1/dpo/export', {
2552
+ method: 'POST',
2553
+ headers: getHeaders(),
2554
+ body: JSON.stringify({ includePairs: true })
2555
+ });
2471
2556
  const data = await res.json();
2472
- const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
2557
+ if (!res.ok) throw new Error(data.detail || data.error || 'Export failed');
2558
+ if (!Array.isArray(data.records)) throw new Error('Export response did not include DPO pairs');
2559
+ const records = data.records;
2560
+ const blob = new Blob([JSON.stringify(records, null, 2)], { type: 'application/json' });
2473
2561
  const url = URL.createObjectURL(blob);
2474
2562
  const a = document.createElement('a');
2475
2563
  a.href = url;
2476
2564
  a.download = 'thumbgate-dpo-pairs-' + new Date().toISOString().slice(0,10) + '.json';
2477
2565
  a.click();
2478
2566
  URL.revokeObjectURL(url);
2479
- const count = data.pairs ? data.pairs.length : Array.isArray(data) ? data.length : 0;
2567
+ const count = records.length;
2480
2568
  status.textContent = '✓ Exported ' + count + ' preference pairs';
2481
2569
  status.style.color = 'var(--green)';
2482
2570
  } catch (e) {
@@ -2491,14 +2579,24 @@ async function exportDpo() {
2491
2579
  var feedbackChart = null;
2492
2580
  var lessonChart = null;
2493
2581
  var gateAuditChart = null;
2582
+ var pendingInsightsData = null;
2494
2583
 
2495
2584
  function renderInsights(data) {
2496
2585
  renderTokenSavings(data.tokenSavings, data.gateStats || {});
2497
2586
  renderPipeline(data.lessonPipeline || {});
2498
- renderFeedbackTrendChart(data.feedbackTimeSeries || {});
2499
- renderLessonTrendChart(data.feedbackTimeSeries || {});
2500
- renderGateAuditChartFromData(data.gateAudit || {});
2501
2587
  renderActionableRemediations(data.actionableRemediations || []);
2588
+ pendingInsightsData = data;
2589
+ var insightsTab = document.getElementById('tab-insights');
2590
+ if (insightsTab && insightsTab.classList.contains('active')) {
2591
+ requestAnimationFrame(renderPendingInsightsCharts);
2592
+ }
2593
+ }
2594
+
2595
+ function renderPendingInsightsCharts() {
2596
+ if (!pendingInsightsData) return;
2597
+ renderFeedbackTrendChart(pendingInsightsData.feedbackTimeSeries || {});
2598
+ renderLessonTrendChart(pendingInsightsData.feedbackTimeSeries || {});
2599
+ renderGateAuditChartFromData(pendingInsightsData.gateAudit || {});
2502
2600
  }
2503
2601
 
2504
2602
  function renderActionableRemediations(remediations) {
@@ -2608,7 +2706,7 @@ function renderFeedbackTrendChart(ts) {
2608
2706
  var canvas = document.getElementById('feedbackTrendChart');
2609
2707
  if (!canvas) return;
2610
2708
  if (typeof Chart === 'undefined') return;
2611
- var days = ts.days || [];
2709
+ var days = (ts.days || []).slice(-90);
2612
2710
  if (feedbackChart) feedbackChart.destroy();
2613
2711
 
2614
2712
  var labels = days.map(function(d) { return d.dayKey.slice(5); });
@@ -2649,7 +2747,7 @@ function renderFeedbackTrendChart(ts) {
2649
2747
  },
2650
2748
  scales: {
2651
2749
  x: { ticks: { color: '#8b8b96', font: { size: 10 }, maxRotation: 45 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2652
- y: { beginAtZero: true, ticks: { color: '#8b8b96', stepSize: 1 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2750
+ y: { beginAtZero: true, ticks: { color: '#8b8b96', precision: 0, maxTicksLimit: 6 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2653
2751
  },
2654
2752
  },
2655
2753
  });
@@ -2659,7 +2757,7 @@ function renderLessonTrendChart(ts) {
2659
2757
  var canvas = document.getElementById('lessonTrendChart');
2660
2758
  if (!canvas) return;
2661
2759
  if (typeof Chart === 'undefined') return;
2662
- var days = ts.days || [];
2760
+ var days = (ts.days || []).slice(-90);
2663
2761
  if (lessonChart) lessonChart.destroy();
2664
2762
 
2665
2763
  var labels = days.map(function(d) { return d.dayKey.slice(5); });
@@ -2707,8 +2805,8 @@ function renderLessonTrendChart(ts) {
2707
2805
  },
2708
2806
  scales: {
2709
2807
  x: { ticks: { color: '#8b8b96', font: { size: 10 }, maxRotation: 45 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2710
- y: { beginAtZero: true, position: 'left', ticks: { color: '#22d3ee', stepSize: 1 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2711
- y1: { beginAtZero: true, position: 'right', ticks: { color: '#4ade80' }, grid: { drawOnChartArea: false } },
2808
+ y: { beginAtZero: true, position: 'left', ticks: { color: '#22d3ee', precision: 0, maxTicksLimit: 6 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2809
+ y1: { beginAtZero: true, position: 'right', ticks: { color: '#4ade80', precision: 0, maxTicksLimit: 6 }, grid: { drawOnChartArea: false } },
2712
2810
  },
2713
2811
  },
2714
2812
  });
@@ -2718,7 +2816,7 @@ function renderGateAuditChartFromData(gateAudit) {
2718
2816
  var canvas = document.getElementById('gateAuditChart');
2719
2817
  if (!canvas) return;
2720
2818
  if (typeof Chart === 'undefined') return;
2721
- var days = gateAudit.days || [];
2819
+ var days = (gateAudit.days || []).slice(-90);
2722
2820
  if (gateAuditChart) gateAuditChart.destroy();
2723
2821
 
2724
2822
  var labels = days.map(function(d) { return (d.dayKey || '').slice(5); });
@@ -2756,7 +2854,7 @@ function renderGateAuditChartFromData(gateAudit) {
2756
2854
  },
2757
2855
  scales: {
2758
2856
  x: { stacked: true, ticks: { color: '#8b8b96', font: { size: 10 } }, grid: { color: 'rgba(34,34,37,0.5)' } },
2759
- y: { stacked: true, beginAtZero: true, ticks: { color: '#8b8b96', stepSize: 1 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2857
+ y: { stacked: true, beginAtZero: true, ticks: { color: '#8b8b96', precision: 0, maxTicksLimit: 6 }, grid: { color: 'rgba(34,34,37,0.5)' } },
2760
2858
  },
2761
2859
  },
2762
2860
  });
@@ -154,7 +154,7 @@
154
154
  <div class="rule">The rule all of this enforces: no accuracy figure ships without the baseline it beats and the split it was measured on.</div>
155
155
 
156
156
  <h2>Reproduce it</h2>
157
- <p>Honest scoping: the measured corpus is <strong>private operator feedback telemetry</strong> and does not ship in the public repo. A clean clone gives you the methodology write-up, the CI quality gate (synthetic fixtures / no private data), and the same harness to run against <em>your</em> feedback log. The figures on this page are first-party operator measurements documented in <a style="color:var(--cyan)" href="https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/ML-EVALUATION.md">docs/ML-EVALUATION.md</a> — not a committed private corpus dump.</p>
157
+ <p>Honest scoping: the measured corpus is <strong>private operator feedback telemetry</strong> and does not ship in the public repo. The exact machine-readable report behind the figures on this page is checked in with provenance at <a href="https://github.com/IgorGanapolsky/ThumbGate/blob/main/evals/risk-model-report.json">evals/risk-model-report.json</a>. A clean clone gives you that report, the methodology write-up, the CI quality gate (synthetic fixtures / no private data), and the same harness to run against <em>your</em> feedback log. The figures on this page are first-party operator measurements documented in <a style="color:var(--cyan)" href="https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/ML-EVALUATION.md">docs/ML-EVALUATION.md</a> — not a committed private corpus dump.</p>
158
158
  <pre>git clone https://github.com/IgorGanapolsky/ThumbGate
159
159
  cd ThumbGate &amp;&amp; npm ci
160
160
  npm run test:risk-quality # CI quality gate — no private data needed
package/public/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <meta name="generator" content="ThumbGate">
7
7
  <meta name="author" content="Igor Ganapolsky">
8
- <meta name="thumbgate-version" content="1.30.0">
8
+ <meta name="thumbgate-version" content="1.34.0">
9
9
  __GOOGLE_SITE_VERIFICATION_META__
10
10
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
11
11
  <link rel="canonical" href="__APP_ORIGIN__/">
@@ -39,7 +39,7 @@
39
39
  "applicationCategory": "DeveloperApplication",
40
40
  "operatingSystem": "macOS, Linux, Windows",
41
41
  "url": "__APP_ORIGIN__",
42
- "description": "Self-improving firewall for AI coding agents. ThumbGate captures feedback as local lessons, re-ranks relevant lessons for each action, promotes repeated negative patterns into warning or blocking gates, expires stale auto-promoted gates, and runs pre-action checks before shell, file, git, deploy, or API tool calls. Detected secret exfiltration and gate-bypass commands are denied by default; strict mode blocks matching destructive-action rules.",
42
+ "description": "Self-improving firewall for AI coding agents. ThumbGate captures feedback as local lessons, re-ranks relevant lessons for each action, promotes repeated negative patterns into warning or blocking gates, expires stale auto-promoted gates, and runs pre-action checks before shell, file, git, deploy, or API tool calls. Detected secret exfiltration and process-kill/environment-override self-disable commands (gate-process bypass) are denied by default; strict mode blocks matching destructive-action rules.",
43
43
  "codeRepository": "https://github.com/IgorGanapolsky/ThumbGate",
44
44
  "featureList": [
45
45
  "Pre-action PreToolUse checks for coding agents",
@@ -559,6 +559,104 @@
559
559
  h1 { font-size: 46px; }
560
560
  .checkout-card, .pro-card { padding: 22px 18px; }
561
561
  }
562
+
563
+ /* Visual-first conversion: thumbs + diagrams */
564
+ .hero-thumbs {
565
+ display: flex;
566
+ align-items: center;
567
+ justify-content: flex-start;
568
+ gap: 14px;
569
+ margin: 0 0 18px;
570
+ font-size: clamp(42px, 6vw, 64px);
571
+ line-height: 1;
572
+ filter: drop-shadow(0 0 28px rgba(34, 211, 238, 0.28));
573
+ }
574
+ .hero-thumbs span {
575
+ display: inline-flex;
576
+ width: 1.15em;
577
+ height: 1.15em;
578
+ align-items: center;
579
+ justify-content: center;
580
+ border-radius: 22%;
581
+ background: rgba(17, 23, 34, 0.9);
582
+ border: 1px solid rgba(39, 50, 70, 0.9);
583
+ }
584
+ .hero-thumbs .up { border-color: rgba(86, 227, 159, 0.55); box-shadow: 0 0 0 1px rgba(86, 227, 159, 0.12); }
585
+ .hero-thumbs .down { border-color: rgba(255, 100, 124, 0.55); box-shadow: 0 0 0 1px rgba(255, 100, 124, 0.12); }
586
+ .hero-visual {
587
+ margin: 22px 0 0;
588
+ border: 1px solid rgba(56, 215, 255, 0.22);
589
+ border-radius: 18px;
590
+ overflow: hidden;
591
+ background: #0a1018;
592
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
593
+ }
594
+ .hero-visual img, .diagram-frame img {
595
+ display: block;
596
+ width: 100%;
597
+ height: auto;
598
+ }
599
+ .diagram-strip { padding: 56px 0 12px; }
600
+ .diagram-frame {
601
+ margin: 28px 0 0;
602
+ border: 1px solid rgba(56, 215, 255, 0.2);
603
+ border-radius: 20px;
604
+ overflow-x: auto;
605
+ overflow-y: hidden;
606
+ -webkit-overflow-scrolling: touch;
607
+ background: #0a0a0b;
608
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
609
+ }
610
+ .diagram-frame img {
611
+ min-width: 640px;
612
+ }
613
+ .hero-visual img {
614
+ min-width: 0;
615
+ }
616
+ @media (max-width: 720px) {
617
+ .diagram-frame img {
618
+ min-width: 720px;
619
+ }
620
+ .hero-visual {
621
+ margin-left: -4px;
622
+ margin-right: -4px;
623
+ }
624
+ }
625
+ .diagram-caption {
626
+ margin: 14px 0 0;
627
+ color: var(--muted);
628
+ font-size: 14px;
629
+ text-align: center;
630
+ }
631
+ .self-improve-callout {
632
+ margin: 28px 0 0;
633
+ padding: 20px 22px;
634
+ border: 1px solid rgba(86, 227, 159, 0.3);
635
+ border-radius: 14px;
636
+ background: rgba(86, 227, 159, 0.06);
637
+ color: #c5ceda;
638
+ font-size: 16px;
639
+ line-height: 1.55;
640
+ max-width: 820px;
641
+ }
642
+ .self-improve-callout strong { color: var(--green); }
643
+ .loop-step .thumb-icon {
644
+ display: inline-flex;
645
+ font-size: 28px;
646
+ line-height: 1;
647
+ margin-bottom: 4px;
648
+ }
649
+ .visual-pair {
650
+ display: grid;
651
+ grid-template-columns: 1.1fr 0.9fr;
652
+ gap: 18px;
653
+ margin-top: 28px;
654
+ align-items: stretch;
655
+ }
656
+ @media (max-width: 900px) {
657
+ .visual-pair { grid-template-columns: 1fr; }
658
+ .hero-thumbs { justify-content: center; }
659
+ }
562
660
  </style>
563
661
  </head>
564
662
  <body data-revenue-assist="off">
@@ -584,6 +682,7 @@
584
682
  <div class="shell hero-grid">
585
683
  <div class="hero-copy">
586
684
  <div class="eyebrow">Now live · Pre-action gates · Not a prompt · Not a postmortem</div>
685
+ <div class="hero-thumbs" aria-hidden="true"><span class="up">👍</span><span class="down">👎</span></div>
587
686
  <h1>Stop AI agent mistakes before they cost you.</h1>
588
687
  <p class="hero-lede">Hard allow/deny at the <strong>tool-call boundary</strong>. Audit entry written at decision time. Every approval teaches the next gate.</p>
589
688
  <p class="fit-line"><strong>Dual path (buy + book):</strong> <strong>$499 Diagnostic</strong> maps one expensive failure on Claude Code, Cursor, Codex, or similar agents and installs a hard gate with proof — or self-serve <strong>Pro at $19/mo</strong> if you already know the loop. Free local evaluate stays free.</p>
@@ -593,7 +692,11 @@
593
692
  <span class="spec-chip"><strong>Promote</strong> gates</span>
594
693
  <span class="spec-chip"><strong>Block</strong> next action</span>
595
694
  </div>
695
+ <figure class="hero-visual">
696
+ <img src="/assets/diagrams/hero-thumbs.svg" width="640" height="360" alt="ThumbGate: thumbs up and thumbs down become a pre-action gate that can ALLOW, WARN, or DENY before the tool call runs" loading="eager" decoding="async">
697
+ </figure>
596
698
  <a class="proof-link" href="#proof">See a strict-mode deny example ↓</a>
699
+ <a class="proof-link-secondary" href="#how-it-works" style="margin-left:14px;color:var(--cyan);font-weight:750;text-decoration:none">See how the self-improving loop works ↓</a>
597
700
  <p class="install-hint"><code>npx thumbgate init</code> free local evaluate · first hard gate usually minutes after install · <a href="/pricing">how we stack up</a></p>
598
701
  <p class="install-hint" id="thumbgate-dashboard-command" style="margin-top:10px;">Dashboard: <code>npx thumbgate dashboard --open</code> · <code>/thumbgate-dashboard</code> · bin <code>thumbgate-dashboard</code> if global · <a href="/dashboard#insights">demo</a></p>
599
702
  </div>
@@ -638,34 +741,60 @@
638
741
  </div>
639
742
  </section>
640
743
 
744
+ <section class="diagram-strip" id="before-after" aria-label="Before and after ThumbGate">
745
+ <div class="shell">
746
+ <div class="section-kicker">Why it’s called ThumbGate</div>
747
+ <h2 class="section-title">Thumbs teach. The gate enforces.</h2>
748
+ <div class="visual-pair">
749
+ <figure class="diagram-frame">
750
+ <img src="/assets/diagrams/before-after.svg" width="960" height="300" alt="Without ThumbGate the same AI agent mistake repeats. With ThumbGate one thumbs-down becomes a local pre-action gate that can refresh or expire." loading="lazy" decoding="async">
751
+ </figure>
752
+ <figure class="diagram-frame">
753
+ <img src="/assets/diagrams/loop.svg" width="960" height="300" alt="Agent proposes a tool, ThumbGate decides ALLOW WARN or DENY, thumbs feedback becomes a rule that closes the loop." loading="lazy" decoding="async">
754
+ </figure>
755
+ </div>
756
+ </div>
757
+ </section>
758
+
641
759
  <section id="how-it-works">
642
760
  <div class="shell">
643
761
  <div class="section-kicker">Self-improving product loop</div>
644
762
  <h2 class="section-title">Pre-action checks—and the systems that refine them.</h2>
645
- <p class="section-lede">No governance novel. Corrections become reviewable local lessons; relevant lessons are re-ranked; repeated negative patterns can become explicit gates; and the next action is checked before execution. <strong style="color:var(--text)">Click a step</strong> to see what happens under the hood.</p>
763
+ <p class="section-lede">Corrections become local lessons; repeated negatives become gates; the next action is checked before execution. <strong style="color:var(--text)">Click a step</strong> for the under-the-hood demo.</p>
764
+ <figure class="diagram-frame" style="margin-top:28px">
765
+ <img src="/assets/diagrams/self-improving-thumbs-loop.svg" width="1100" height="420" alt="Self-improving ThumbGate loop: thumbs up or thumbs down become a local lesson, then a prevention rule, then ALLOW WARN or DENY on the next tool call. The loop closes under your control without retraining the model." loading="lazy" decoding="async">
766
+ </figure>
767
+ <div class="self-improve-callout">
768
+ <strong>Is it really self-improving?</strong>
769
+ Yes — the control layer, not the LLM. 👍/👎 → local lesson → ranked rule → next tool call gated. No model retrain.
770
+ </div>
646
771
  <div class="loop" role="tablist" aria-label="Self-improving product loop — click a step for under-the-hood demo">
647
772
  <!-- Cards are divs (not <button>) so headings/chips stay valid HTML; browsers
648
773
  auto-close <button> around <h3>/<div>, which broke click targets. -->
649
774
  <div class="loop-step" role="tab" tabindex="0" id="loop-tab-1" data-loop-step="1" aria-selected="false" aria-controls="loop-panel" aria-expanded="false">
650
775
  <span class="loop-number">1</span>
776
+ <div class="thumb-icon" aria-hidden="true">👍👎</div>
651
777
  <h3>Capture feedback</h3>
652
- <p>Record explicit 👍 or 👎 feedback with the action and outcome context that made it useful or wrong.</p>
778
+ <p>Record explicit 👍 or 👎 with the action and outcome context.</p>
653
779
  <span class="loop-hint">Under the hood ↓</span>
654
780
  </div>
655
781
  <div class="loop-step" role="tab" tabindex="0" id="loop-tab-2" data-loop-step="2" aria-selected="false" aria-controls="loop-panel" aria-expanded="false">
656
782
  <span class="loop-number">2</span>
783
+ <div class="thumb-icon" aria-hidden="true">🧠</div>
657
784
  <h3>Remember locally</h3>
658
- <p>Store a reviewable lesson that survives sessions and model changes without sending the control history into model weights.</p>
785
+ <p>Store a reviewable lesson that survives sessions without writing into model weights.</p>
659
786
  <span class="loop-hint">Under the hood ↓</span>
660
787
  </div>
661
788
  <div class="loop-step" role="tab" tabindex="0" id="loop-tab-3" data-loop-step="3" aria-selected="false" aria-controls="loop-panel" aria-expanded="false">
662
789
  <span class="loop-number">3</span>
790
+ <div class="thumb-icon" aria-hidden="true">📈</div>
663
791
  <h3>Rank and refine</h3>
664
- <p>Re-rank relevant lessons for the action. Repeated negative patterns can promote from warnings to blocking gates; stale gates expire.</p>
792
+ <p>Re-rank lessons for the action. Repeated negatives can promote to blocking gates; stale gates expire.</p>
665
793
  <span class="loop-hint">Under the hood ↓</span>
666
794
  </div>
667
795
  <div class="loop-step" role="tab" tabindex="0" id="loop-tab-4" data-loop-step="4" aria-selected="false" aria-controls="loop-panel" aria-expanded="false">
668
796
  <span class="loop-number">4</span>
797
+ <div class="thumb-icon" aria-hidden="true">🛡️</div>
669
798
  <h3>Gate the next action</h3>
670
799
  <p>The action is allowed, warned, or denied before the tool proceeds.</p>
671
800
  <div class="decisions" aria-label="Possible decisions">
@@ -717,7 +846,7 @@
717
846
  <div class="shell">
718
847
  <div class="section-kicker">What the $499 enterprise gate buys</div>
719
848
  <h2 class="section-title">A managed gate for one painful workflow.</h2>
720
- <p class="section-lede">This is the enterprise entry offer for one workflow, not an org-wide hosted platform license.</p>
849
+ <p class="section-lede">Enterprise entry for one workflownot an org-wide platform license.</p>
721
850
  <div class="deliverables">
722
851
  <article class="deliverable">
723
852
  <h3>Failure map</h3>
@@ -760,8 +889,8 @@ next decision recorded before execution</pre>
760
889
  </div>
761
890
  </div>
762
891
  <div class="proof-copy">
763
- <p>Detected secret exfiltration and attempts to kill or bypass the gate process are denied by default. Matching destructive actions warn by default and deny in strict mode.</p>
764
- <p>Managed strict-mode example. It is not a claim that every free install blocks every risky command automatically.</p>
892
+ <p>Detected secret exfiltration and gate-process bypass attempts are denied by default (process-kill/environment-override self-disable floors). Matching destructive actions warn by default and deny in strict mode.</p>
893
+ <p>Managed strict-mode examplenot a claim that every free install blocks every risky command automatically.</p>
765
894
  <a href="https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md" target="_blank" rel="noopener">Read the test-backed verification evidence →</a>
766
895
  <p style="margin-top:12px;"><a href="/whitepaper">White paper</a> · <a href="/eval-scorecard">Scorecard</a> · <a href="/architecture">Architecture</a> · <a href="/case-studies">Cases</a></p>
767
896
  </div>
@@ -802,7 +931,7 @@ next decision recorded before execution</pre>
802
931
  <div class="shell">
803
932
  <div class="section-kicker">Two cash paths</div>
804
933
  <h2 class="section-title">Start self-serve, or bring the failure that already cost you time.</h2>
805
- <p class="section-lede">Pro is the self-serve subscription for operators. The $499 enterprise gate is a managed install for one painful workflow—not another policy deck.</p>
934
+ <p class="section-lede">Pro is self-serve for operators. The $499 gate is a managed install for one painful workflow—not a policy deck.</p>
806
935
  <div style="display:flex;flex-wrap:wrap;gap:12px;justify-content:center;margin-top:22px">
807
936
  <a class="nav-buy" href="/checkout/pro?utm_source=website&amp;utm_medium=homepage_final&amp;utm_campaign=pro_self_serve&amp;cta_id=final_pro_buy&amp;cta_placement=final&amp;plan_id=pro" data-offer-link data-cta-id="final_pro_buy">Start Pro — $19/mo</a>
808
937
  <a class="nav-enterprise" href="#enterprise-gate" data-offer-link data-cta-id="final_diagnostic_buy">Buy the $499 enterprise gate</a>
@@ -813,7 +942,7 @@ next decision recorded before execution</pre>
813
942
 
814
943
  <footer>
815
944
  <div class="shell footer-inner">
816
- <span>ThumbGate · MIT License · npm v1.30.0</span>
945
+ <span>ThumbGate · MIT License · npm v1.34.0</span>
817
946
  <div class="footer-links">
818
947
  <a href="https://github.com/IgorGanapolsky/ThumbGate" target="_blank" rel="noopener">GitHub</a>
819
948
  <a href="/guide">Technical setup</a>
@@ -1020,7 +1149,7 @@ next decision recorded before execution</pre>
1020
1149
  'mode <span class="y">warn-by-default</span>\n' +
1021
1150
  'decision <span class="y">WARN</span>\n' +
1022
1151
  'note logged + flagged; tool may still proceed\n\n' +
1023
- 'secret exfil / gate-bypass floors → <span class="r">DENY</span> regardless',
1152
+ 'secret exfil / gate process bypass floors → <span class="r">DENY</span> regardless',
1024
1153
  bodyStrict:
1025
1154
  'proposed git push --force origin main\n' +
1026
1155
  'hook PreToolUse → gates-engine\n' +
@@ -1030,7 +1159,7 @@ next decision recorded before execution</pre>
1030
1159
  'next blocked before execution',
1031
1160
  facts: [
1032
1161
  'PreToolUse hooks check Bash / Edit / Write before the tool runs',
1033
- 'Outcomes: ALLOW, WARN, or DENY',
1162
+ 'Outcomes: allowed, warned, or denied (ALLOW / WARN / DENY)',
1034
1163
  'Default install is warn + audit; hard deny needs strict mode (plus secret floors)',
1035
1164
  ],
1036
1165
  honest: 'Default is not “block everything.” Default warns. Strict mode denies matching high-risk actions. Secrets/exfil still hard-deny.',
@@ -25,7 +25,7 @@
25
25
  "alternateName": "thumbgate",
26
26
  "applicationCategory": "DeveloperApplication",
27
27
  "operatingSystem": "Cross-platform, Node.js >=18.18.0",
28
- "softwareVersion": "1.30.0",
28
+ "softwareVersion": "1.34.0",
29
29
  "url": "https://thumbgate.ai/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
@@ -195,6 +195,7 @@
195
195
  <a href="/learn">Learn</a>
196
196
  <a href="/compare">Compare</a>
197
197
  <a href="/numbers">Numbers</a>
198
+ <a href="/evaluations">Evaluations</a>
198
199
  <a href="/dashboard">Dashboard</a>
199
200
  <a href="/pro">Pro</a>
200
201
  </nav>
@@ -202,7 +203,7 @@
202
203
  <main class="container">
203
204
  <h1>The Numbers</h1>
204
205
  <p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
205
- <div class="freshness">Updated: 2026-05-07 · Version 1.30.0</div>
206
+ <div class="freshness">Updated: 2026-05-07 · Version 1.34.0</div>
206
207
  <div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
207
208
 
208
209
  <h2>Gate enforcement</h2>