thumbgate 1.17.0 → 1.19.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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +26 -4
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/model-candidates.json +31 -0
- package/package.json +29 -8
- package/public/compare.html +6 -0
- package/public/federal.html +375 -0
- package/public/guide.html +2 -2
- package/public/index.html +49 -19
- package/public/learn.html +28 -0
- package/public/numbers.html +2 -2
- package/public/pro.html +4 -4
- package/scripts/activation-tracker.js +127 -0
- package/scripts/auto-promote-gates.js +4 -1
- package/scripts/feedback-loop.js +14 -1
- package/scripts/feedback-to-rules.js +11 -1
- package/scripts/feedback_quality_eval.py +725 -0
- package/scripts/memory-scope-readiness.js +315 -0
- package/scripts/plausible-server-events.js +162 -0
- package/scripts/seo-gsd.js +75 -2
- package/scripts/statusline-links.js +2 -0
- package/scripts/telemetry-analytics.js +1 -0
- package/src/api/server.js +557 -12
package/public/pro.html
CHANGED
|
@@ -718,7 +718,7 @@ __GA_BOOTSTRAP__
|
|
|
718
718
|
<a href="#pricing">Pricing</a>
|
|
719
719
|
<a href="#faq">FAQ</a>
|
|
720
720
|
<a href="/dashboard">Demo</a>
|
|
721
|
-
<a class="nav-cta" data-quick-read-link href="https://buy.stripe.com/
|
|
721
|
+
<a rel="nofollow noopener noreferrer" target="_blank" class="nav-cta" data-quick-read-link href="https://buy.stripe.com/5kQ7sL76s1eSaK55e33sI2H" onclick="sendFirstPartyTelemetry('quick_read_checkout_started',{ctaId:'pro_page_nav_quick_read_checkout',price:19});sendGa4Event('begin_checkout',{currency:'USD',value:19,items:[{item_id:'quick_read',item_name:'AI Agent Failure Quick Read'}]});">Pay $19 quick read</a>
|
|
722
722
|
</div>
|
|
723
723
|
</div>
|
|
724
724
|
</nav>
|
|
@@ -738,7 +738,7 @@ __GA_BOOTSTRAP__
|
|
|
738
738
|
<div class="proof-pill">Founder support on risky flows</div>
|
|
739
739
|
</div>
|
|
740
740
|
<div class="hero-actions">
|
|
741
|
-
<a class="btn-primary" data-quick-read-link href="https://buy.stripe.com/
|
|
741
|
+
<a rel="nofollow noopener noreferrer" target="_blank" class="btn-primary" data-quick-read-link href="https://buy.stripe.com/5kQ7sL76s1eSaK55e33sI2H" onclick="sendFirstPartyTelemetry('quick_read_checkout_started',{ctaId:'pro_page_hero_quick_read_checkout',price:19});sendGa4Event('begin_checkout',{currency:'USD',value:19,items:[{item_id:'quick_read',item_name:'AI Agent Failure Quick Read'}]});">Pay $19 quick read</a>
|
|
742
742
|
<a class="btn-secondary btn-pro-checkout" href="/checkout/pro?utm_source=website&utm_medium=pro_page_hero&utm_campaign=pro_pack&cta_id=pro_page_primary&cta_placement=hero&plan_id=pro&landing_path=%2Fpro">Start Pro dashboard</a>
|
|
743
743
|
<a class="btn-secondary btn-demo" href="/dashboard?utm_source=website&utm_medium=pro_page&utm_campaign=pro_pack">Open dashboard demo</a>
|
|
744
744
|
<a class="btn-ghost btn-free-path" href="/guide?utm_source=website&utm_medium=pro_page&utm_campaign=free_install">Stay on Free and install locally</a>
|
|
@@ -780,8 +780,8 @@ __GA_BOOTSTRAP__
|
|
|
780
780
|
<h3>Buy the paid diagnostic</h3>
|
|
781
781
|
<p>Skip self-serve Pro and pay for the smallest useful review now.</p>
|
|
782
782
|
<div class="price-stack">
|
|
783
|
-
<a class="btn-secondary" data-first-rule-link href="https://buy.stripe.com/
|
|
784
|
-
<a class="btn-secondary" data-quick-read-link href="https://buy.stripe.com/
|
|
783
|
+
<a rel="nofollow noopener noreferrer" target="_blank" class="btn-secondary" data-first-rule-link href="https://buy.stripe.com/fZu28rfCY6zcbO99uj3sI2G" onclick="sendFirstPartyTelemetry('first_failure_rule_checkout_started',{ctaId:'pro_page_first_failure_rule_checkout',price:1});sendGa4Event('begin_checkout',{currency:'USD',value:1,items:[{item_id:'first_failure_rule',item_name:'First AI Agent Failure Rule'}]});">Pay $1 first rule</a>
|
|
784
|
+
<a rel="nofollow noopener noreferrer" target="_blank" class="btn-secondary" data-quick-read-link href="https://buy.stripe.com/5kQ7sL76s1eSaK55e33sI2H" onclick="sendFirstPartyTelemetry('quick_read_checkout_started',{ctaId:'pro_page_quick_read_checkout',price:19});">Pay $19 quick read</a>
|
|
785
785
|
<a class="btn-primary" data-sprint-diagnostic-link href="__SPRINT_DIAGNOSTIC_CHECKOUT_URL__" onclick="sendFirstPartyTelemetry('workflow_sprint_diagnostic_checkout_started',{ctaId:'pro_page_sprint_diagnostic_checkout'});sendGa4Event('begin_checkout',{currency:'USD',value:__SPRINT_DIAGNOSTIC_PRICE_DOLLARS__});">Pay $__SPRINT_DIAGNOSTIC_PRICE_DOLLARS__ diagnostic</a>
|
|
786
786
|
<a class="btn-secondary" data-workflow-sprint-link href="__WORKFLOW_SPRINT_CHECKOUT_URL__" onclick="sendFirstPartyTelemetry('workflow_sprint_checkout_started',{ctaId:'pro_page_workflow_sprint_checkout'});sendGa4Event('begin_checkout',{currency:'USD',value:__WORKFLOW_SPRINT_PRICE_DOLLARS__});">Pay $__WORKFLOW_SPRINT_PRICE_DOLLARS__ sprint</a>
|
|
787
787
|
</div>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Activation tracker — fires `activation_first_rule_promoted` exactly once
|
|
5
|
+
* per install, the first time a prevention rule auto-promotes from feedback.
|
|
6
|
+
*
|
|
7
|
+
* Why: v1.17.0 opened the free tier (5 active rules, unlimited captures).
|
|
8
|
+
* The metric that decides whether that worked is the % of `npx thumbgate init`
|
|
9
|
+
* runs that produce a first auto-promoted prevention rule within 24h. Without
|
|
10
|
+
* this telemetry, every funnel decision is guessing. This is improvement #1
|
|
11
|
+
* from the 2026-05-13 revenue-ROI critique.
|
|
12
|
+
*
|
|
13
|
+
* Payload (anonymous):
|
|
14
|
+
* - eventType: 'activation_first_rule_promoted'
|
|
15
|
+
* - installId: stable random UUID (from cli-telemetry.getInstallId)
|
|
16
|
+
* - daysToFirstRule: days between INSTALL_ID_PATH creation and now
|
|
17
|
+
* - visitorType: ci | owner | real_user (from cli-telemetry.classifyInstall)
|
|
18
|
+
*
|
|
19
|
+
* No personal data, no rule content, no feedback text. Just the activation
|
|
20
|
+
* signal. Respects THUMBGATE_NO_TELEMETRY=1 / DO_NOT_TRACK=1 opt-out via
|
|
21
|
+
* the underlying trackEvent helper.
|
|
22
|
+
*
|
|
23
|
+
* Idempotency: writes a marker file. After the first firing the function
|
|
24
|
+
* is a no-op for the lifetime of that install.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
|
|
30
|
+
const { trackEvent, getInstallId, classifyInstall, INSTALL_ID_PATH } = require('./cli-telemetry');
|
|
31
|
+
|
|
32
|
+
const MARKER_DIR = path.join(path.dirname(INSTALL_ID_PATH), 'activation');
|
|
33
|
+
const MARKER_PATH = path.join(MARKER_DIR, 'first-rule-promoted.json');
|
|
34
|
+
|
|
35
|
+
function readMarker() {
|
|
36
|
+
try {
|
|
37
|
+
if (!fs.existsSync(MARKER_PATH)) return null;
|
|
38
|
+
return JSON.parse(fs.readFileSync(MARKER_PATH, 'utf8'));
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function writeMarker(record) {
|
|
45
|
+
try {
|
|
46
|
+
if (!fs.existsSync(MARKER_DIR)) fs.mkdirSync(MARKER_DIR, { recursive: true });
|
|
47
|
+
fs.writeFileSync(MARKER_PATH, JSON.stringify(record, null, 2));
|
|
48
|
+
} catch {
|
|
49
|
+
// non-fatal: worst case we double-fire on a future run; the telemetry
|
|
50
|
+
// backend can dedup by installId. Better to be silent than to throw.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function computeDaysSinceInstall() {
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(INSTALL_ID_PATH)) return null;
|
|
57
|
+
const installed = fs.statSync(INSTALL_ID_PATH).mtimeMs;
|
|
58
|
+
if (!Number.isFinite(installed) || installed <= 0) return null;
|
|
59
|
+
const diffMs = Date.now() - installed;
|
|
60
|
+
if (diffMs < 0) return 0;
|
|
61
|
+
// 1 decimal of precision; the metric uses 24h buckets but a finer-grained
|
|
62
|
+
// number lets analytics chart same-day vs. 1d / 2d / week-of activation.
|
|
63
|
+
return Math.round((diffMs / 86_400_000) * 10) / 10;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fire the activation event if this is the first rule promotion for this
|
|
71
|
+
* install. Returns true if an event was actually emitted, false if skipped
|
|
72
|
+
* (already fired before, or environment opted out of telemetry).
|
|
73
|
+
*
|
|
74
|
+
* Always synchronous and never throws — safe to call from any side-effect
|
|
75
|
+
* site without try/catch wrapping. The underlying telemetry call is itself
|
|
76
|
+
* fire-and-forget over HTTPS with a 3s timeout.
|
|
77
|
+
*/
|
|
78
|
+
function recordFirstRulePromotion(metadata = {}) {
|
|
79
|
+
if (readMarker()) return false; // already fired
|
|
80
|
+
|
|
81
|
+
if (process.env.THUMBGATE_NO_TELEMETRY === '1' || process.env.DO_NOT_TRACK === '1') {
|
|
82
|
+
// Still write the marker so a later run with telemetry re-enabled does not
|
|
83
|
+
// fire stale activation data weeks after the actual first promotion.
|
|
84
|
+
writeMarker({
|
|
85
|
+
installId: getInstallId(),
|
|
86
|
+
promotedAt: new Date().toISOString(),
|
|
87
|
+
telemetryOptOut: true,
|
|
88
|
+
});
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const installId = getInstallId();
|
|
93
|
+
const daysToFirstRule = computeDaysSinceInstall();
|
|
94
|
+
const visitorType = classifyInstall();
|
|
95
|
+
|
|
96
|
+
writeMarker({
|
|
97
|
+
installId,
|
|
98
|
+
promotedAt: new Date().toISOString(),
|
|
99
|
+
daysToFirstRule,
|
|
100
|
+
visitorType,
|
|
101
|
+
...metadata,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
trackEvent('activation_first_rule_promoted', {
|
|
105
|
+
daysToFirstRule,
|
|
106
|
+
visitorType,
|
|
107
|
+
...metadata,
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resetForTesting() {
|
|
113
|
+
// Test-only helper. Removes the marker so a subsequent recordFirstRulePromotion
|
|
114
|
+
// call re-fires. Never used in production code paths.
|
|
115
|
+
try {
|
|
116
|
+
if (fs.existsSync(MARKER_PATH)) fs.unlinkSync(MARKER_PATH);
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
recordFirstRulePromotion,
|
|
122
|
+
computeDaysSinceInstall,
|
|
123
|
+
readMarker,
|
|
124
|
+
writeMarker,
|
|
125
|
+
MARKER_PATH,
|
|
126
|
+
resetForTesting,
|
|
127
|
+
};
|
|
@@ -6,7 +6,10 @@ const path = require('path');
|
|
|
6
6
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
7
7
|
|
|
8
8
|
const MAX_AUTO_GATES = 10;
|
|
9
|
-
|
|
9
|
+
// 1+ failure auto-promotes to a warning gate. Cold buyers expect "one 👎 → blocked next time"
|
|
10
|
+
// — a 2-capture threshold made first-capture invisible and broke the activation loop. Block
|
|
11
|
+
// escalation still requires 3 captures (BLOCK_THRESHOLD) so noise doesn't auto-hard-block.
|
|
12
|
+
const WARN_THRESHOLD = 1;
|
|
10
13
|
const BLOCK_THRESHOLD = 3; // 3+ repeated failures hard-block the action
|
|
11
14
|
const WINDOW_DAYS = 30;
|
|
12
15
|
|
package/scripts/feedback-loop.js
CHANGED
|
@@ -1398,7 +1398,20 @@ function captureFeedback(params) {
|
|
|
1398
1398
|
if (feedbackEvent.signal === 'negative') {
|
|
1399
1399
|
try {
|
|
1400
1400
|
const autoPromote = require('./auto-promote-gates');
|
|
1401
|
-
autoPromote.promote(FEEDBACK_LOG_PATH);
|
|
1401
|
+
const promoteResult = autoPromote.promote(FEEDBACK_LOG_PATH);
|
|
1402
|
+
// First-rule activation telemetry: anonymous ping the first time
|
|
1403
|
+
// a prevention rule auto-promotes for this install. Idempotent —
|
|
1404
|
+
// see scripts/activation-tracker.js. Critical for activation funnel
|
|
1405
|
+
// analytics; no rule content, just install_id + days_to_first_rule.
|
|
1406
|
+
if (promoteResult && Array.isArray(promoteResult.promotions) && promoteResult.promotions.length > 0) {
|
|
1407
|
+
try {
|
|
1408
|
+
const { recordFirstRulePromotion } = require('./activation-tracker');
|
|
1409
|
+
recordFirstRulePromotion({
|
|
1410
|
+
promotionCount: promoteResult.promotions.length,
|
|
1411
|
+
totalGates: promoteResult.totalGates,
|
|
1412
|
+
});
|
|
1413
|
+
} catch { /* activation telemetry is non-critical */ }
|
|
1414
|
+
}
|
|
1402
1415
|
} catch { /* Gate promotion is non-critical */ }
|
|
1403
1416
|
}
|
|
1404
1417
|
|
|
@@ -32,7 +32,17 @@ function normalize(ctx) {
|
|
|
32
32
|
return (ctx || '').replace(/\/Users\/[^\s/]+/g, '~').replace(/:[0-9]+/g, '').toLowerCase().trim();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// HIGH_RISK_TAGS triggers single-capture promotion (count >= 1 && hasHighRisk).
|
|
36
|
+
// Tags here MUST overlap with what inferSemanticTags() actually emits (see scripts/feedback-loop.js)
|
|
37
|
+
// — otherwise cold buyers' first 👎 stays a lesson and never becomes a gate.
|
|
38
|
+
const HIGH_RISK_TAGS = new Set([
|
|
39
|
+
// Original semantic-category labels
|
|
40
|
+
'git-workflow', 'scope-control', 'trust-breach', 'execution-gap', 'regression', 'security',
|
|
41
|
+
// Tags inferSemanticTags() emits for destructive / irreversible operations
|
|
42
|
+
'destructive', 'force-push', 'delete', 'drop', 'force-overwrite',
|
|
43
|
+
'production', 'database', 'payment', 'credentials', 'secrets',
|
|
44
|
+
'rm-rf', 'reset-hard', 'truncate', 'data-loss',
|
|
45
|
+
]);
|
|
36
46
|
function analyze(entries) {
|
|
37
47
|
let positiveCount = 0, negativeCount = 0;
|
|
38
48
|
const categories = {};
|