thumbgate 1.23.1 → 1.25.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/marketplace.json +5 -5
- package/.claude-plugin/plugin.json +2 -2
- package/.well-known/llms.txt +26 -11
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +84 -38
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +39 -16
- package/bin/postinstall.js +11 -22
- package/config/gate-templates.json +72 -0
- package/config/github-about.json +1 -1
- package/config/post-deploy-marketing-pages.json +6 -1
- package/package.json +20 -8
- package/public/agent-manager.html +3 -3
- package/public/agents-cost-savings.html +3 -3
- package/public/ai-malpractice-prevention.html +335 -7
- package/public/blog.html +3 -3
- package/public/codex-enterprise.html +3 -3
- package/public/codex-plugin.html +4 -4
- package/public/compare.html +6 -6
- package/public/dashboard.html +211 -126
- package/public/guide.html +5 -5
- package/public/index.html +156 -47
- package/public/learn.html +24 -10
- package/public/lessons.html +2 -2
- package/public/numbers.html +6 -6
- package/public/pricing.html +6 -5
- package/public/pro.html +1 -0
- package/scripts/billing.js +17 -0
- package/scripts/commercial-offer.js +4 -1
- package/scripts/dashboard.js +53 -1
- package/scripts/gates-engine.js +101 -16
- package/scripts/mcp-oauth.js +293 -0
- package/scripts/plausible-server-events.js +2 -1
- package/scripts/rate-limiter.js +16 -12
- package/scripts/security-scanner.js +80 -10
- package/scripts/seo-gsd.js +167 -1
- package/scripts/telemetry-analytics.js +310 -0
- package/scripts/tool-registry.js +35 -1
- package/scripts/vector-store.js +1 -0
- package/scripts/visitor-journey.js +172 -0
- package/src/api/server.js +226 -31
- package/adapters/chatgpt/openapi.yaml +0 -1705
package/public/numbers.html
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
<meta property="og:title" content="ThumbGate — The Numbers">
|
|
11
11
|
<meta property="og:description" content="Generated first-party operational snapshot: configured gates, recorded interventions, and explicit zero-evidence caveats.">
|
|
12
12
|
<meta property="og:type" content="website">
|
|
13
|
-
<meta property="og:url" content="https://thumbgate
|
|
13
|
+
<meta property="og:url" content="https://thumbgate.ai/numbers">
|
|
14
14
|
<meta name="twitter:card" content="summary_large_image">
|
|
15
|
-
<link rel="canonical" href="https://thumbgate
|
|
15
|
+
<link rel="canonical" href="https://thumbgate.ai/numbers">
|
|
16
16
|
<link rel="icon" type="image/png" href="/thumbgate-icon.png">
|
|
17
17
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
18
18
|
<script defer data-domain="thumbgate-production.up.railway.app" src="https://plausible.io/js/script.js"></script>
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"alternateName": "thumbgate",
|
|
26
26
|
"applicationCategory": "DeveloperApplication",
|
|
27
27
|
"operatingSystem": "Cross-platform, Node.js >=18.18.0",
|
|
28
|
-
"softwareVersion": "1.
|
|
29
|
-
"url": "https://thumbgate
|
|
28
|
+
"softwareVersion": "1.25.0",
|
|
29
|
+
"url": "https://thumbgate.ai/numbers",
|
|
30
30
|
"dateModified": "2026-05-07",
|
|
31
31
|
"creator": {
|
|
32
32
|
"@type": "Person",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@type": "Dataset",
|
|
47
47
|
"name": "ThumbGate Operational Snapshot",
|
|
48
48
|
"description": "First-party operational snapshot from the ThumbGate pre-action check runtime: configured checks, recorded block/warn events, estimated token savings from recorded blocks, and Bayes error rate when the sample supports it.",
|
|
49
|
-
"url": "https://thumbgate
|
|
49
|
+
"url": "https://thumbgate.ai/numbers",
|
|
50
50
|
"license": "https://opensource.org/licenses/MIT",
|
|
51
51
|
"creator": {
|
|
52
52
|
"@type": "Person",
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
<main class="container">
|
|
203
203
|
<h1>The Numbers</h1>
|
|
204
204
|
<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.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 · Version 1.25.0</div>
|
|
206
206
|
<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
207
|
|
|
208
208
|
<h2>Gate enforcement</h2>
|
package/public/pricing.html
CHANGED
|
@@ -12,6 +12,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
12
12
|
<meta property="og:url" content="__APP_ORIGIN__/pricing">
|
|
13
13
|
<meta property="og:image" content="/og.png">
|
|
14
14
|
<link rel="canonical" href="__APP_ORIGIN__/pricing">
|
|
15
|
+
<link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
|
|
15
16
|
<link rel="icon" type="image/png" href="/thumbgate-icon.png">
|
|
16
17
|
<link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
|
|
17
18
|
|
|
@@ -49,7 +50,7 @@ __GA_BOOTSTRAP__
|
|
|
49
50
|
"@context": "https://schema.org",
|
|
50
51
|
"@type": "FAQPage",
|
|
51
52
|
"mainEntity": [
|
|
52
|
-
{ "@type": "Question", "name": "What does Pro add over the free CLI?", "acceptedAnswer": { "@type": "Answer", "text": "Free gives you
|
|
53
|
+
{ "@type": "Question", "name": "What does Pro add over the free CLI?", "acceptedAnswer": { "@type": "Answer", "text": "Free gives you 5 captures/day and 3 active rules, running entirely on your machine. Pro is the hosted layer: unlimited captures, unlimited rules, lesson sync across machines, a dashboard without self-hosting, managed adapter updates, and DPO export. You're paying for infrastructure we run, not features we hide." } },
|
|
53
54
|
{ "@type": "Question", "name": "Does ThumbGate send my code to the cloud?", "acceptedAnswer": { "@type": "Answer", "text": "No. The CLI is local-first — no data leaves your machine. Pro and Team add hosted sync for dashboards and shared lessons, but your source code stays local." } },
|
|
54
55
|
{ "@type": "Question", "name": "When should I pick Team over Pro?", "acceptedAnswer": { "@type": "Answer", "text": "When one engineer's correction should protect the whole team. Team shares the lesson database across seats so a fix in one repo prevents the same mistake in every repo." } },
|
|
55
56
|
{ "@type": "Question", "name": "Can I cancel anytime?", "acceptedAnswer": { "@type": "Answer", "text": "Yes. Pro and Team are month-to-month with a 7-day refund window on the first charge. Cancel from the billing portal and your subscription ends at the period close." } }
|
|
@@ -237,8 +238,8 @@ __GA_BOOTSTRAP__
|
|
|
237
238
|
<div class="price">$0</div>
|
|
238
239
|
<div class="price-sub">Block repeated mistakes daily. Forever free for solo devs.</div>
|
|
239
240
|
<ul>
|
|
240
|
-
<li>
|
|
241
|
-
<li>Up to
|
|
241
|
+
<li>5 feedback captures/day (25 total) — enough to see the value</li>
|
|
242
|
+
<li>Up to 3 active prevention rules</li>
|
|
242
243
|
<li>All MCP integrations (Claude Code, Cursor, Codex, Gemini, Amp)</li>
|
|
243
244
|
<li>PreToolUse hook blocking with built-in safety checks</li>
|
|
244
245
|
<li>Runs 100% local — no account, no signup, no data leaves your machine</li>
|
|
@@ -257,7 +258,7 @@ __GA_BOOTSTRAP__
|
|
|
257
258
|
<li><strong>Hosted lesson sync</strong> — corrections follow you across machines, no manual export</li>
|
|
258
259
|
<li><strong>Managed adapter matrix</strong> — we track runtime changes in Claude Code, Cursor, Codex, Gemini, Amp so you don't</li>
|
|
259
260
|
<li><strong>Hosted dashboard</strong> — see every blocked action, every rule that fired, without running your own server</li>
|
|
260
|
-
<li><strong>Unlimited prevention rules</strong> — free caps at
|
|
261
|
+
<li><strong>Unlimited prevention rules</strong> — free caps at 3 auto-promoted rules</li>
|
|
261
262
|
<li><strong>DPO + HuggingFace export</strong> — training data from your real corrections</li>
|
|
262
263
|
<li><strong>Auto-connect</strong> — new agent surfaces appear automatically after setup</li>
|
|
263
264
|
<li>7-day refund window. Cancel anytime.</li>
|
|
@@ -296,7 +297,7 @@ __GA_BOOTSTRAP__
|
|
|
296
297
|
<div class="faq-list">
|
|
297
298
|
<div class="faq-item">
|
|
298
299
|
<div class="faq-q">What does Pro add over the free CLI?</div>
|
|
299
|
-
<div class="faq-a">Free gives you
|
|
300
|
+
<div class="faq-a">Free gives you 5 captures/day and 3 active rules, running entirely on your machine. Pro is the hosted layer: unlimited captures, unlimited rules, lesson sync across machines, a dashboard without self-hosting, managed adapter updates, and DPO export. You're paying for infrastructure we run, not features we hide.</div>
|
|
300
301
|
</div>
|
|
301
302
|
<div class="faq-item">
|
|
302
303
|
<div class="faq-q">Does ThumbGate send my code to the cloud?</div>
|
package/public/pro.html
CHANGED
|
@@ -11,6 +11,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
11
11
|
<meta property="og:type" content="website">
|
|
12
12
|
<meta property="og:url" content="__APP_ORIGIN__/pro">
|
|
13
13
|
<link rel="canonical" href="__APP_ORIGIN__/pro">
|
|
14
|
+
<link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
|
|
14
15
|
<link rel="icon" type="image/png" href="/thumbgate-icon.png">
|
|
15
16
|
<link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
|
|
16
17
|
<meta property="og:image" content="/og.png">
|
package/scripts/billing.js
CHANGED
|
@@ -51,6 +51,7 @@ const {
|
|
|
51
51
|
} = require('./analytics-window');
|
|
52
52
|
const { ensureParentDir } = require('./fs-utils');
|
|
53
53
|
const mailer = require('./mailer');
|
|
54
|
+
const { recordCheckoutFunnelEvent } = require('./plausible-server-events');
|
|
54
55
|
|
|
55
56
|
function loadWorkflowSprintIntakeModule() {
|
|
56
57
|
const modulePath = path.resolve(__dirname, 'workflow-sprint-intake.js');
|
|
@@ -3038,6 +3039,22 @@ async function handleWebhook(rawBody, signature) {
|
|
|
3038
3039
|
attribution,
|
|
3039
3040
|
});
|
|
3040
3041
|
}
|
|
3042
|
+
// Fire Plausible purchase event so the funnel poller can measure
|
|
3043
|
+
// end-to-end conversion: visitor → CTA → checkout → email → Stripe → purchase.
|
|
3044
|
+
// Fire-and-forget (never blocks the webhook response).
|
|
3045
|
+
void recordCheckoutFunnelEvent('purchase', {
|
|
3046
|
+
page: '/success',
|
|
3047
|
+
props: {
|
|
3048
|
+
sessionId: session.id,
|
|
3049
|
+
customerId,
|
|
3050
|
+
traceId: traceId || '',
|
|
3051
|
+
packId: packId || '',
|
|
3052
|
+
amount: session.amount_total != null ? String(session.amount_total) : '',
|
|
3053
|
+
currency: session.currency || '',
|
|
3054
|
+
...attribution,
|
|
3055
|
+
},
|
|
3056
|
+
});
|
|
3057
|
+
|
|
3041
3058
|
return {
|
|
3042
3059
|
handled: true,
|
|
3043
3060
|
action: 'provisioned_api_key',
|
|
@@ -61,10 +61,12 @@ function buildCaptureReceipt({ signal, feedbackId, memoryId, actionType } = {})
|
|
|
61
61
|
` Stored proof : ${normalizedSignal} feedback${feedbackId ? ` (${feedbackId})` : ''}`,
|
|
62
62
|
memoryId ? ` Local memory : ${memoryId}` : ' Local memory : saved locally',
|
|
63
63
|
actionType ? ` Rule pressure : ${actionType}` : ' Rule pressure : available for promotion',
|
|
64
|
+
' Free today : this proof protects this local machine',
|
|
65
|
+
' Pro sync : keep this lesson, rule, and dashboard synced across machines and agent runtimes',
|
|
64
66
|
' Next proof : npx thumbgate stats',
|
|
65
67
|
' Cost proof : npx thumbgate cost',
|
|
66
68
|
'',
|
|
67
|
-
` Solo Pro : ${PRO_PRICE_LABEL} for
|
|
69
|
+
` Solo Pro : ${PRO_PRICE_LABEL} for hosted sync, search, dashboard, and exports`,
|
|
68
70
|
` Upgrade : ${trackedProUrl('cli_capture_receipt', actionType || normalizedSignal.toLowerCase())}`,
|
|
69
71
|
` Team path : ${TEAM_PRICE_LABEL}; start with one repeated workflow failure`,
|
|
70
72
|
' https://thumbgate.ai/#workflow-sprint-intake',
|
|
@@ -98,6 +100,7 @@ function buildStatsReceipt(stats = {}) {
|
|
|
98
100
|
lines.push(` Failure pressure : ${negatives} negative ${pluralize(negatives, 'signal')}`);
|
|
99
101
|
}
|
|
100
102
|
lines.push(' Show the buyer : npx thumbgate cost');
|
|
103
|
+
lines.push(' Pro sync value : keep these lessons/rules visible across laptops, CI, containers, and agent runtimes');
|
|
101
104
|
lines.push(` Solo Pro : ${trackedProUrl('cli_stats_receipt', 'proof_seen')}`);
|
|
102
105
|
lines.push(' Team workflow : https://thumbgate.ai/#workflow-sprint-intake');
|
|
103
106
|
lines.push('');
|
package/scripts/dashboard.js
CHANGED
|
@@ -967,6 +967,20 @@ function computeAnalyticsSummary(feedbackDir, options = {}) {
|
|
|
967
967
|
return {
|
|
968
968
|
window: telemetry.window || analyticsWindow,
|
|
969
969
|
telemetry,
|
|
970
|
+
firstPartyTrafficQuality: telemetry.trafficQuality || {
|
|
971
|
+
rawEvents: telemetry.totalEvents || 0,
|
|
972
|
+
externalEvents: 0,
|
|
973
|
+
excludedEvents: 0,
|
|
974
|
+
exclusionRate: 0,
|
|
975
|
+
byAudience: {},
|
|
976
|
+
byExclusionReason: {},
|
|
977
|
+
external: {
|
|
978
|
+
uniqueVisitors: 0,
|
|
979
|
+
pageViews: 0,
|
|
980
|
+
checkoutStarts: 0,
|
|
981
|
+
},
|
|
982
|
+
verdict: 'missing',
|
|
983
|
+
},
|
|
970
984
|
funnel: {
|
|
971
985
|
visitors: uniqueVisitors,
|
|
972
986
|
sessions: telemetry.visitors ? telemetry.visitors.uniqueSessions || 0 : 0,
|
|
@@ -1149,16 +1163,42 @@ function computeInstrumentationReadiness(analytics, billing) {
|
|
|
1149
1163
|
const coverage = billing && billing.coverage ? billing.coverage : {};
|
|
1150
1164
|
const telemetry = analytics.telemetry || {};
|
|
1151
1165
|
const visitors = telemetry.visitors || {};
|
|
1166
|
+
const quality = telemetry.trafficQuality || analytics.firstPartyTrafficQuality || {};
|
|
1167
|
+
const external = quality.external || {};
|
|
1152
1168
|
const cli = telemetry.cli || {};
|
|
1169
|
+
const plausibleExportConfigured = Boolean(
|
|
1170
|
+
process.env.PLAUSIBLE_API_KEY && (process.env.PLAUSIBLE_SITE_ID || process.env.PLAUSIBLE_DOMAIN)
|
|
1171
|
+
);
|
|
1172
|
+
const posthogExportConfigured = Boolean(
|
|
1173
|
+
(process.env.POSTHOG_PERSONAL_API_KEY || process.env.POSTHOG_API_KEY) && process.env.POSTHOG_PROJECT_ID
|
|
1174
|
+
);
|
|
1175
|
+
const ga4ExportConfigured = Boolean(
|
|
1176
|
+
process.env.GA4_PROPERTY_ID && (process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.GOOGLE_CLIENT_EMAIL)
|
|
1177
|
+
);
|
|
1178
|
+
const dashboardGradeExportReady = plausibleExportConfigured || posthogExportConfigured || ga4ExportConfigured;
|
|
1153
1179
|
|
|
1154
1180
|
return {
|
|
1155
|
-
plausibleConfigured: /plausible\.io\/js\/script
|
|
1181
|
+
plausibleConfigured: /plausible\.io\/js\/script(?:\.tagged-events)?\.js|\/js\/analytics\.js/.test(landingPage),
|
|
1156
1182
|
ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
|
|
1157
1183
|
googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
|
|
1158
1184
|
softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
|
|
1159
1185
|
faqSchemaPresent: /"@type": "FAQPage"/.test(landingPage),
|
|
1160
1186
|
telemetryEventsPresent: (telemetry.totalEvents || 0) > 0,
|
|
1161
1187
|
uniqueVisitorsTracked: visitors.uniqueVisitors || 0,
|
|
1188
|
+
rawTelemetryEvents: quality.rawEvents || telemetry.totalEvents || 0,
|
|
1189
|
+
externalTelemetryEvents: quality.externalEvents || 0,
|
|
1190
|
+
excludedTelemetryEvents: quality.excludedEvents || 0,
|
|
1191
|
+
externalVisitorsTracked: external.uniqueVisitors || 0,
|
|
1192
|
+
externalPageViewsTracked: external.pageViews || 0,
|
|
1193
|
+
externalCheckoutStartsTracked: external.checkoutStarts || 0,
|
|
1194
|
+
externalVisitorPathsTracked: Array.isArray(external.visitorPaths) ? external.visitorPaths.length : 0,
|
|
1195
|
+
internalTestPollutionRate: quality.exclusionRate || 0,
|
|
1196
|
+
trafficQualityVerdict: quality.verdict || 'missing',
|
|
1197
|
+
topExcludedTrafficReason: quality.topExclusionReason || null,
|
|
1198
|
+
plausibleExportConfigured,
|
|
1199
|
+
posthogExportConfigured,
|
|
1200
|
+
ga4ExportConfigured,
|
|
1201
|
+
dashboardGradeExportReady,
|
|
1162
1202
|
cliInstallsTracked: cli.uniqueInstalls || 0,
|
|
1163
1203
|
funnelEventsPresent: (analytics.reconciliation.telemetryCheckoutStarts || 0) > 0,
|
|
1164
1204
|
seoSignalsPresent: (analytics.seo.landingViews || 0) > 0,
|
|
@@ -1865,8 +1905,10 @@ function printDashboard(data) {
|
|
|
1865
1905
|
console.log('');
|
|
1866
1906
|
console.log('\uD83D\uDCBC Growth Analytics');
|
|
1867
1907
|
console.log(` Unique Visitors : ${analytics.trafficMetrics.visitors}`);
|
|
1908
|
+
console.log(` External Visitors: ${instrumentation.externalVisitorsTracked}`);
|
|
1868
1909
|
console.log(` Sessions : ${analytics.trafficMetrics.sessions}`);
|
|
1869
1910
|
console.log(` Page Views : ${analytics.trafficMetrics.pageViews}`);
|
|
1911
|
+
console.log(` External Views : ${instrumentation.externalPageViewsTracked}`);
|
|
1870
1912
|
console.log(` CTA Clicks : ${analytics.trafficMetrics.ctaClicks}`);
|
|
1871
1913
|
console.log(` Leads : ${analytics.funnel.acquisitionLeads}`);
|
|
1872
1914
|
console.log(` Sprint Leads : ${analytics.pipeline.workflowSprintLeads.total}`);
|
|
@@ -1877,6 +1919,7 @@ function printDashboard(data) {
|
|
|
1877
1919
|
console.log(` Booked Revenue : $${(analytics.revenue.bookedRevenueCents / 100).toFixed(2)}`);
|
|
1878
1920
|
console.log(` Matched Journeys : ${analytics.reconciliation.matchedPaidOrders}/${analytics.reconciliation.telemetryCheckoutStarts}`);
|
|
1879
1921
|
console.log(` Buyer Loss : ${analytics.buyerLoss.totalSignals}`);
|
|
1922
|
+
console.log(` Data Quality : ${analytics.firstPartyTrafficQuality.verdict} (${instrumentation.excludedTelemetryEvents}/${instrumentation.rawTelemetryEvents} excluded)`);
|
|
1880
1923
|
if (analytics.telemetry.visitors.topSource) {
|
|
1881
1924
|
console.log(` Top Source : ${analytics.telemetry.visitors.topSource.key} (${analytics.telemetry.visitors.topSource.count}\u00D7)`);
|
|
1882
1925
|
}
|
|
@@ -1896,6 +1939,15 @@ function printDashboard(data) {
|
|
|
1896
1939
|
console.log(` GA4 : ${instrumentation.ga4Configured ? 'configured' : 'missing'}`);
|
|
1897
1940
|
console.log(` Search Console : ${instrumentation.googleSearchConsoleConfigured ? 'configured' : 'missing'}`);
|
|
1898
1941
|
console.log(` Telemetry Events : ${instrumentation.telemetryEventsPresent ? instrumentation.uniqueVisitorsTracked : 0} visitors`);
|
|
1942
|
+
console.log(` Clean Visitors : ${instrumentation.externalVisitorsTracked} external (${Math.round((instrumentation.internalTestPollutionRate || 0) * 100)}% internal/test/bot events)`);
|
|
1943
|
+
console.log(` Clean Paths : ${instrumentation.externalVisitorPathsTracked} first-party paths`);
|
|
1944
|
+
if (instrumentation.topExcludedTrafficReason) {
|
|
1945
|
+
console.log(` Top Exclusion : ${instrumentation.topExcludedTrafficReason.key} (${instrumentation.topExcludedTrafficReason.count}\u00D7)`);
|
|
1946
|
+
}
|
|
1947
|
+
console.log(` Plausible Export : ${instrumentation.plausibleExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1948
|
+
console.log(` PostHog Export : ${instrumentation.posthogExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1949
|
+
console.log(` GA4 Export : ${instrumentation.ga4ExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1950
|
+
console.log(` Visitor Paths : ${instrumentation.dashboardGradeExportReady ? 'export-ready' : 'not dashboard-grade in repo'}`);
|
|
1899
1951
|
console.log(` SEO Signals : ${instrumentation.seoSignalsPresent ? analytics.seo.landingViews : 0}`);
|
|
1900
1952
|
console.log(` Buyer Loss : ${instrumentation.buyerLossSignalsPresent ? analytics.buyerLoss.totalSignals : 0}`);
|
|
1901
1953
|
console.log(` Attribution : ${Math.round((instrumentation.trafficAttributionCoverage || 0) * 100)}% page-view coverage`);
|
package/scripts/gates-engine.js
CHANGED
|
@@ -55,6 +55,9 @@ const {
|
|
|
55
55
|
const {
|
|
56
56
|
evaluateSecurityScan,
|
|
57
57
|
} = require('./security-scanner');
|
|
58
|
+
const { evaluateSequenceState } = loadOptionalModule('./sequence-guard', () => ({
|
|
59
|
+
evaluateSequenceState: () => null,
|
|
60
|
+
}));
|
|
58
61
|
const { getAutoGatesPath } = require('./auto-promote-gates');
|
|
59
62
|
const { recordAuditEvent, auditToFeedback } = require('./audit-trail');
|
|
60
63
|
|
|
@@ -1953,12 +1956,12 @@ function buildBlockActionProCta() {
|
|
|
1953
1956
|
if (totalBlocks < 5) return null; // Too early — let them experience the product
|
|
1954
1957
|
|
|
1955
1958
|
if (totalBlocks < 25) {
|
|
1956
|
-
return '\n\n💡 Pro:
|
|
1959
|
+
return '\n\n💡 Pro: keep this rule synced across laptops, CI, containers, and agent runtimes → thumbgate.ai/go/pro';
|
|
1957
1960
|
}
|
|
1958
1961
|
if (totalBlocks < 100) {
|
|
1959
|
-
return `\n\n💡 ${totalBlocks} actions blocked. Pro keeps rules
|
|
1962
|
+
return `\n\n💡 ${totalBlocks} actions blocked. Pro keeps these lessons/rules synced everywhere → thumbgate.ai/go/pro ($19/mo)`;
|
|
1960
1963
|
}
|
|
1961
|
-
return `\n\n💡 ${totalBlocks} mistakes caught. Your team could use
|
|
1964
|
+
return `\n\n💡 ${totalBlocks} mistakes caught. Your team could use shared hosted enforcement → thumbgate.ai/go/pro`;
|
|
1962
1965
|
} catch (_) {
|
|
1963
1966
|
return null;
|
|
1964
1967
|
}
|
|
@@ -2132,8 +2135,9 @@ function buildRecentCorrectiveActionsContext(options = {}) {
|
|
|
2132
2135
|
function buildRelevantLessonContext(toolName, toolInput) {
|
|
2133
2136
|
if (!toolName) return null;
|
|
2134
2137
|
|
|
2135
|
-
const { retrieveRelevantLessons } = loadOptionalModule('./lesson-retrieval', () => ({
|
|
2138
|
+
const { retrieveRelevantLessons, calculateRetrievalEntropy } = loadOptionalModule('./lesson-retrieval', () => ({
|
|
2136
2139
|
retrieveRelevantLessons: () => [],
|
|
2140
|
+
calculateRetrievalEntropy: () => 0,
|
|
2137
2141
|
}));
|
|
2138
2142
|
|
|
2139
2143
|
// Extract a searchable action context from the tool input
|
|
@@ -2142,23 +2146,77 @@ function buildRelevantLessonContext(toolName, toolInput) {
|
|
|
2142
2146
|
|
|
2143
2147
|
try {
|
|
2144
2148
|
const lessons = retrieveRelevantLessons(toolName, actionContext, { maxResults: 3 });
|
|
2145
|
-
// retrieveRelevantLessons already filters at relevanceScore > 0.1 internally;
|
|
2146
|
-
// any negative lesson that survives retrieval is relevant enough to surface.
|
|
2147
|
-
const negative = lessons.filter((l) => l.signal === 'negative');
|
|
2148
|
-
if (negative.length === 0) return null;
|
|
2149
|
-
|
|
2150
|
-
const formatted = negative.map((l) => {
|
|
2151
|
-
const title = (l.title || '').replace(/^MISTAKE:\s*/, '').slice(0, 140);
|
|
2152
|
-
const advice = extractAvoidanceAdvice(l.content);
|
|
2153
|
-
return advice ? ` • ${title}\n → ${advice}` : ` • ${title}`;
|
|
2154
|
-
});
|
|
2155
2149
|
|
|
2156
|
-
|
|
2150
|
+
const entropy = calculateRetrievalEntropy(lessons);
|
|
2151
|
+
if (entropy > 0.7) {
|
|
2152
|
+
recordStat("retrieval_entropy_high", "block");
|
|
2153
|
+
return { decision: "deny", gate: "knowledge-conflict-gate", message: "✗ THUMBGATE: Action blocked due to high Knowledge Entropy (conflicting past lessons).", severity: "high" };
|
|
2154
|
+
}
|
|
2155
|
+
return formatNegativeLessonContext(lessons);
|
|
2157
2156
|
} catch {
|
|
2158
2157
|
return null;
|
|
2159
2158
|
}
|
|
2160
2159
|
}
|
|
2161
2160
|
|
|
2161
|
+
/**
|
|
2162
|
+
* Async counterpart of buildRelevantLessonContext: uses HYBRID (dense embeddings +
|
|
2163
|
+
* lexical) retrieval so the agent is warned about semantically-related past mistakes
|
|
2164
|
+
* even when they share no keywords with the current action. Wired into runAsync.
|
|
2165
|
+
* Degrades to the lexical result automatically when no embedder is available.
|
|
2166
|
+
*/
|
|
2167
|
+
async function buildRelevantLessonContextAsync(toolName, toolInput) {
|
|
2168
|
+
if (!toolName) return null;
|
|
2169
|
+
|
|
2170
|
+
const { retrieveRelevantLessonsAsync, retrieveRelevantLessons, calculateRetrievalEntropy } = loadOptionalModule(
|
|
2171
|
+
'./lesson-retrieval',
|
|
2172
|
+
() => ({ retrieveRelevantLessonsAsync: null, retrieveRelevantLessons: () => [], calculateRetrievalEntropy: () => 0 }),
|
|
2173
|
+
);
|
|
2174
|
+
|
|
2175
|
+
const actionContext = extractActionContext(toolName, toolInput);
|
|
2176
|
+
if (!actionContext) return null;
|
|
2177
|
+
|
|
2178
|
+
try {
|
|
2179
|
+
const lessons = retrieveRelevantLessonsAsync
|
|
2180
|
+
? await retrieveRelevantLessonsAsync(toolName, actionContext, { maxResults: 3 })
|
|
2181
|
+
: retrieveRelevantLessons(toolName, actionContext, { maxResults: 3 });
|
|
2182
|
+
|
|
2183
|
+
// Knowledge Conflict Detection: if retrieved lessons have high sentiment entropy,
|
|
2184
|
+
// it indicates conflicting past evidence. Block and require human disambiguation.
|
|
2185
|
+
const entropy = calculateRetrievalEntropy(lessons);
|
|
2186
|
+
if (entropy > 0.7) {
|
|
2187
|
+
recordStat('retrieval_entropy_high', 'block');
|
|
2188
|
+
return {
|
|
2189
|
+
decision: 'deny',
|
|
2190
|
+
gate: 'knowledge-conflict-gate',
|
|
2191
|
+
message: '✗ THUMBGATE: Action blocked due to high Knowledge Entropy (conflicting past lessons). Please disambiguate your instructions or verify the intended behavior manually.',
|
|
2192
|
+
severity: 'high',
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
return formatNegativeLessonContext(lessons);
|
|
2197
|
+
} catch {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
/**
|
|
2203
|
+
* Shared formatter: render the negative (mistake) lessons that survived retrieval
|
|
2204
|
+
* into the PreToolUse warning block. Retrieval already filters by relevance, so any
|
|
2205
|
+
* negative lesson present is relevant enough to surface.
|
|
2206
|
+
*/
|
|
2207
|
+
function formatNegativeLessonContext(lessons) {
|
|
2208
|
+
const negative = (lessons || []).filter((l) => l.signal === 'negative');
|
|
2209
|
+
if (negative.length === 0) return null;
|
|
2210
|
+
|
|
2211
|
+
const formatted = negative.map((l) => {
|
|
2212
|
+
const title = (l.title || '').replace(/^MISTAKE:\s*/, '').slice(0, 140);
|
|
2213
|
+
const advice = extractAvoidanceAdvice(l.content);
|
|
2214
|
+
return advice ? ` • ${title}\n → ${advice}` : ` • ${title}`;
|
|
2215
|
+
});
|
|
2216
|
+
|
|
2217
|
+
return `[ThumbGate] Past mistakes relevant to this action — read before proceeding:\n${formatted.join('\n')}`;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2162
2220
|
function extractActionContext(toolName, toolInput) {
|
|
2163
2221
|
if (!toolInput) return toolName;
|
|
2164
2222
|
const parts = [toolName];
|
|
@@ -2196,6 +2254,12 @@ async function runAsync(input) {
|
|
|
2196
2254
|
|
|
2197
2255
|
const toolName = input.tool_name || '';
|
|
2198
2256
|
const toolInput = input.tool_input || {};
|
|
2257
|
+
|
|
2258
|
+
const sequenceGuard = evaluateSequenceState(toolName, toolInput);
|
|
2259
|
+
if (sequenceGuard && sequenceGuard.decision === 'deny') {
|
|
2260
|
+
return formatOutput(sequenceGuard);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2199
2263
|
const result = await evaluateGatesAsync(toolName, toolInput);
|
|
2200
2264
|
|
|
2201
2265
|
// Attach security warnings to allow/warn results
|
|
@@ -2208,11 +2272,18 @@ async function runAsync(input) {
|
|
|
2208
2272
|
}
|
|
2209
2273
|
}
|
|
2210
2274
|
|
|
2275
|
+
|
|
2211
2276
|
const behavioralContext = buildBehavioralContext();
|
|
2212
|
-
const lessonContext =
|
|
2277
|
+
const lessonContext = await buildRelevantLessonContextAsync(toolName, toolInput);
|
|
2278
|
+
|
|
2279
|
+
if (lessonContext && lessonContext.decision === "deny") {
|
|
2280
|
+
return formatOutput(lessonContext);
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2213
2283
|
const recentContext = buildRecentCorrectiveActionsContext();
|
|
2214
2284
|
const combinedContext = mergeContextStrings(lessonContext, recentContext, behavioralContext);
|
|
2215
2285
|
return formatOutput(result, combinedContext);
|
|
2286
|
+
|
|
2216
2287
|
}
|
|
2217
2288
|
|
|
2218
2289
|
function run(input) {
|
|
@@ -2229,6 +2300,12 @@ function run(input) {
|
|
|
2229
2300
|
|
|
2230
2301
|
const toolName = input.tool_name || '';
|
|
2231
2302
|
const toolInput = input.tool_input || {};
|
|
2303
|
+
|
|
2304
|
+
const sequenceGuard = evaluateSequenceState(toolName, toolInput);
|
|
2305
|
+
if (sequenceGuard && sequenceGuard.decision === 'deny') {
|
|
2306
|
+
return formatOutput(sequenceGuard);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2232
2309
|
const result = evaluateGates(toolName, toolInput);
|
|
2233
2310
|
|
|
2234
2311
|
// Attach security warnings to allow/warn results
|
|
@@ -2241,11 +2318,18 @@ function run(input) {
|
|
|
2241
2318
|
}
|
|
2242
2319
|
}
|
|
2243
2320
|
|
|
2321
|
+
|
|
2244
2322
|
const behavioralContext = buildBehavioralContext();
|
|
2245
2323
|
const lessonContext = buildRelevantLessonContext(toolName, toolInput);
|
|
2324
|
+
|
|
2325
|
+
if (lessonContext && lessonContext.decision === "deny") {
|
|
2326
|
+
return formatOutput(lessonContext);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2246
2329
|
const recentContext = buildRecentCorrectiveActionsContext();
|
|
2247
2330
|
const combinedContext = mergeContextStrings(lessonContext, recentContext, behavioralContext);
|
|
2248
2331
|
return formatOutput(result, combinedContext);
|
|
2332
|
+
|
|
2249
2333
|
}
|
|
2250
2334
|
|
|
2251
2335
|
// ---------------------------------------------------------------------------
|
|
@@ -2562,6 +2646,7 @@ module.exports = {
|
|
|
2562
2646
|
buildBehavioralContext,
|
|
2563
2647
|
buildRecentCorrectiveActionsContext,
|
|
2564
2648
|
buildRelevantLessonContext,
|
|
2649
|
+
buildRelevantLessonContextAsync,
|
|
2565
2650
|
extractActionContext,
|
|
2566
2651
|
extractAvoidanceAdvice,
|
|
2567
2652
|
mergeContextStrings,
|