thumbgate 1.19.0 โ†’ 1.20.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "owner": {
5
5
  "name": "Igor Ganapolsky",
6
6
  "email": "ig5973700@gmail.com"
@@ -13,7 +13,7 @@
13
13
  "source": "npm",
14
14
  "package": "thumbgate"
15
15
  },
16
- "version": "1.19.0",
16
+ "version": "1.20.0",
17
17
  "author": {
18
18
  "name": "Igor Ganapolsky"
19
19
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "Type ๐Ÿ‘ or ๐Ÿ‘Ž on any agent action. ThumbGate captures it, distills a lesson, and blocks the pattern from repeating. One thumbs-down = the agent physically cannot make that mistake again. 33 pre-action checks, budget enforcement, self-protection, and NIST/SOC2 compliance tags.",
4
- "version": "1.19.0",
4
+ "version": "1.20.0",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "ThumbGate โ€” ๐Ÿ‘๐Ÿ‘Ž feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
5
5
  "homepage": "https://thumbgate-production.up.railway.app",
6
6
  "transport": "stdio",
package/README.md CHANGED
@@ -91,8 +91,8 @@ ThumbGate doesn't make your agent smarter. It makes your agent *cheaper to be wr
91
91
  ## Quick Start
92
92
 
93
93
  ```bash
94
- npx thumbgate init # auto-detects your agent, wires everything
95
- npx thumbgate capture "Never run DROP on production tables"
94
+ npx thumbgate init # auto-detects your agent, wires everything
95
+ npx thumbgate capture --feedback=down --context="Never run DROP on production tables"
96
96
  ```
97
97
 
98
98
  That single command creates a prevention rule. Next time any AI agent tries to run `DROP` on production:
@@ -253,10 +253,10 @@ ThumbGate sells three concrete outcomes:
253
253
  ## CLI Reference
254
254
 
255
255
  ```bash
256
- npx thumbgate init # detect agent, wire hooks
257
- npx thumbgate doctor # health check
258
- npx thumbgate capture # create a check from text
259
- npx thumbgate lessons # see what's been learned
256
+ npx thumbgate init # detect agent, wire hooks
257
+ npx thumbgate doctor # health check
258
+ npx thumbgate capture --feedback=up|down --context="<text>" # capture a signal as a stored lesson
259
+ npx thumbgate lessons # see what's been learned
260
260
  npx thumbgate explore # terminal explorer for lessons, checks, stats
261
261
  npx thumbgate background-governance # review background-agent run risk
262
262
  npx thumbgate model-candidates --workload=dashboard-analysis --provider=openai --json # evaluate GPT-5.5 routing
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.19.0", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.20.0", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.19.0", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.20.0", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -216,7 +216,7 @@ const {
216
216
  finalizeSession: finalizeFeedbackSession,
217
217
  } = require('../../scripts/feedback-session');
218
218
 
219
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.19.0' };
219
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.20.0' };
220
220
  const COMMERCE_CATEGORIES = [
221
221
  'product_recommendation',
222
222
  'brand_compliance',
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.19.0",
10
+ "thumbgate@1.20.0",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
package/bin/cli.js CHANGED
@@ -141,6 +141,10 @@ function telemetryPing(installId) {
141
141
 
142
142
  function proNudge(context) {
143
143
  if (process.env.THUMBGATE_NO_NUDGE === '1') return;
144
+ try {
145
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
146
+ if (isProTier()) return;
147
+ } catch (_) { /* if rate-limiter is unavailable, fall through and nudge */ }
144
148
  const messages = [
145
149
  `\n ๐Ÿ’ก Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
146
150
  `\n ๐Ÿ’ก Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
@@ -1157,7 +1161,10 @@ function pro() {
1157
1161
  }
1158
1162
 
1159
1163
  const resolvedKey = resolveProKey();
1160
- if (resolvedKey && resolvedKey.key) {
1164
+ // creator-dev legitimately returns {key: '', source: 'creator-dev', plan: 'enterprise'}
1165
+ // when THUMBGATE_DEV_KEY is unset โ€” startLocalProDashboard accepts the empty
1166
+ // key in that case (see pro-local-dashboard.js:141), so launch on source too.
1167
+ if (resolvedKey && (resolvedKey.key || resolvedKey.source === 'creator-dev')) {
1161
1168
  return launchDashboard(resolvedKey.key, 'pro_dashboard_launch');
1162
1169
  }
1163
1170
 
@@ -2299,6 +2306,36 @@ function startApi() {
2299
2306
 
2300
2307
  function help() {
2301
2308
  const v = pkgVersion();
2309
+ const helpArgs = process.argv.slice(3);
2310
+ const showAll = helpArgs.includes('all')
2311
+ || helpArgs.includes('--all')
2312
+ || helpArgs.includes('--full');
2313
+
2314
+ // Default `thumbgate help` shows a curated short list. The full ~70-command
2315
+ // surface lives behind `thumbgate help all` so first-time users aren't hit
2316
+ // with a wall of text. (Pre-2026-05-18 default: dump everything.)
2317
+ if (!showAll) {
2318
+ console.log(`thumbgate v${v} โ€” pre-action checks for AI coding agents`);
2319
+ console.log('');
2320
+ console.log('Common commands:');
2321
+ console.log(' init Detect agent and wire ThumbGate hooks');
2322
+ console.log(' capture --feedback=up|down --context="<text>" Capture a thumbs signal as a stored lesson');
2323
+ console.log(' stats Approval rate, recent trend, blocked-pattern count');
2324
+ console.log(' lessons [query] Search promoted lessons');
2325
+ console.log(' explore Interactive TUI for lessons, gates, stats');
2326
+ console.log(' dashboard Open the local ThumbGate dashboard');
2327
+ console.log(' doctor Audit runtime isolation + bootstrap context');
2328
+ console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
2329
+ console.log('');
2330
+ console.log('More:');
2331
+ console.log(' thumbgate help all Full subcommand surface (~70 commands)');
2332
+ console.log(' thumbgate <cmd> --help Per-command flags (where supported)');
2333
+ console.log('');
2334
+ console.log('Docs: https://github.com/IgorGanapolsky/ThumbGate');
2335
+ proNudge();
2336
+ return;
2337
+ }
2338
+
2302
2339
  const { groupedCommands, commandHelpLine } = require(path.join(PKG_ROOT, 'scripts', 'cli-schema'));
2303
2340
  const groups = groupedCommands();
2304
2341
  const GROUP_LABELS = {
@@ -2310,7 +2347,7 @@ function help() {
2310
2347
  advanced: 'Advanced',
2311
2348
  };
2312
2349
 
2313
- console.log(`thumbgate v${v} โ€” pre-action checks for AI coding agents`);
2350
+ console.log(`thumbgate v${v} โ€” pre-action checks for AI coding agents (full command surface)`);
2314
2351
  console.log('');
2315
2352
 
2316
2353
  for (const [groupKey, label] of Object.entries(GROUP_LABELS)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 33 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
5
5
  "homepage": "https://thumbgate-production.up.railway.app",
6
6
  "repository": {
@@ -249,6 +249,7 @@
249
249
  "verify:full": "node scripts/verify-run.js full",
250
250
  "budget:status": "node scripts/budget-guard.js --status",
251
251
  "revenue:status": "node scripts/revenue-status.js",
252
+ "revenue:doctor": "node scripts/revenue-observability-doctor.js",
252
253
  "revenue:plan": "node scripts/may-2026-revenue-machine.js",
253
254
  "revenue:status:local": "node bin/cli.js cfo",
254
255
  "revenue:repair:github-marketplace": "node bin/cli.js repair-github-marketplace --write",
@@ -328,7 +329,7 @@
328
329
  "social:prospect:bluesky:dry": "node scripts/social-bluesky-prospecting.js --dry-run",
329
330
  "social:reply-publish:bluesky:dry": "node scripts/social-reply-monitor-bluesky.js --publish-approved --dry-run",
330
331
  "test:python": "python3 -m pytest tests/*.py",
331
- "test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export",
332
+ "test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export && npm run test:stripe-checkout-diagnostic && npm run test:stripe-business-identity-probe && npm run test:revenue-observability-doctor && npm run test:public-bundle-ratchet && npm run test:ci-cd-hygiene-audit",
332
333
  "test:hook-stop-verify-deploy": "node --test tests/hook-stop-verify-deploy.test.js",
333
334
  "test:hook-stop-anti-claim": "node --test tests/hook-stop-anti-claim.test.js",
334
335
  "test:plausible-server-events": "node --test tests/plausible-server-events.test.js",
@@ -336,6 +337,9 @@
336
337
  "test:unified-revenue-rollup": "node --test tests/unified-revenue-rollup.test.js",
337
338
  "test:conversion-rate-stats": "node --test tests/conversion-rate-stats.test.js",
338
339
  "test:external-customer-audit": "node --test tests/external-customer-audit.test.js",
340
+ "test:stripe-checkout-diagnostic": "node --test tests/stripe-checkout-diagnostic.test.js",
341
+ "test:stripe-business-identity-probe": "node --test tests/stripe-business-identity-probe.test.js",
342
+ "test:ci-cd-hygiene-audit": "node --test tests/ci-cd-hygiene-audit.test.js",
339
343
  "test:telemetry-export": "node --test tests/telemetry-export.test.js",
340
344
  "test:swarm-coordinator": "node --test tests/swarm-coordinator.test.js",
341
345
  "test:session-report": "node --test tests/session-report.test.js",
@@ -431,7 +435,7 @@
431
435
  "test:workflow": "node --test tests/workflow-contract.test.js tests/social-marketing-assets.test.js tests/social-pipeline.test.js tests/positioning-contract.test.js tests/docs-claim-hygiene.test.js tests/thumbgate-scope.test.js tests/workflow-runs.test.js tests/workflow-sprint-intake.test.js tests/gtm-revenue-loop.test.js tests/may-2026-revenue-machine.test.js tests/customer-discovery-sprint.test.js tests/revenue-pack-utils.test.js tests/aiventyx-marketplace-plan.test.js tests/cursor-marketplace-revenue-pack.test.js tests/codex-marketplace-revenue-pack.test.js tests/codex-plugin-revenue-pack.test.js tests/gemini-cli-demand-pack.test.js tests/roo-sunset-demand-pack.test.js tests/linkedin-workflow-hardening-pack.test.js tests/chatgpt-gpt-revenue-pack.test.js tests/mcp-directory-revenue-pack.test.js tests/money-marketplace-distribution-pack.test.js tests/autonomous-sales-agent.test.js tests/reddit-dm-workflow-hardening-pack.test.js tests/sales-pipeline.test.js tests/reddit-dm-outreach.test.js tests/github-outreach.test.js tests/enterprise-story.test.js tests/ralph-loop.test.js tests/ralph-mode-ci.test.js tests/guide-conversion-path.test.js tests/roo-sunset-marketing.test.js",
432
436
  "test:sales-pipeline": "node --test tests/sales-pipeline.test.js",
433
437
  "test:billing": "node --test tests/billing.test.js tests/stripe-sync-product-images.test.js",
434
- "test:cli": "node --test tests/analytics-report.test.js tests/agent-design-governance.test.js tests/codex-self-heal.test.js tests/creator-campaigns.test.js tests/cli.test.js tests/codex-bridge-script.test.js tests/dependabot-changeset.test.js tests/dispatch-brief.test.js tests/feedback-normalize.test.js tests/install-mcp.test.js tests/pr-manager.test.js tests/pro-local-dashboard.test.js tests/published-cli.test.js tests/revenue-status.test.js tests/stripe-live-status.test.js",
438
+ "test:cli": "node --test tests/analytics-report.test.js tests/agent-design-governance.test.js tests/codex-self-heal.test.js tests/creator-campaigns.test.js tests/cli.test.js tests/codex-bridge-script.test.js tests/dependabot-changeset.test.js tests/dispatch-brief.test.js tests/feedback-normalize.test.js tests/install-mcp.test.js tests/pr-manager.test.js tests/pro-local-dashboard.test.js tests/published-cli.test.js tests/revenue-status.test.js tests/stripe-live-status.test.js tests/creator-dev-and-prune.test.js",
435
439
  "test:evolution": "node --test tests/workspace-evolver.test.js",
436
440
  "test:watcher": "node --test tests/jsonl-watcher.test.js",
437
441
  "test:autoresearch": "node --test tests/autoresearch.test.js",
@@ -508,6 +512,7 @@
508
512
  "test:bot-detection": "node --test tests/bot-detection.test.js",
509
513
  "test:checkout-bot-guard": "node --test tests/checkout-bot-guard.test.js",
510
514
  "test:checkout-pro-confirmation-gate": "node --test tests/checkout-pro-confirmation-gate.test.js",
515
+ "test:revenue-observability-doctor": "node --test tests/revenue-observability-doctor.test.js",
511
516
  "test:postinstall": "node --test tests/postinstall.test.js",
512
517
  "test:funnel-invariants": "node --test tests/funnel-invariants.test.js",
513
518
  "test:cli-telemetry": "node --test tests/cli-telemetry.test.js",
@@ -640,7 +645,8 @@
640
645
  "test:brand-assets": "node --test tests/brand-assets.test.js",
641
646
  "test:enforcement-teeth": "node --test tests/enforcement-teeth.test.js",
642
647
  "test:bayes-optimal-gate": "node --test tests/bayes-optimal-gate.test.js",
643
- "test:actionable-remediations": "node --test tests/actionable-remediations.test.js"
648
+ "test:actionable-remediations": "node --test tests/actionable-remediations.test.js",
649
+ "test:public-bundle-ratchet": "node --test tests/public-bundle-ratchet.test.js"
644
650
  },
645
651
  "keywords": [
646
652
  "mcp",
@@ -261,6 +261,12 @@
261
261
  <p><a href="/compare/heidi" class="cta">Read ThumbGate vs HEIDI</a></p>
262
262
  </div>
263
263
 
264
+ <div class="card">
265
+ <h3>Evaluating Rein for AI agent governance?</h3>
266
+ <p>Rein is a Python decorator that wraps tool functions with policy checks, audit trails, and circuit breakers โ€” targeted at production apps in trading, healthcare, and legal. ThumbGate is the coding-agent specialist with a learning feedback loop and MIT licensing. Same category (pre-action gates), different layer and stack.</p>
267
+ <p><a href="/compare/rein" class="cta">Read ThumbGate vs Rein</a></p>
268
+ </div>
269
+
264
270
  <h2>How It Works</h2>
265
271
  <div class="step-grid">
266
272
  <div class="step-card">
package/public/index.html CHANGED
@@ -19,7 +19,7 @@ __GOOGLE_SITE_VERIFICATION_META__
19
19
  <meta property="og:image" content="https://thumbgate-production.up.railway.app/og.png">
20
20
  <meta name="twitter:card" content="summary_large_image">
21
21
  <meta name="twitter:image" content="https://thumbgate-production.up.railway.app/og.png">
22
- <meta name="thumbgate-version" content="1.19.0">
22
+ <meta name="thumbgate-version" content="1.20.0">
23
23
  <meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agent enforcement layer, save LLM tokens, reduce Claude API cost, reduce OpenAI cost, AI agent token savings, prevent LLM retries, prevent hallucination retries, stop AI token waste, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
24
24
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
25
25
 
@@ -1318,6 +1318,11 @@ __GA_BOOTSTRAP__
1318
1318
  </div>
1319
1319
  <span class="team-intake-badge">30-minute intake</span>
1320
1320
  </div>
1321
+ <details style="border:1px solid var(--border);border-radius:14px;padding:18px 22px;background:var(--bg-raised);margin:18px 0;">
1322
+ <summary style="cursor:pointer;font-size:16px;font-weight:600;color:var(--cyan);list-style:none;display:flex;align-items:center;gap:8px;">
1323
+ <span style="display:inline-block;transition:transform 0.15s;">โ–ธ</span>
1324
+ Need consulting, a one-off diagnostic, or a hardening sprint? View paid services &amp; kits
1325
+ </summary>
1321
1326
  <div class="team-paid-path" data-sprint-paid-path aria-label="Paid workflow hardening options">
1322
1327
  <div class="team-paid-card">
1323
1328
  <div>
@@ -1360,6 +1365,7 @@ __GA_BOOTSTRAP__
1360
1365
  </div>
1361
1366
  </div>
1362
1367
  </div>
1368
+ </details>
1363
1369
  <div class="team-intake-recovery" aria-label="Checkout recovery path for workflow sprint buyers">
1364
1370
  <div>
1365
1371
  <strong>Not ready to pay from a checkout page?</strong>
@@ -1494,7 +1500,7 @@ __GA_BOOTSTRAP__
1494
1500
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
1495
1501
  <a href="/blog">Blog</a>
1496
1502
  </div>
1497
- <span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.19.0</span>
1503
+ <span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.20.0</span>
1498
1504
  </div>
1499
1505
  </footer>
1500
1506
 
@@ -1840,6 +1846,27 @@ async function handleProCheckout() {
1840
1846
  }
1841
1847
  globalThis.location.assign(checkoutUrl);
1842
1848
  }
1849
+
1850
+ // Conversion-path safety: /services redirects to /#workflow-sprint-intake.
1851
+ // Modern browsers auto-open a <details> containing the hash target, but
1852
+ // older Safari/Firefox/Chrome (<2020) do not. Force-open any ancestor
1853
+ // <details> of the hash target so high-intent buyers always see the
1854
+ // paid intake content on arrival.
1855
+ function openDetailsForHash() {
1856
+ var hash = globalThis.location.hash;
1857
+ if (!hash || hash.length < 2) return;
1858
+ var target;
1859
+ try { target = document.querySelector(hash); } catch (_) { return; }
1860
+ if (!target) return;
1861
+ var el = target;
1862
+ while (el && el !== document.body) {
1863
+ if (el.tagName === 'DETAILS') { el.open = true; }
1864
+ el = el.parentElement;
1865
+ }
1866
+ target.scrollIntoView({ behavior: 'auto', block: 'start' });
1867
+ }
1868
+ globalThis.addEventListener('DOMContentLoaded', openDetailsForHash);
1869
+ globalThis.addEventListener('hashchange', openDetailsForHash);
1843
1870
  </script>
1844
1871
  </body>
1845
1872
  </html>
package/public/learn.html CHANGED
@@ -266,6 +266,13 @@
266
266
  <span class="article-tag">PreToolUse</span>
267
267
  <span class="article-tag">Technical</span>
268
268
  </a>
269
+ <a href="/learn/ai-agent-governance" class="article-card">
270
+ <h3>AI Agent Governance โ€” The Four Layers Pattern</h3>
271
+ <p>AI agent governance has four layers: prompt rules, decorator wrappers, pre-action hooks, sandbox isolation. Each catches a different failure mode. Pick the layer that matches your stack โ€” and understand why prompt rules alone fail.</p>
272
+ <span class="article-tag">Governance</span>
273
+ <span class="article-tag">Architecture</span>
274
+ <span class="article-tag">Pattern</span>
275
+ </a>
269
276
  <a href="/learn/agent-harness-pattern" class="article-card">
270
277
  <h3>The Agent Harness Pattern: Why Your AI Needs a Seatbelt</h3>
271
278
  <p>Tsinghua researchers formalized agent harnesses as first-class objects with contracts, verification checks, and durable state. ThumbGate implements this pattern in production today.</p>
@@ -274,6 +281,14 @@
274
281
  <span class="article-tag">NLAH</span>
275
282
  </a>
276
283
 
284
+ <a href="/learn/agent-swarms-shared-gates" class="article-card">
285
+ <h3>Agent Swarms: One Gate Layer, Every Model</h3>
286
+ <p>A 5-agent swarm without shared memory pays 5&times; the tokens on every repeated mistake. Here is how a single MCP gate layer makes Opus, GPT, and Gemini fail the same way only once.</p>
287
+ <span class="article-tag">Agent Swarms</span>
288
+ <span class="article-tag">Multi-Agent</span>
289
+ <span class="article-tag">Shared Memory</span>
290
+ </a>
291
+
277
292
  <a href="/learn/ai-agent-persistent-memory" class="article-card">
278
293
  <h3>How to Give Your AI Coding Agent Persistent Memory Across Sessions</h3>
279
294
  <p>Your agent forgets everything when the session ends. Here is how to give Claude Code, Cursor, Codex, and Gemini memory that survives restarts โ€” without retraining.</p>
@@ -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.19.0",
28
+ "softwareVersion": "1.20.0",
29
29
  "url": "https://thumbgate-production.up.railway.app/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
@@ -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.19.0</div>
205
+ <div class="freshness">Updated: 2026-05-07 ยท Version 1.20.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/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 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>
721
+ <a class="nav-cta btn-pro-checkout" href="/checkout/pro?utm_source=website&utm_medium=pro_page_nav&utm_campaign=pro_pack&cta_id=pro_page_nav&cta_placement=nav&plan_id=pro&landing_path=%2Fpro">Start Pro</a>
722
722
  </div>
723
723
  </div>
724
724
  </nav>
@@ -730,7 +730,7 @@ __GA_BOOTSTRAP__
730
730
  <h1>Buy the operator loop that proves your AI agent stopped repeating the mistake.</h1>
731
731
  <p style="font-size:13px;opacity:0.8;margin-bottom:0.5rem;">Updated: <time datetime="2026-04-20">2026-04-20</time> ยท by <a href="https://github.com/IgorGanapolsky" style="color:inherit;">Igor Ganapolsky</a></p>
732
732
  <p>ThumbGate Pro is for one operator who already hit a repeated AI-agent failure and now needs proof: what was blocked, why it was blocked, and what changed before the next risky run.</p>
733
- <p>If you need help today, start with the $19 quick read: send one failed tool call or workflow snippet and get the likely rule shape plus proof check. Use Pro when you want the local dashboard and DPO export.</p>
733
+ <p>Start Pro when you want the local dashboard, DPO export, and a single proof lane for the repeated mistake you need to stop. Team diagnostics and custom services are handled through intake, not this buyer path.</p>
734
734
  <div class="hero-proof">
735
735
  <div class="proof-pill">Personal local dashboard</div>
736
736
  <div class="proof-pill">DPO export from real corrections</div>
@@ -738,8 +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 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
- <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>
741
+ <a class="btn-primary 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
742
  <a class="btn-secondary btn-demo" href="/dashboard?utm_source=website&utm_medium=pro_page&utm_campaign=pro_pack">Open dashboard demo</a>
744
743
  <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>
745
744
  </div>
@@ -775,18 +774,6 @@ __GA_BOOTSTRAP__
775
774
  <p>Visual check debugger, DPO export, auto-connect after activation, Model Hardening Advisor, and founder support for the risky flow you need to harden first.</p>
776
775
  </div>
777
776
 
778
- <div class="aside-card" data-pro-paid-recovery>
779
- <div class="aside-kicker">Team workflow blocked?</div>
780
- <h3>Buy the paid diagnostic</h3>
781
- <p>Skip self-serve Pro and pay for the smallest useful review now.</p>
782
- <div class="price-stack">
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
- <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
- <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
- </div>
788
- </div>
789
-
790
777
  <div class="aside-card">
791
778
  <div class="aside-kicker">Keep the buyer path warm</div>
792
779
  <h3>Save your work email before you decide</h3>
@@ -1024,7 +1011,6 @@ function sendFirstPartyTelemetry(eventType, props) {
1024
1011
  }
1025
1012
 
1026
1013
  function sendGa4Event(e,p){if(typeof gtag==='function')gtag('event',e,p||{})}
1027
- function initializeProPaidRecovery(){var c=document.querySelector('[data-pro-paid-recovery]');if(!c)return;var n=0;c.querySelectorAll('a[href]').forEach(function(a){var h=a.getAttribute('href')||'';if(/^https?:\/\//.test(h))n+=1;else a.hidden=true});c.hidden=n===0}
1028
1014
 
1029
1015
  function initializeBuyerIntent() {
1030
1016
  globalThis.ThumbGateBuyerIntent.initializeBuyerIntent({
@@ -1067,7 +1053,6 @@ trackClick('.btn-pro-checkout', 'pro_checkout_start', { tier: 'pro', page: 'pro'
1067
1053
  trackClick('.btn-demo', 'pro_demo_click', { page: 'pro' });
1068
1054
  trackClick('.btn-free-path', 'pro_free_path_click', { page: 'pro' });
1069
1055
  trackClick('.proof-links a', 'pro_proof_click', { page: 'pro' });
1070
- initializeProPaidRecovery();
1071
1056
  initializeBuyerIntent();
1072
1057
  globalThis.buyerJourney = globalThis.ThumbGateBuyerIntent.initializeBehaviorAnalytics({
1073
1058
  pageType: 'marketing',
@@ -1084,10 +1069,6 @@ globalThis.buyerJourney = globalThis.ThumbGateBuyerIntent.initializeBehaviorAnal
1084
1069
  ],
1085
1070
  ctaImpressions: [
1086
1071
  { selector: '.btn-pro-checkout', ctaId: 'pro_checkout', ctaPlacement: 'pro_page', planId: 'pro' },
1087
- { selector: '[data-first-rule-link]', ctaId: 'pro_page_first_failure_rule_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'first_failure_rule' },
1088
- { selector: '[data-quick-read-link]', ctaId: 'pro_page_quick_read_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'quick_read' },
1089
- { selector: '[data-sprint-diagnostic-link]', ctaId: 'pro_page_sprint_diagnostic_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'sprint_diagnostic' },
1090
- { selector: '[data-workflow-sprint-link]', ctaId: 'pro_page_workflow_sprint_checkout', ctaPlacement: 'pro_paid_recovery', planId: 'workflow_sprint' },
1091
1072
  { selector: '.btn-demo', ctaId: 'pro_demo', ctaPlacement: 'pro_page', planId: 'proof' },
1092
1073
  { selector: '.btn-free-path', ctaId: 'pro_free_path', ctaPlacement: 'pro_page', planId: 'free' }
1093
1074
  ]
@@ -13,6 +13,22 @@ const WARN_THRESHOLD = 1;
13
13
  const BLOCK_THRESHOLD = 3; // 3+ repeated failures hard-block the action
14
14
  const WINDOW_DAYS = 30;
15
15
 
16
+ // Default TTL on auto-promoted gates. Reddit reviewer @MomSausageandPeppers
17
+ // (2026-05-13) flagged that without expiry, "accidental dislikes become policy
18
+ // forever." Gates expire 90 days after promotion UNLESS they keep firing โ€”
19
+ // every fire refreshes lastFiredAt, and expireGates() keeps any gate fired
20
+ // within the last TTL window regardless of original promotion date. Manual
21
+ // force-promote bypasses TTL (operator says "permanent"). Override via
22
+ // THUMBGATE_RULE_TTL_DAYS env var.
23
+ const DEFAULT_RULE_TTL_DAYS = 90;
24
+ function getRuleTtlDays() {
25
+ const raw = Number(process.env.THUMBGATE_RULE_TTL_DAYS);
26
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_RULE_TTL_DAYS;
27
+ }
28
+ function getRuleTtlMs() {
29
+ return getRuleTtlDays() * 24 * 60 * 60 * 1000;
30
+ }
31
+
16
32
  const NEG_SIGNALS = new Set(['negative', 'negative_strong', 'down', 'thumbs_down']);
17
33
 
18
34
  function getFeedbackLogPath() {
@@ -66,15 +82,54 @@ function isNegative(entry) {
66
82
  return NEG_SIGNALS.has(sig);
67
83
  }
68
84
 
85
+ /**
86
+ * Normalize a captured command/context string so trivial variants collapse
87
+ * to the same gate signature.
88
+ *
89
+ * Reddit critique (MomSausageandPeppers, 2026-05-17): "commands are matched
90
+ * by string equality, so `rm -rf node_modules` and `rm -rf ./node_modules`
91
+ * create separate gates."
92
+ *
93
+ * Conservative โ€” only collapse variants that are *unambiguously* the same
94
+ * intent. Does NOT reorder flags, strip `&&` chains, or canonicalize
95
+ * subcommands (each can change semantics).
96
+ *
97
+ * 1. Lowercase
98
+ * 2. Strip `/Users/<name>` and `/home/<name>` home-dir prefixes (โ†’ `~`)
99
+ * 3. Drop `:LINE` and `:LINE:COL` refs
100
+ * 4. Per-token: strip one layer of matching outer quotes/backticks
101
+ * 5. Per-token: drop leading `./`
102
+ * 6. Collapse whitespace + trim
103
+ */
104
+ function normalizeCommandSignature(input) {
105
+ let text = String(input || '');
106
+ if (!text) return '';
107
+ text = text.toLowerCase();
108
+ text = text.replace(/\/users\/[^\s/]+/g, '~').replace(/\/home\/[^\s/]+/g, '~');
109
+ text = text.replace(/:\d+(?::\d+)?\b/g, '');
110
+ const tokens = text.split(/\s+/).filter(Boolean).map((tok) => {
111
+ let t = tok;
112
+ if (t.length >= 2) {
113
+ const first = t[0];
114
+ const last = t[t.length - 1];
115
+ if ((first === '"' || first === "'" || first === '`') && first === last) {
116
+ t = t.slice(1, -1);
117
+ }
118
+ }
119
+ if (t.startsWith('./')) t = t.slice(2);
120
+ return t;
121
+ }).filter(Boolean);
122
+ return tokens.join(' ').trim();
123
+ }
124
+
69
125
  function extractPatternKey(entry) {
70
126
  // Use tags as primary grouping key; fall back to context normalization
71
127
  const tags = (entry.tags || []).filter((t) => !['feedback', 'negative', 'positive'].includes(t));
72
128
  if (tags.length > 0) return tags.sort().join('+');
73
129
 
74
- const ctx = (entry.context || entry.whatWentWrong || '').toLowerCase().trim();
130
+ const ctx = (entry.context || entry.whatWentWrong || '').trim();
75
131
  if (ctx.length < 10) return null;
76
- // Normalize paths and numbers for grouping
77
- return ctx.replace(/\/Users\/[^\s/]+/g, '~').replace(/:[0-9]+/g, '').replace(/\s+/g, ' ').slice(0, 100);
132
+ return normalizeCommandSignature(ctx).slice(0, 100);
78
133
  }
