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.
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +54 -16
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +105 -10
- package/adapters/opencode/opencode.json +1 -1
- package/bench/observability-eval-suite.json +2 -2
- package/bin/cli.js +168 -31
- package/config/evals/generation-quality-golden.json +95 -0
- package/config/evals/rag-answer-quality-golden.json +91 -0
- package/config/evals/retrieval-hybrid-ablation.json +66 -0
- package/config/evals/retrieval-ranking-golden.json +522 -0
- package/config/gates/claim-verifiers.example.json +42 -0
- package/config/gates/claim-verifiers.json +25 -0
- package/config/gates/default.json +217 -50
- package/config/mcp-allowlists.json +233 -206
- package/config/model-tiers.json +7 -2
- package/glama.json +6 -0
- package/hooks/hooks.json +1 -1
- package/package.json +69 -12
- package/public/assets/diagrams/before-after.svg +17 -16
- package/public/assets/diagrams/hero-thumbs.svg +68 -0
- package/public/assets/diagrams/loop.svg +19 -13
- package/public/assets/diagrams/self-improving-thumbs-loop.svg +105 -0
- package/public/compare.html +1 -0
- package/public/dashboard.html +126 -28
- package/public/evaluations.html +1 -1
- package/public/index.html +142 -13
- package/public/numbers.html +3 -2
- package/public/pricing.html +143 -30
- package/scripts/a-plus-evidence-scorecard.js +303 -0
- package/scripts/agent-readiness.js +110 -0
- package/scripts/async-eval-observability.js +36 -11
- package/scripts/audit-trail.js +37 -1
- package/scripts/auto-promote-gates.js +149 -34
- package/scripts/auto-wire-hooks.js +20 -8
- package/scripts/cli-schema.js +14 -0
- package/scripts/colbert-style-maxsim.js +236 -0
- package/scripts/cross-encoder-reranker.js +356 -126
- package/scripts/dashboard-chat.js +350 -17
- package/scripts/document-intake.js +283 -7
- package/scripts/eval-quality-suite.js +204 -0
- package/scripts/feedback-loop.js +115 -7
- package/scripts/feedback-paths.js +32 -13
- package/scripts/feedback-quality.js +53 -0
- package/scripts/feedback-schema.js +3 -0
- package/scripts/file-ledger-lock.js +130 -0
- package/scripts/filesystem-search.js +17 -7
- package/scripts/financial-control-plane.js +1514 -0
- package/scripts/gates-engine.js +202 -7
- package/scripts/gemini-embedding-policy.js +1 -0
- package/scripts/harness-tool-names.js +70 -0
- package/scripts/hook-runtime.js +15 -3
- package/scripts/hook-stop-anti-claim.js +63 -3
- package/scripts/human-escalation.js +353 -41
- package/scripts/lesson-db.js +16 -5
- package/scripts/lesson-embedding-index.js +67 -20
- package/scripts/lesson-embedding-maintenance.js +177 -0
- package/scripts/lesson-reranker.js +55 -9
- package/scripts/lesson-retrieval.js +305 -29
- package/scripts/lesson-search.js +22 -8
- package/scripts/llm-client.js +304 -15
- package/scripts/model-tier-router.js +593 -0
- package/scripts/pragmatic-hybrid-search.js +379 -0
- package/scripts/provider-action-normalizer.js +11 -4
- package/scripts/rag-document-pipeline.js +461 -0
- package/scripts/rag-structured-output.js +441 -0
- package/scripts/ragas-style-metrics.js +351 -0
- package/scripts/request-envelope.js +178 -0
- package/scripts/rerank-pipeline.js +370 -0
- package/scripts/rerank-quality-eval.js +155 -0
- package/scripts/retrieval-hybrid-ablation.js +120 -0
- package/scripts/retrieval-quality-tier.js +118 -0
- package/scripts/secret-scanner.js +395 -4
- package/scripts/self-distill-agent.js +7 -1
- package/scripts/self-healing-check.js +25 -0
- package/scripts/skill-packs.js +183 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/statusline-links.js +1 -1
- package/scripts/statusline.sh +8 -1
- package/scripts/telemetry-analytics.js +13 -1
- package/scripts/thumbgate-search.js +98 -6
- package/scripts/tier-budget-guard.js +186 -0
- package/scripts/tool-registry.js +141 -5
- package/scripts/universal-claim-evaluator.js +767 -0
- package/scripts/vector-store.js +154 -17
- package/scripts/verify-marketing-pages-deployed.js +85 -3
- package/scripts/workflow-sentinel.js +77 -11
- package/server.json +44 -0
- package/smithery.yaml +17 -0
- package/src/api/server.js +196 -13
package/public/dashboard.html
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
-
|
|
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', {
|
|
2470
|
-
|
|
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
|
-
|
|
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 =
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
});
|
package/public/evaluations.html
CHANGED
|
@@ -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 && 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.
|
|
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
|
|
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">
|
|
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 👎
|
|
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
|
|
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
|
|
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">
|
|
849
|
+
<p class="section-lede">Enterprise entry for one workflow—not 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
|
|
764
|
-
<p>Managed strict-mode example
|
|
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 example—not 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
|
|
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&utm_medium=homepage_final&utm_campaign=pro_self_serve&cta_id=final_pro_buy&cta_placement=final&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.
|
|
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
|
|
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:
|
|
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.',
|
package/public/numbers.html
CHANGED
|
@@ -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.
|
|
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.
|
|
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>
|