79
134
 
80
135
  function extractDiagnosticKeys(entry) {
@@ -147,10 +202,17 @@ function buildGateRule(group) {
147
202
  : group.key.startsWith('constraint:')
148
203
  ? 'repeated constraint violation'
149
204
  : 'repeated pattern';
150
-
205
+
151
206
  const occurrencesText = group.count === 'MANUAL' ? 'manual' : `${group.count} occurrences`;
152
207
  const suggestedMessage = `Auto-promoted ${kind}: "${context}" (${occurrencesText} in ${WINDOW_DAYS} days)`;
153
208
 
209
+ // TTL: auto-promoted rules expire after the configured window unless
210
+ // refreshed by a fresh fire. Manual force-promote bypasses TTL โ€” operator
211
+ // says "permanent" by going through the force path.
212
+ const nowMs = Date.now();
213
+ const isManual = group.count === 'MANUAL';
214
+ const expiresAt = isManual ? null : new Date(nowMs + getRuleTtlMs()).toISOString();
215
+
154
216
  return {
155
217
  id: patternToGateId(group.key),
156
218
  trigger: `auto:${group.key}`,
@@ -160,10 +222,82 @@ function buildGateRule(group) {
160
222
  severity,
161
223
  occurrences: group.count,
162
224
  promotedAt: new Date().toISOString(),
225
+ expiresAt,
226
+ lastFiredAt: null,
163
227
  source: group.source || 'auto-promote',
164
228
  };
165
229
  }
166
230
 
231
+ /**
232
+ * Drop expired gates from the data and return the gates removed.
233
+ *
234
+ * A gate is expired when its `expiresAt` is in the past AND its
235
+ * `lastFiredAt` (if set) is also outside the TTL window โ€” high-signal
236
+ * gates that keep firing get continuously renewed and never expire.
237
+ *
238
+ * `expiresAt: null` is treated as "permanent" (used by force-promote /
239
+ * legacy gates without TTL data).
240
+ */
241
+ function expireGates(data, now = Date.now()) {
242
+ const safeData = data && typeof data === 'object'
243
+ ? { version: data.version || 1, gates: Array.isArray(data.gates) ? data.gates : [], promotionLog: Array.isArray(data.promotionLog) ? data.promotionLog : [] }
244
+ : { version: 1, gates: [], promotionLog: [] };
245
+ const ttlMs = getRuleTtlMs();
246
+ const kept = [];
247
+ const expired = [];
248
+ for (const gate of safeData.gates) {
249
+ if (!gate || typeof gate !== 'object') continue;
250
+ // No expiresAt โ†’ treat as permanent (manual force-promote, legacy gates).
251
+ if (gate.expiresAt == null) {
252
+ kept.push(gate);
253
+ continue;
254
+ }
255
+ const expiresMs = Date.parse(gate.expiresAt);
256
+ if (!Number.isFinite(expiresMs)) {
257
+ kept.push(gate);
258
+ continue;
259
+ }
260
+ // If last fire is within TTL window, refresh the gate (extend expiresAt).
261
+ const lastFiredMs = gate.lastFiredAt ? Date.parse(gate.lastFiredAt) : NaN;
262
+ if (Number.isFinite(lastFiredMs) && now - lastFiredMs < ttlMs) {
263
+ kept.push({ ...gate, expiresAt: new Date(lastFiredMs + ttlMs).toISOString() });
264
+ continue;
265
+ }
266
+ if (now < expiresMs) {
267
+ kept.push(gate);
268
+ } else {
269
+ expired.push({ id: gate.id, expiresAt: gate.expiresAt, lastFiredAt: gate.lastFiredAt });
270
+ }
271
+ }
272
+ safeData.gates = kept;
273
+ if (expired.length > 0) {
274
+ safeData.promotionLog.push(
275
+ ...expired.map((e) => ({ type: 'expired', gateId: e.id, expiredAt: e.expiresAt, timestamp: new Date(now).toISOString() }))
276
+ );
277
+ }
278
+ return { data: safeData, expired };
279
+ }
280
+
281
+ /**
282
+ * Mark a gate as fired now. Refreshes lastFiredAt AND extends expiresAt by
283
+ * the full TTL โ€” a gate that keeps catching repeats sharpens, doesn't
284
+ * decay. Caller passes the gate ID; returns the updated gate (or null).
285
+ */
286
+ function recordGateFire(data, gateId, now = Date.now()) {
287
+ if (!data || !Array.isArray(data.gates)) return null;
288
+ const idx = data.gates.findIndex((g) => g && g.id === gateId);
289
+ if (idx === -1) return null;
290
+ const gate = data.gates[idx];
291
+ const lastFiredAtIso = new Date(now).toISOString();
292
+ const updated = {
293
+ ...gate,
294
+ lastFiredAt: lastFiredAtIso,
295
+ expiresAt: gate.expiresAt == null ? null : new Date(now + getRuleTtlMs()).toISOString(),
296
+ };
297
+ data.gates[idx] = updated;
298
+ return updated;
299
+ }
300
+
167
301
  function forcePromote(context, action = 'block') {
168
302
  if (!context) throw new Error('context is required for force-promote');
169
303
  const data = loadAutoGates();
@@ -202,9 +336,15 @@ function promote(feedbackLogPath) {
202
336
  const logPath = feedbackLogPath || getFeedbackLogPath();
203
337
  const entries = readJSONL(logPath);
204
338
  const groups = groupNegativeFeedback(entries, WINDOW_DAYS);
205
- const data = loadAutoGates();
206
- const existingIds = new Set(data.gates.map((g) => g.id));
207
- const promotions = [];
339
+ // Expire stale gates BEFORE running the promotion loop so an expiring
340
+ // gate that's about to be re-promoted gets a fresh TTL via the normal
341
+ // path rather than carrying a near-stale expiresAt.
342
+ const { data: expiredData, expired } = expireGates(loadAutoGates());
343
+ const data = expiredData;
344
+ if (expired.length > 0) {
345
+ saveAutoGates(data);
346
+ }
347
+ const promotions = expired.map((e) => ({ type: 'expired', gateId: e.id, expiredAt: e.expiresAt }));
208
348
 
209
349
  for (const group of Object.values(groups)) {
210
350
  if (group.count < WARN_THRESHOLD) continue;
@@ -298,9 +438,15 @@ module.exports = {
298
438
  patternToGateId,
299
439
  buildGateRule,
300
440
  extractPatternKey,
441
+ normalizeCommandSignature,
301
442
  isNegative,
443
+ expireGates,
444
+ recordGateFire,
445
+ getRuleTtlDays,
446
+ getRuleTtlMs,
302
447
  MAX_AUTO_GATES,
303
448
  WARN_THRESHOLD,
304
449
  BLOCK_THRESHOLD,
305
450
  WINDOW_DAYS,
451
+ DEFAULT_RULE_TTL_DAYS,
306
452
  };
@@ -186,48 +186,69 @@ function hookAlreadyPresent(hookArray, command) {
186
186
  * (defaults to process.cwd()).
187
187
  * @returns {{ hooks: Array, removedPaths: string[] }}
188
188
  */
189
+ // Shell-style variable expansion limited to the env vars Claude Code
190
+ // documents for hook commands (CLAUDE_PROJECT_DIR), plus other process env
191
+ // vars. Surrounding ASCII quotes are stripped first so tokens like
192
+ // `"$CLAUDE_PROJECT_DIR"/.claude/hooks/x.sh` resolve correctly.
193
+ function expandShellToken(token, resolveBase) {
194
+ let s = token;
195
+ if (s.startsWith('"') && s.includes('"', 1)) {
196
+ s = s.slice(1, s.indexOf('"', 1)) + s.slice(s.indexOf('"', 1) + 1);
197
+ } else if (s.startsWith("'") && s.includes("'", 1)) {
198
+ s = s.slice(1, s.indexOf("'", 1)) + s.slice(s.indexOf("'", 1) + 1);
199
+ }
200
+ const lookup = (name) => (name === 'CLAUDE_PROJECT_DIR'
201
+ ? process.env.CLAUDE_PROJECT_DIR || resolveBase
202
+ : process.env[name]);
203
+ s = s.replace(/\$\{([A-Za-z_]\w*)\}/g, (_, n) => {
204
+ const v = lookup(n);
205
+ return v == null ? `\${${n}}` : v;
206
+ });
207
+ s = s.replace(/\$([A-Za-z_]\w*)/g, (_, n) => {
208
+ const v = lookup(n);
209
+ return v == null ? `$${n}` : v;
210
+ });
211
+ return s;
212
+ }
213
+
214
+ // Returns the raw (unexpanded) script-path token if the command points at a
215
+ // missing script file, else null. Anything that doesn't look like a file
216
+ // reference, or contains unresolved $VAR after expansion, returns null โ€”
217
+ // caller treats null as "keep the hook" (err on the side of NOT pruning).
218
+ function staleHookPath(command, resolveBase) {
219
+ if (!command) return null;
220
+ const rawFirstToken = command.split(/\s+/)[0];
221
+ const firstToken = expandShellToken(rawFirstToken, resolveBase);
222
+ const looksLikePath =
223
+ firstToken.includes('/') ||
224
+ firstToken.includes('\\') ||
225
+ firstToken.endsWith('.sh');
226
+ if (!looksLikePath) return null;
227
+ if (firstToken.includes('$')) return null;
228
+ const resolved = path.isAbsolute(firstToken)
229
+ ? firstToken
230
+ : path.resolve(resolveBase, firstToken);
231
+ return fs.existsSync(resolved) ? null : rawFirstToken;
232
+ }
233
+
189
234
  function pruneStaleFileHooks(hookArray, baseDir) {
190
235
  if (!Array.isArray(hookArray)) {
191
236
  return { hooks: [], removedPaths: [] };
192
237
  }
193
-
194
238
  const resolveBase = baseDir || process.cwd();
195
239
  const removedPaths = [];
196
-
197
240
  const hooks = hookArray.filter((entry) => {
198
241
  const entryHooks = Array.isArray(entry && entry.hooks) ? entry.hooks : [];
199
- let shouldRemove = false;
200
-
201
242
  for (const hook of entryHooks) {
202
243
  const command = hook && typeof hook.command === 'string' ? hook.command : '';
203
- if (!command) continue;
204
-
205
- // Extract the first token as the potential script path.
206
- const firstToken = command.split(/\s+/)[0];
207
-
208
- // Only treat it as a file reference if it looks like a path.
209
- const looksLikePath =
210
- firstToken.includes('/') ||
211
- firstToken.includes('\\') ||
212
- firstToken.endsWith('.sh');
213
-
214
- if (!looksLikePath) continue;
215
-
216
- // Resolve the path (absolute or relative to baseDir).
217
- const resolved = path.isAbsolute(firstToken)
218
- ? firstToken
219
- : path.resolve(resolveBase, firstToken);
220
-
221
- if (!fs.existsSync(resolved)) {
222
- removedPaths.push(firstToken);
223
- shouldRemove = true;
224
- break;
244
+ const stale = staleHookPath(command, resolveBase);
245
+ if (stale !== null) {
246
+ removedPaths.push(stale);
247
+ return false;
225
248
  }
226
249
  }
227
-
228
- return !shouldRemove;
250
+ return true;
229
251
  });
230
-
231
252
  return { hooks, removedPaths };
232
253
  }
233
254
 
@@ -14,7 +14,15 @@
14
14
  */
15
15
 
16
16
  const { captureFeedback } = require('./feedback-loop');
17
- const { distillFromHistory } = require('./history-distiller');
17
+ const { loadOptionalModule } = require('./private-core-boundary');
18
+ // `history-distiller` is a PRIVATE_CORE_MODULE โ€” present in this checkout and
19
+ // in ThumbGate-Core, but intentionally excluded from the public npm tarball.
20
+ // The hard `require('./history-distiller')` form crashed `hook-auto-capture`
21
+ // in published 1.19.0 with MODULE_NOT_FOUND. Public-shell fallback returns
22
+ // null distillation; caller already handles a null distillResult.
23
+ const { distillFromHistory } = loadOptionalModule('./history-distiller', () => ({
24
+ distillFromHistory: () => null,
25
+ }));
18
26
  const { getRecentLesson, getLessonStats } = require('./lesson-inference');
19
27
 
20
28
  const G = '\x1b[32m';
@@ -42,6 +42,17 @@ const PAYWALL_MESSAGES = {
42
42
  function isProTier(authContext) {
43
43
  if (authContext && authContext.tier === 'pro') return true;
44
44
  if (process.env.THUMBGATE_API_KEY || process.env.THUMBGATE_PRO_MODE === '1' || process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
45
+ // Creator/dogfooding bypass: when the owner has the dev secret + bypass
46
+ // configured (env or ~/.config/thumbgate/dev.json), treat the install as Pro
47
+ // so marketing nudges and rate limits stop firing on the maintainer's own
48
+ // machine.
49
+ try {
50
+ const { isCreatorDev } = require('./pro-local-dashboard');
51
+ if (isCreatorDev()) return true;
52
+ } catch (_) {
53
+ // pro-local-dashboard unavailable in this runtime โ€” fall through to
54
+ // license/free-tier handling rather than failing the check.
55
+ }
45
56
  try {
46
57
  const { isProLicensed } = require('./license');
47
58
  if (isProLicensed()) return true;
package/src/api/server.js CHANGED
@@ -1499,50 +1499,11 @@ function buildCheckoutIntentHref(baseUrl, metadata = {}, overrides = {}) {
1499
1499
 
1500
1500
  function renderCheckoutIntentPage({
1501
1501
  confirmHref,
1502
- firstRuleCheckoutHref,
1503
- quickReadCheckoutHref,
1504
- workflowTeardownCheckoutHref,
1505
1502
  workflowIntakeHref,
1506
- teamOptionsHref,
1507
- diagnosticCheckoutHref,
1508
- sprintCheckoutHref,
1509
- sprintDiagnosticPriceDollars = 499,
1510
- workflowSprintPriceDollars = 1500,
1511
1503
  }) {
1512
1504
  const safeConfirmHref = escapeHtmlAttribute(confirmHref);
1513
- const safeFirstRuleCheckoutHref = firstRuleCheckoutHref
1514
- ? escapeHtmlAttribute(firstRuleCheckoutHref)
1515
- : '';
1516
- const safeQuickReadCheckoutHref = quickReadCheckoutHref
1517
- ? escapeHtmlAttribute(quickReadCheckoutHref)
1518
- : '';
1519
- const safeWorkflowTeardownCheckoutHref = workflowTeardownCheckoutHref
1520
- ? escapeHtmlAttribute(workflowTeardownCheckoutHref)
1521
- : '';
1522
1505
  const safeWorkflowIntakeHref = escapeHtmlAttribute(workflowIntakeHref);
1523
- const safeTeamOptionsHref = escapeHtmlAttribute(teamOptionsHref);
1524
- const safeDiagnosticCheckoutHref = diagnosticCheckoutHref
1525
- ? escapeHtmlAttribute(diagnosticCheckoutHref)
1526
- : '';
1527
- const safeSprintCheckoutHref = sprintCheckoutHref
1528
- ? escapeHtmlAttribute(sprintCheckoutHref)
1529
- : '';
1530
- const diagnosticAction = safeDiagnosticCheckoutHref
1531
- ? `<a data-i="sprint_diagnostic_checkout" href="${safeDiagnosticCheckoutHref}">Book $${sprintDiagnosticPriceDollars} diagnostic</a>`
1532
- : '';
1533
- const sprintAction = safeSprintCheckoutHref
1534
- ? `<a data-i="workflow_sprint_checkout" href="${safeSprintCheckoutHref}">Start $${workflowSprintPriceDollars} sprint</a>`
1535
- : '';
1536
- const firstRuleAction = safeFirstRuleCheckoutHref
1537
- ? `<a data-i="first_failure_rule_checkout" href="${safeFirstRuleCheckoutHref}">Pay $1 first rule</a>`
1538
- : '';
1539
- const quickReadAction = safeQuickReadCheckoutHref
1540
- ? `<a data-i="quick_read_checkout" href="${safeQuickReadCheckoutHref}">Pay $19 quick read</a>`
1541
- : '';
1542
- const teardownAction = safeWorkflowTeardownCheckoutHref
1543
- ? `<a data-i="workflow_teardown_checkout" href="${safeWorkflowTeardownCheckoutHref}">Pay $99 teardown</a>`
1544
- : '';
1545
- return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm โ€” ThumbGate Pro</title><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,-apple-system,sans-serif;line-height:1.5}main{max-width:520px;margin:8vh auto;padding:0 20px}.brand{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:14px;color:#94a3b8}.brand-mark{width:24px;height:24px;background:#22d3ee;border-radius:6px;display:inline-block}h1{font-size:24px;margin:0 0 8px;color:#fff}.price{font-size:32px;font-weight:700;color:#22d3ee;margin:8px 0 4px}.price small{font-size:14px;color:#94a3b8;font-weight:400}p{color:#cbd5e1;margin:8px 0}a{display:block;text-decoration:none}a.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:20px 0 10px}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0;font-size:14px}.trust{margin:24px 0;padding:16px;border:1px solid #1f2937;border-radius:8px;background:#0f172a}.trust-item{font-size:13px;color:#cbd5e1;padding:4px 0;display:flex;gap:8px}.trust-item::before{content:"โœ“";color:#22d3ee;font-weight:700}details{margin-top:32px;font-size:13px;color:#94a3b8}details summary{cursor:pointer;padding:8px 0}details a{border:1px solid #374151;color:#94a3b8;padding:10px;text-align:center;border-radius:6px;margin:6px 0;font-size:13px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}</style><main><div class="brand"><span class="brand-mark"></span><span>ThumbGate</span></div><h1>Start ThumbGate Pro</h1><div class="price">$19<small>/mo</small></div><p>Block every repeat AI-agent mistake. Local-first. MIT-licensed CLI included. Cancel anytime.</p><a class="primary" data-i="pro_checkout_confirmed" href="${safeConfirmHref}">Pay $19/mo with Stripe โ†’</a><div class="trust"><div class="trust-item">6 paying customers, 18,000+ installs verified on npm</div><div class="trust-item">Cancel anytime โ€” instant refund within 7 days</div><div class="trust-item">MIT open source ยท no vendor lock-in</div><div class="trust-item">Works with Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode</div></div><details><summary>Other paid paths (diagnostic, sprint, teardown, single-rule)</summary>${diagnosticAction.replace('<a ', '<a class="secondary" ')}${sprintAction.replace('<a ', '<a class="secondary" ')}${teardownAction.replace('<a ', '<a class="secondary" ')}${quickReadAction.replace('<a ', '<a class="secondary" ')}${firstRuleAction.replace('<a ', '<a class="secondary" ')}<a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Send workflow first (intake)</a><a class="secondary" data-i="team_paid_path" href="${safeTeamOptionsHref}">See all options</a></details><p class="back"><a href="/">โ† Back to thumbgate.ai</a></p></main><script>addEventListener('click',e=>{let a=e.target.closest('[data-i]');if(a&&navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:a.dataset.i,ctaPlacement:'checkout_interstitial'})],{type:'application/json'}))})</script></html>`;
1506
+ return `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Confirm โ€” ThumbGate Pro</title><style>body{background:#0a0a0a;color:#eee;font-family:system-ui,-apple-system,sans-serif;line-height:1.5}main{max-width:520px;margin:8vh auto;padding:0 20px}.brand{display:flex;align-items:center;gap:10px;margin-bottom:24px;font-size:14px;color:#94a3b8}.brand-mark{width:24px;height:24px;background:#22d3ee;border-radius:6px;display:inline-block}h1{font-size:24px;margin:0 0 8px;color:#fff}.price{font-size:32px;font-weight:700;color:#22d3ee;margin:8px 0 4px}.price small{font-size:14px;color:#94a3b8;font-weight:400}p{color:#cbd5e1;margin:8px 0}a{display:block;text-decoration:none}a.primary{background:#22d3ee;color:#000;padding:16px;text-align:center;border-radius:8px;font-weight:700;font-size:16px;margin:20px 0 10px}a.secondary{border:1px solid #374151;color:#cbd5e1;padding:12px;text-align:center;border-radius:8px;margin:8px 0 0;font-size:14px}.trust{margin:24px 0;padding:16px;border:1px solid #1f2937;border-radius:8px;background:#0f172a}.trust-item{font-size:13px;color:#cbd5e1;padding:4px 0;display:flex;gap:8px}.trust-item::before{content:"โœ“";color:#22d3ee;font-weight:700}.choice-note{font-size:13px;color:#94a3b8;margin-top:14px}.back{text-align:center;color:#64748b;font-size:12px;margin-top:24px}.back a{color:#64748b;display:inline}</style><main><div class="brand"><span class="brand-mark"></span><span>ThumbGate</span></div><h1>Start ThumbGate Pro</h1><div class="price">$19<small>/mo</small></div><p>Block every repeat AI-agent mistake. Local-first. MIT-licensed CLI included. Cancel anytime.</p><a class="primary" data-i="pro_checkout_confirmed" href="${safeConfirmHref}">Pay $19/mo with Stripe โ†’</a><a class="secondary" data-i="workflow_sprint_intake" href="${safeWorkflowIntakeHref}">Not sure yet? Send the workflow first</a><p class="choice-note">One checkout path here. Diagnostics, sprints, kits, and custom services live outside the Pro checkout so the buyer is not asked to choose between unrelated offers.</p><div class="trust"><div class="trust-item">6 paying customers, 18,000+ installs verified on npm</div><div class="trust-item">Cancel anytime โ€” instant refund within 7 days</div><div class="trust-item">MIT open source ยท no vendor lock-in</div><div class="trust-item">Works with Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode</div></div><p class="back"><a href="/">โ† Back to thumbgate.ai</a></p></main><script>addEventListener('click',e=>{let a=e.target.closest('[data-i]');if(a&&navigator.sendBeacon)navigator.sendBeacon('/v1/telemetry/ping',new Blob([JSON.stringify({eventType:'checkout_interstitial_cta_clicked',clientType:'web',page:'/checkout/pro',ctaId:a.dataset.i,ctaPlacement:'checkout_interstitial'})],{type:'application/json'}))})</script></html>`;
1546
1507
  }
1547
1508
 
1548
1509
  function buildCheckoutBootstrapBody(parsed, req, journeyState = resolveJourneyState(req, parsed)) {
@@ -4617,67 +4578,9 @@ async function addContext(){
4617
4578
  ctaPlacement: 'checkout_interstitial',
4618
4579
  planId: 'team',
4619
4580
  });
4620
- const teamOptionsHref = buildCheckoutIntentHref(`${hostedConfig.appOrigin}/guides/ai-agent-governance-sprint`, analyticsMetadata, {
4621
- utmMedium: 'checkout_interstitial_paid_path',
4622
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_team_paid_path',
4623
- ctaId: 'checkout_interstitial_team_paid_path',
4624
- ctaPlacement: 'checkout_interstitial',
4625
- planId: 'team',
4626
- });
4627
- const firstRuleCheckoutHref = buildCheckoutIntentHref(FIRST_FAILURE_RULE_CHECKOUT_URL, analyticsMetadata, {
4628
- utmMedium: 'checkout_interstitial_paid_path',
4629
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_first_failure_rule',
4630
- ctaId: 'checkout_interstitial_first_failure_rule_checkout',
4631
- ctaPlacement: 'checkout_interstitial',
4632
- planId: 'first_failure_rule',
4633
- });
4634
- const quickReadCheckoutHref = buildCheckoutIntentHref(QUICK_READ_CHECKOUT_URL, analyticsMetadata, {
4635
- utmMedium: 'checkout_interstitial_paid_path',
4636
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_quick_read',
4637
- ctaId: 'checkout_interstitial_quick_read_checkout',
4638
- ctaPlacement: 'checkout_interstitial',
4639
- planId: 'quick_read',
4640
- });
4641
- const workflowTeardownCheckoutHref = buildCheckoutIntentHref(WORKFLOW_TEARDOWN_CHECKOUT_URL, analyticsMetadata, {
4642
- utmMedium: 'checkout_interstitial_paid_path',
4643
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_teardown',
4644
- ctaId: 'checkout_interstitial_workflow_teardown_checkout',
4645
- ctaPlacement: 'checkout_interstitial',
4646
- planId: 'workflow_teardown',
4647
- });
4648
- const diagnosticCheckoutHref = buildCheckoutIntentHref(
4649
- hostedConfig.sprintDiagnosticCheckoutUrl || SPRINT_DIAGNOSTIC_CHECKOUT_URL,
4650
- analyticsMetadata,
4651
- {
4652
- utmMedium: 'checkout_interstitial_paid_path',
4653
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_diagnostic',
4654
- ctaId: 'checkout_interstitial_sprint_diagnostic_checkout',
4655
- ctaPlacement: 'checkout_interstitial',
4656
- planId: 'sprint_diagnostic',
4657
- }
4658
- );
4659
- const sprintCheckoutHref = buildCheckoutIntentHref(
4660
- hostedConfig.workflowSprintCheckoutUrl || WORKFLOW_SPRINT_CHECKOUT_URL,
4661
- analyticsMetadata,
4662
- {
4663
- utmMedium: 'checkout_interstitial_paid_path',
4664
- utmCampaign: analyticsMetadata.utmCampaign || 'checkout_interstitial_workflow_sprint',
4665
- ctaId: 'checkout_interstitial_workflow_sprint_checkout',
4666
- ctaPlacement: 'checkout_interstitial',
4667
- planId: 'workflow_sprint',
4668
- }
4669
- );
4670
4581
  const html = renderCheckoutIntentPage({
4671
4582
  confirmHref: buildCheckoutConfirmHref(parsed),
4672
- firstRuleCheckoutHref,
4673
- quickReadCheckoutHref,
4674
- workflowTeardownCheckoutHref,
4675
4583
  workflowIntakeHref,
4676
- teamOptionsHref,
4677
- diagnosticCheckoutHref,
4678
- sprintCheckoutHref,
4679
- sprintDiagnosticPriceDollars: hostedConfig.sprintDiagnosticPriceDollars || 499,
4680
- workflowSprintPriceDollars: hostedConfig.workflowSprintPriceDollars || 1500,
4681
4584
  botClassification,
4682
4585
  });
4683
4586
  sendHtml(res, 200, html, responseHeaders);