thumbgate 1.27.17 → 1.27.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +33 -5
  5. package/adapters/chatgpt/openapi.yaml +1707 -0
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/codex/config.toml +2 -2
  8. package/adapters/mcp/server-stdio.js +1 -1
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bin/cli.js +63 -54
  11. package/bin/postinstall.js +23 -14
  12. package/config/post-deploy-marketing-pages.json +19 -0
  13. package/config/tessl-tiles.json +1 -3
  14. package/package.json +99 -229
  15. package/public/guide.html +0 -4
  16. package/public/index.html +28 -49
  17. package/public/learn.html +0 -8
  18. package/public/numbers.html +2 -2
  19. package/scripts/agent-readiness.js +0 -147
  20. package/scripts/bot-detection.js +61 -3
  21. package/scripts/cli-feedback.js +4 -14
  22. package/scripts/cli-schema.js +7 -0
  23. package/scripts/durability/step.js +3 -3
  24. package/scripts/feedback-loop.js +10 -32
  25. package/scripts/gates-engine.js +72 -1
  26. package/scripts/hook-runtime.js +2 -2
  27. package/scripts/license.js +10 -9
  28. package/scripts/llm-client.js +44 -260
  29. package/scripts/mcp-config.js +7 -1
  30. package/scripts/published-cli.js +0 -8
  31. package/scripts/seo-gsd.js +0 -5
  32. package/src/api/server.js +111 -35
  33. package/.claude/commands/dashboard.md +0 -15
  34. package/.claude/commands/thumbgate-blocked.md +0 -27
  35. package/.claude/commands/thumbgate-dashboard.md +0 -15
  36. package/.claude/commands/thumbgate-doctor.md +0 -30
  37. package/.claude/commands/thumbgate-guard.md +0 -36
  38. package/.claude/commands/thumbgate-protect.md +0 -30
  39. package/.claude/commands/thumbgate-rules.md +0 -30
  40. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  41. package/adapters/letta/README.md +0 -41
  42. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  43. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  44. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  45. package/bench/observability-eval-suite.json +0 -26
  46. package/bin/dashboard-cli.js +0 -7
  47. package/commands/dashboard.md +0 -15
  48. package/commands/thumbgate-dashboard.md +0 -15
  49. package/config/builtin-lessons.json +0 -23
  50. package/public/about.html +0 -162
  51. package/public/agent-manager.html +0 -179
  52. package/public/agents-cost-savings.html +0 -153
  53. package/public/ai-malpractice-prevention.html +0 -818
  54. package/public/assets/brand/github-social-preview.png +0 -0
  55. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  56. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  57. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  58. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  59. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  60. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  61. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  62. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  63. package/public/assets/brand/thumbgate-mark.svg +0 -21
  64. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  65. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  66. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  67. package/public/assets/legal-intake-control-flow.svg +0 -66
  68. package/public/brand/thumbgate-mark.svg +0 -19
  69. package/public/brand/thumbgate-og.svg +0 -16
  70. package/public/chatgpt-app.html +0 -330
  71. package/public/codex-enterprise.html +0 -123
  72. package/public/js/buyer-intent.js +0 -672
  73. package/public/pricing.html +0 -399
  74. package/scripts/action-receipts.js +0 -324
  75. package/scripts/activation-quickstart.js +0 -187
  76. package/scripts/agent-operations-planner.js +0 -621
  77. package/scripts/ai-component-inventory.js +0 -367
  78. package/scripts/async-eval-observability.js +0 -236
  79. package/scripts/audit.js +0 -65
  80. package/scripts/aws-blocks-guardrails.js +0 -228
  81. package/scripts/bot-detector.js +0 -50
  82. package/scripts/classifier-routing.js +0 -130
  83. package/scripts/dashboard-chat.js +0 -332
  84. package/scripts/feedback-aggregate.js +0 -281
  85. package/scripts/feedback-sanitizer.js +0 -105
  86. package/scripts/hob-pack.js +0 -591
  87. package/scripts/hook-stop-anti-claim.js +0 -301
  88. package/scripts/install-shim.js +0 -87
  89. package/scripts/mcp-oauth.js +0 -293
  90. package/scripts/noop-detect.js +0 -285
  91. package/scripts/omlx-smoke.js +0 -192
  92. package/scripts/parallel-workflow-orchestrator.js +0 -293
  93. package/scripts/plan-gate.js +0 -243
  94. package/scripts/plausible-domain-config.js +0 -99
  95. package/scripts/qa-scenario-planner.js +0 -136
  96. package/scripts/repeat-metric.js +0 -137
  97. package/scripts/secret-fixture-tokens.js +0 -61
  98. package/scripts/secret-redaction.js +0 -166
  99. package/scripts/self-harness-optimizer.js +0 -141
  100. package/scripts/self-healing-check.js +0 -193
  101. package/scripts/silent-failure-cluster.js +0 -531
  102. package/scripts/statusline-cache-read.js +0 -57
  103. package/scripts/sync-telemetry-from-prod.js +0 -374
  104. package/scripts/tool-contract-validator.js +0 -76
  105. package/scripts/trajectory-scorer.js +0 -63
  106. package/scripts/verify-marketing-pages-deployed.js +0 -195
  107. package/scripts/visitor-journey.js +0 -172
package/public/guide.html CHANGED
@@ -366,10 +366,6 @@ npx thumbgate init --agent gemini</code></pre>
366
366
  <strong>Verification Evidence</strong>
367
367
  <span>Human-readable proof log for the engineering and workflow claims used across the site.</span>
368
368
  </a>
369
- <a href="https://github.com/IgorGanapolsky/ThumbGate/blob/main/proof/automation/report.json" target="_blank" rel="noopener">
370
- <strong>Automation Proof</strong>
371
- <span>Machine-readable report for the feedback, enforcement, and automation surfaces behind ThumbGate.</span>
372
- </a>
373
369
  </div>
374
370
 
375
371
  <h2>Get Started</h2>
package/public/index.html CHANGED
@@ -20,7 +20,7 @@ __GOOGLE_SITE_VERIFICATION_META__
20
20
  <meta property="og:image" content="https://thumbgate.ai/og.png">
21
21
  <meta name="twitter:card" content="summary_large_image">
22
22
  <meta name="twitter:image" content="https://thumbgate.ai/og.png">
23
- <meta name="thumbgate-version" content="1.27.17">
23
+ <meta name="thumbgate-version" content="1.27.15">
24
24
  <meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agentic development cycle, AC/DC framework, Guide Generate Verify Solve, 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">
25
25
  <link rel="canonical" href="__APP_ORIGIN__/">
26
26
  <link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
@@ -411,9 +411,10 @@ __GA_BOOTSTRAP__
411
411
  /* NAV */
412
412
  nav { position: sticky; top: 0; z-index: 50; background: rgba(10,10,11,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid var(--border); padding: 14px 0; }
413
413
  nav .container { display: flex; justify-content: space-between; align-items: center; }
414
- .nav-logo { color: var(--text); text-decoration: none; display: inline-flex; align-items: center; flex: 0 0 auto; }
415
- .nav-logo .logo-mark { width: 220px; height: 66px; display: block; flex: 0 0 auto; object-fit: contain; }
416
- .nav-logo .logo-text { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
414
+ .nav-logo { font-weight: 700; font-size: 15px; letter-spacing: -0.02em; color: var(--text); text-decoration: none; display: inline-flex; align-items: center; gap: 8px; }
415
+ .nav-logo .logo-mark { width: 28px; height: 28px; display: block; }
416
+ .nav-logo .logo-text { color: var(--text); }
417
+ .nav-logo span { color: var(--cyan); }
417
418
  .nav-links { display: flex; gap: 24px; align-items: center; }
418
419
  .nav-links a { color: var(--text-muted); text-decoration: none; font-size: 13px; transition: color 0.15s; }
419
420
  .nav-links a:hover { color: var(--text); }
@@ -446,20 +447,13 @@ __GA_BOOTSTRAP__
446
447
  .hero-actions { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 12px; margin: 0 auto 16px; max-width: 900px; }
447
448
  .hero-actions a, .hero-actions .hero-install { min-height: 48px; border-radius: 8px; }
448
449
  .hero-actions .hero-install { margin-bottom: 0; font-size: 18px; padding: 14px 22px; border: 1px solid rgba(34,211,238,0.55); background: rgba(34,211,238,0.07); box-shadow: none; }
449
- .hero-actions .btn-install-hero { font-size: 17px; padding: 14px 24px; font-weight: 800; background: var(--cyan); color: var(--bg); border: 1px solid rgba(34,211,238,0.65); box-shadow: 0 0 0 1px rgba(34,211,238,0.28), 0 12px 32px rgba(34,211,238,0.18); }
450
+ .hero-actions .btn-install-hero { font-size: 16px; padding: 14px 22px; }
450
451
  .hero-actions .hero-diagnostic { font-size: 16px; padding: 14px 22px; background: var(--green); color: #06120a; font-weight: 800; box-shadow: none; }
451
452
  .offer-router{max-width:940px;margin:18px auto 22px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;text-align:left}
452
453
  .offer-route{border:1px solid rgba(34,211,238,.18);background:rgba(17,17,19,.72);border-radius:8px;padding:14px}
453
454
  .offer-route strong{display:block;color:var(--text);font-size:14px;margin-bottom:6px}.offer-route p{margin:0 0 10px;color:var(--text-muted);font-size:13px}
454
455
  .offer-route a,.offer-route button{color:var(--cyan);font-weight:800;font-size:13px;text-decoration:none;background:transparent;border:0;padding:0;cursor:pointer}.offer-route.primary{background:rgba(34,211,238,.07)}
455
- .repeat-proof-strip { max-width: 760px; margin: 0 auto 24px; display: grid; grid-template-columns: 1fr auto 1fr; gap: 10px; align-items: stretch; text-align: left; }
456
- .repeat-proof-step { border: 1px solid rgba(34,211,238,0.18); background: rgba(17,17,19,0.76); border-radius: 8px; padding: 12px 14px; min-height: 74px; }
457
- .repeat-proof-step strong { display: block; color: var(--text); font-size: 13px; margin-bottom: 4px; }
458
- .repeat-proof-step code { display: block; color: var(--text-muted); font-family: var(--mono); font-size: 12px; line-height: 1.45; white-space: normal; }
459
- .repeat-proof-step.blocked { border-color: rgba(248,113,113,0.34); background: rgba(248,113,113,0.08); }
460
- .repeat-proof-arrow { display: flex; align-items: center; justify-content: center; color: var(--cyan); font-weight: 800; }
461
456
  .btn-pro-page { display: inline-flex; align-items: center; justify-content: center; padding: 11px 18px; background: var(--cyan); color: var(--bg); border-radius: 999px; text-decoration: none; font-size: 14px; font-weight: 700; box-shadow: 0 0 0 1px rgba(34,211,238,0.28), 0 12px 32px rgba(34,211,238,0.18); transition: opacity 0.15s, transform 0.15s; }
462
- .btn-pro-page.hero-secondary { background: rgba(17,17,19,0.72); color: var(--cyan); border: 1px solid rgba(34,211,238,0.32); box-shadow: none; }
463
457
  .btn-pro-page:hover { opacity: 0.9; transform: translateY(-1px); }
464
458
  .btn-gpt-page { display: inline-flex; align-items: center; justify-content: center; padding: 14px 32px; background: var(--green); color: #06120a; border-radius: 8px; text-decoration: none; font-size: 16px; font-weight: 800; box-shadow: 0 0 0 1px rgba(74,222,128,0.32), 0 12px 32px rgba(74,222,128,0.18); transition: opacity 0.15s, transform 0.15s; }
465
459
  .btn-gpt-page:hover { opacity: 0.92; transform: translateY(-1px); }
@@ -712,8 +706,6 @@ __GA_BOOTSTRAP__
712
706
  .hero-actions { flex-direction: column; }
713
707
  .hero-actions a { width: 100%; }
714
708
  .offer-router { grid-template-columns: 1fr; }
715
- .repeat-proof-strip { grid-template-columns: 1fr; }
716
- .repeat-proof-arrow { min-height: 20px; }
717
709
  .hero-paid-actions { justify-content: center; }
718
710
  .hero-paid-actions a { width: 100%; }
719
711
  .nav-links a:not(.nav-cta) { display: none; }
@@ -738,7 +730,7 @@ __GA_BOOTSTRAP__
738
730
  <!-- NAV -->
739
731
  <nav>
740
732
  <div class="container">
741
- <a href="/" class="nav-logo" aria-label="ThumbGate home"><img src="/assets/brand/thumbgate-logo-transparent.svg?v=20260623-transparent-wordmark-nav" alt="" class="logo-mark" width="220" height="66"><span class="logo-text">ThumbGate</span></a>
733
+ <a href="/" class="nav-logo"><img src="/assets/brand/thumbgate-mark-inline-v3.svg" alt="ThumbGate" class="logo-mark" width="28" height="28"><span class="logo-text">ThumbGate</span></a>
742
734
  <div class="nav-links">
743
735
  <a href="#how-it-works">How It Works</a>
744
736
  <a href="/pricing">Pricing</a>
@@ -763,27 +755,15 @@ __GA_BOOTSTRAP__
763
755
  <!-- HERO -->
764
756
  <section class="hero">
765
757
  <div class="container">
766
- <div class="hero-badge">For AI coders who already corrected the same agent twice</div>
767
- <h1>Stop repeated AI agent mistakes before they run again.</h1>
768
- <p class="hero-lede">ThumbGate turns one thumbs-down into a local pre-action check. If Claude Code, Cursor, Codex, Gemini CLI, or another MCP-capable agent tries the same unsafe command, edit, deploy, retry loop, or API call again, ThumbGate blocks it before execution and records why.</p>
769
-
770
- <div class="hero-signals" aria-label="Repeated agent mistakes ThumbGate stops">
771
- <a class="signal-pill signal-down" href="#first-check">Repeated command</a>
772
- <a class="signal-pill" href="#first-check">Runaway retry loop</a>
773
- <a class="signal-pill" href="#first-check">Rejected edit returns</a>
774
- <a class="signal-pill signal-up" href="#pricing">Upgrade after proof</a>
775
- </div>
776
-
777
- <div class="repeat-proof-strip" aria-label="ThumbGate thumbs-down to blocked repeat example">
778
- <div class="repeat-proof-step">
779
- <strong>1. You reject it once</strong>
780
- <code>thumbs down: never force-push main without explicit approval</code>
781
- </div>
782
- <div class="repeat-proof-arrow">→</div>
783
- <div class="repeat-proof-step blocked">
784
- <strong>2. The repeat is stopped</strong>
785
- <code>agent: git push --force origin main<br>BLOCKED before execution</code>
786
- </div>
758
+ <div class="hero-badge">👍 👎 Local-first · open-source · Pro proof when it matters</div>
759
+ <h1>Stop AI agents before they spend money, leak secrets, or break production.</h1>
760
+ <p class="hero-lede">Claude Code, Cursor, Codex, Gemini CLI, and MCP tools now touch terminals, repos, databases, SaaS APIs, and cloud systems. ThumbGate checks the next tool call before it runs, blocks repeated failures from your thumbs-down feedback, and gives paid operators proof, sync, recall, and exports when local rules become business-critical.</p>
761
+
762
+ <div class="hero-signals" aria-label="June 2026 buyer signals">
763
+ <a class="signal-pill signal-down" href="#june-2026-proof">MCP tool risk</a>
764
+ <a class="signal-pill" href="#june-2026-proof">Managed-agent rollout</a>
765
+ <a class="signal-pill" href="#june-2026-proof">Token spend pressure</a>
766
+ <a class="signal-pill signal-up" href="#pricing">Free wedge, paid proof</a>
787
767
  </div>
788
768
 
789
769
  <div class="hero-proof-card" aria-label="ThumbGate blocking dangerous AI agent commands in real time">
@@ -791,26 +771,26 @@ __GA_BOOTSTRAP__
791
771
  </div>
792
772
 
793
773
  <div class="hero-actions">
774
+ <a href="/checkout/pro?utm_source=website&utm_medium=hero_cta&utm_campaign=pro_upgrade&cta_id=hero_start_pro&cta_placement=hero&plan_id=pro&landing_path=%2F" data-revenue-cta data-cta-id="hero_start_pro" data-cta-placement="hero" data-tier="pro" data-plan-id="pro" data-price="19" onclick="trackRevenueCta(this);try{posthog.capture('hero_pro_checkout_click',{cta:'hero_start_pro',tier:'pro',price:19})}catch(_){}" class="btn-pro-page hero-pro hero-pro-primary">Start Pro — $19/mo</a>
775
+ <a href="#workflow-sprint-intake" onclick="try{posthog.capture('hero_sprint_click',{cta:'sprint_intake'})}catch(_){};sendFirstPartyTelemetry('hero_sprint_intake_started',{ctaId:'hero_workflow_sprint',ctaPlacement:'hero',offer:'workflow_sprint'});" class="btn-pro-page hero-pro">Workflow Hardening Sprint →</a>
794
776
  <a href="/go/install?utm_source=website&utm_medium=hero_cta&utm_campaign=install_free&cta_id=hero_install_cli&cta_placement=hero" onclick="event.preventDefault(); navigator.clipboard.writeText('npx thumbgate init'); this.textContent='Copied ✓ — paste in your repo'; setTimeout(()=>{this.textContent='Install Free CLI'},2000); try{posthog.capture('hero_install_click',{cta:'install_cli'})}catch(_){}" class="btn-free btn-install-hero" title="Click to copy: npx thumbgate init">Install Free CLI</a>
795
- <a href="/checkout/pro?utm_source=website&utm_medium=hero_cta&utm_campaign=pro_upgrade&cta_id=hero_start_pro&cta_placement=hero&plan_id=pro&landing_path=%2F" data-revenue-cta data-cta-id="hero_start_pro" data-cta-placement="hero" data-tier="pro" data-plan-id="pro" data-price="19" onclick="trackRevenueCta(this);try{posthog.capture('hero_pro_checkout_click',{cta:'hero_start_pro',tier:'pro',price:19})}catch(_){}" class="btn-pro-page hero-pro hero-pro-primary hero-secondary">Upgrade after one blocked repeat</a>
796
- <a href="#workflow-sprint-intake" onclick="try{posthog.capture('hero_sprint_click',{cta:'sprint_intake'})}catch(_){};sendFirstPartyTelemetry('hero_sprint_intake_started',{ctaId:'hero_workflow_sprint',ctaPlacement:'hero',offer:'workflow_sprint'});" class="btn-pro-page hero-pro hero-secondary">Team workflow intake →</a>
797
777
  </div>
798
778
 
799
779
  <div class="offer-router" aria-label="Choose the right ThumbGate path">
800
780
  <div class="offer-route primary">
801
- <strong>Start here: Free CLI</strong>
802
- <p>2 captures/day, 3 active rules. Enough to prove one blocked repeat in your repo.</p>
803
- <button data-router-install onclick="copyInstall(this)"><span class="copy-hint">Copy npx thumbgate init</span></button>
781
+ <strong>Solo operator: Start Pro</strong>
782
+ <p>Unlimited rules, recall, sync, proof, exports.</p>
783
+ <a href="/checkout/pro?utm_source=website&utm_medium=offer_router&cta_id=router_start_pro&cta_placement=offer_router&plan_id=pro" data-revenue-cta data-cta-id="router_start_pro" data-cta-placement="offer_router" data-tier="pro" data-plan-id="pro" data-price="19" onclick="trackRevenueCta(this);">Pay $19/mo with Stripe →</a>
804
784
  </div>
805
785
  <div class="offer-route">
806
- <strong>After it blocks a repeat: Pro</strong>
807
- <p>Unlimited rules, recall, sync, dashboard proof, and exports for one operator.</p>
808
- <a href="/checkout/pro?utm_source=website&utm_medium=offer_router&cta_id=router_start_pro&cta_placement=offer_router&plan_id=pro" data-revenue-cta data-cta-id="router_start_pro" data-cta-placement="offer_router" data-tier="pro" data-plan-id="pro" data-price="19" onclick="trackRevenueCta(this);">Upgrade to Pro →</a>
786
+ <strong>Enterprise workflow: Start with intake</strong>
787
+ <p>One repeated failure, one owner, one proof plan.</p>
788
+ <a href="#workflow-sprint-intake" onclick="sendFirstPartyTelemetry('workflow_sprint_intake_started',{ctaId:'router_workflow_sprint',ctaPlacement:'offer_router',offer:'workflow_sprint'});">Talk to us →</a>
809
789
  </div>
810
790
  <div class="offer-route">
811
- <strong>Team has the same failure: intake</strong>
812
- <p>One repeated workflow, one owner, one proof plan before a wider rollout.</p>
813
- <a href="#workflow-sprint-intake" onclick="sendFirstPartyTelemetry('workflow_sprint_intake_started',{ctaId:'router_workflow_sprint',ctaPlacement:'offer_router',offer:'workflow_sprint'});">Send workflow first →</a>
791
+ <strong>Still evaluating: Free CLI</strong>
792
+ <p>2 captures/day, 3 active rules. Enough to prove one blocked repeat.</p>
793
+ <button data-router-install onclick="copyInstall(this)"><span class="copy-hint">Copy npx thumbgate init</span></button>
814
794
  </div>
815
795
  </div>
816
796
 
@@ -1287,7 +1267,6 @@ __GA_BOOTSTRAP__
1287
1267
  <a href="/guides/relational-knowledge-ai-recommendations">Relational knowledge</a>
1288
1268
  <a href="/guides/claude-code-feedback">Claude Code feedback</a>
1289
1269
  <a href="/guides/autoresearch-agent-safety">Autoresearch safety</a>
1290
- <a href="/guides/aws-blocks-agent-guardrails">AWS Blocks guardrails</a>
1291
1270
  </div>
1292
1271
  </div>
1293
1272
  </div>
@@ -1815,7 +1794,7 @@ __GA_BOOTSTRAP__
1815
1794
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
1816
1795
  <a href="/blog">Blog</a>
1817
1796
  </div>
1818
- <span class="footer-copy">© 2026 ThumbGate · MIT License · npm v1.27.17</span>
1797
+ <span class="footer-copy">© 2026 ThumbGate · MIT License · npm v1.27.15</span>
1819
1798
  </div>
1820
1799
  </footer>
1821
1800
 
package/public/learn.html CHANGED
@@ -457,14 +457,6 @@
457
457
  <span class="article-tag">Sprint</span>
458
458
  </a>
459
459
 
460
- <a href="/guides/aws-blocks-agent-guardrails" class="article-card">
461
- <h3>AWS Blocks Agent Guardrails</h3>
462
- <p>Guard the local-to-cloud jump before AWS Blocks agents deploy, destroy resources, mutate data, expand IAM, or call Bedrock tools.</p>
463
- <span class="article-tag">AWS Blocks</span>
464
- <span class="article-tag">Cloud Safety</span>
465
- <span class="article-tag">Pre-action Gates</span>
466
- </a>
467
-
468
460
  <a href="/guides/ai-search-topical-presence" class="article-card">
469
461
  <h3>AI Search Topical Presence</h3>
470
462
  <p>Why AI assistants recommend the tools they repeatedly see tied to a buyer problem, and how ThumbGate builds that association with proof-backed pages.</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.27.17",
28
+ "softwareVersion": "1.27.15",
29
29
  "url": "https://thumbgate.ai/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.27.17</div>
205
+ <div class="freshness">Updated: 2026-05-07 · Version 1.27.15</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>
@@ -2,7 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  const fs = require('fs');
5
- const os = require('os');
6
5
  const path = require('path');
7
6
  const {
8
7
  getActiveMcpProfile,
@@ -166,141 +165,6 @@ function summarizePermissionTier(profileName = getActiveMcpProfile()) {
166
165
  };
167
166
  }
168
167
 
169
- function flattenHookCommands(value, commands = []) {
170
- if (!value) return commands;
171
- if (typeof value === 'string') {
172
- commands.push(value);
173
- return commands;
174
- }
175
- if (Array.isArray(value)) {
176
- value.forEach((item) => flattenHookCommands(item, commands));
177
- return commands;
178
- }
179
- if (typeof value === 'object') {
180
- if (typeof value.command === 'string') commands.push(value.command);
181
- Object.entries(value)
182
- .filter(([key]) => key !== 'command')
183
- .forEach(([, item]) => flattenHookCommands(item, commands));
184
- }
185
- return commands;
186
- }
187
-
188
- function readJsonIfExists(filePath) {
189
- try {
190
- if (!fs.existsSync(filePath)) return null;
191
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
192
- } catch (_) {
193
- return null;
194
- }
195
- }
196
-
197
- function detectHookAutoCapture(projectRoot) {
198
- const effectiveRoot = projectRoot || findProjectRoot();
199
- const settingsFiles = ['.claude/settings.json', '.claude/settings.local.json']
200
- .map((file) => path.join(effectiveRoot, file))
201
- .filter((file) => fs.existsSync(file));
202
- const commands = settingsFiles.flatMap((file) => {
203
- const parsed = readJsonIfExists(file);
204
- return flattenHookCommands(parsed && parsed.hooks && parsed.hooks.UserPromptSubmit);
205
- });
206
- const wiredCommands = commands.filter((command) => /hook-auto-capture(\.sh)?\b/.test(command));
207
-
208
- return {
209
- ready: wiredCommands.length > 0,
210
- settingsFiles: settingsFiles.map((file) => path.relative(effectiveRoot, file)),
211
- wiredCommands,
212
- recommendation: wiredCommands.length > 0
213
- ? 'UserPromptSubmit auto-capture hook is wired for typed thumbs feedback.'
214
- : 'Run `npx thumbgate init --wire-hooks` so typed thumbs up/down feedback is captured automatically.',
215
- };
216
- }
217
-
218
- function probeFeedbackCapture(projectRoot) {
219
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-doctor-feedback-'));
220
- const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
221
- const previousProjectDir = process.env.THUMBGATE_PROJECT_DIR;
222
- const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
223
-
224
- process.env.THUMBGATE_FEEDBACK_DIR = tempDir;
225
- // Keep this probe isolated. Direct project scope intentionally overrides
226
- // THUMBGATE_FEEDBACK_DIR in the runtime resolver, which is correct for real
227
- // project writes but wrong for a doctor self-test.
228
- delete process.env.THUMBGATE_PROJECT_DIR;
229
- process.env.THUMBGATE_NO_NUDGE = '1';
230
-
231
- try {
232
- const {
233
- captureFeedback,
234
- getFeedbackPaths,
235
- readJSONL,
236
- } = require('./feedback-loop');
237
- const result = captureFeedback({
238
- signal: 'down',
239
- context: 'doctor feedback loop probe: prove thumbs feedback persists to durable logs',
240
- whatWentWrong: 'A runtime claimed thumbs feedback worked without verifying durable capture',
241
- whatToChange: 'Run thumbgate doctor or feedback-self-test before claiming the loop is wired',
242
- tags: 'doctor,feedback-loop-probe',
243
- });
244
- const paths = getFeedbackPaths();
245
- const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
246
- const memoryId = result.memoryRecord && result.memoryRecord.id;
247
- const feedbackStored = Boolean(feedbackId && readJSONL(paths.FEEDBACK_LOG_PATH).some((row) => row.id === feedbackId));
248
- const memoryStored = Boolean(memoryId && readJSONL(paths.MEMORY_LOG_PATH).some((row) => row.id === memoryId));
249
-
250
- return {
251
- ready: Boolean(result.accepted && feedbackStored && memoryStored),
252
- accepted: Boolean(result.accepted),
253
- feedbackStored,
254
- memoryStored,
255
- feedbackId: feedbackId || null,
256
- memoryId: memoryId || null,
257
- mode: 'isolated-temp-store',
258
- feedbackDir: paths.FEEDBACK_DIR,
259
- recommendation: (result.accepted && feedbackStored && memoryStored)
260
- ? 'Feedback capture writes durable feedback and memory records.'
261
- : 'Feedback capture did not persist both feedback and memory records; run `npx thumbgate feedback-self-test --json`.',
262
- };
263
- } catch (error) {
264
- return {
265
- ready: false,
266
- accepted: false,
267
- feedbackStored: false,
268
- memoryStored: false,
269
- feedbackId: null,
270
- memoryId: null,
271
- mode: 'isolated-temp-store',
272
- feedbackDir: tempDir,
273
- error: error && error.message ? error.message : String(error),
274
- recommendation: 'Feedback capture probe failed; run `npx thumbgate feedback-self-test --json` for details.',
275
- };
276
- } finally {
277
- if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
278
- else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
279
- if (previousProjectDir === undefined) delete process.env.THUMBGATE_PROJECT_DIR;
280
- else process.env.THUMBGATE_PROJECT_DIR = previousProjectDir;
281
- if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
282
- else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
283
- try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch (_) { /* ignore cleanup failure */ }
284
- }
285
- }
286
-
287
- function assessFeedbackLoop(projectRoot) {
288
- const hookAutoCapture = detectHookAutoCapture(projectRoot);
289
- const captureProbe = probeFeedbackCapture(projectRoot);
290
- const ready = hookAutoCapture.ready && captureProbe.ready;
291
-
292
- return {
293
- ready,
294
- automatic: hookAutoCapture.ready,
295
- durableCapture: captureProbe.ready,
296
- hookAutoCapture,
297
- captureProbe,
298
- recommendation: ready
299
- ? 'Typed thumbs feedback is automatically detected and durable capture is verified.'
300
- : 'Install hooks and verify capture before claiming thumbs feedback improves future runs.',
301
- };
302
- }
303
-
304
168
  function generateAgentReadinessReport({
305
169
  projectRoot = PROJECT_ROOT,
306
170
  mcpProfile = null,
@@ -308,13 +172,11 @@ function generateAgentReadinessReport({
308
172
  const runtime = detectRuntimeIsolation();
309
173
  const bootstrap = collectBootstrapFiles(projectRoot);
310
174
  const permissions = summarizePermissionTier(mcpProfile || getActiveMcpProfile());
311
- const feedbackLoop = assessFeedbackLoop(projectRoot);
312
175
 
313
176
  const warnings = [];
314
177
  if (!runtime.isolated) warnings.push(runtime.recommendation);
315
178
  if (!bootstrap.ready) warnings.push(bootstrap.recommendation);
316
179
  if (!permissions.ready) warnings.push(permissions.recommendation);
317
- if (!feedbackLoop.ready) warnings.push(feedbackLoop.recommendation);
318
180
 
319
181
  return {
320
182
  generatedAt: new Date().toISOString(),
@@ -323,12 +185,10 @@ function generateAgentReadinessReport({
323
185
  runtime,
324
186
  bootstrap,
325
187
  permissions,
326
- feedbackLoop,
327
188
  articleAlignment: {
328
189
  runtimeIsolation: runtime.isolated,
329
190
  contextConditioning: bootstrap.ready,
330
191
  permissionEnvelope: permissions.ready,
331
- feedbackLoop: feedbackLoop.ready,
332
192
  },
333
193
  warnings,
334
194
  };
@@ -348,10 +208,6 @@ function reportToText(report) {
348
208
  lines.push(`Permissions: ${report.permissions.profile} (${report.permissions.tier})`);
349
209
  lines.push(` Write-capable tools: ${report.permissions.writeCapableTools.length}`);
350
210
  lines.push(` Recommendation: ${report.permissions.recommendation}`);
351
- lines.push(`Feedback loop: ${report.feedbackLoop.ready ? 'ready' : 'needs_attention'}`);
352
- lines.push(` Auto-capture hook: ${report.feedbackLoop.automatic ? 'wired' : 'missing'}`);
353
- lines.push(` Durable capture: ${report.feedbackLoop.durableCapture ? 'verified' : 'failed'}`);
354
- lines.push(` Recommendation: ${report.feedbackLoop.recommendation}`);
355
211
 
356
212
  if (report.warnings.length > 0) {
357
213
  lines.push('');
@@ -370,9 +226,6 @@ module.exports = {
370
226
  detectRuntimeIsolation,
371
227
  collectBootstrapFiles,
372
228
  summarizePermissionTier,
373
- detectHookAutoCapture,
374
- probeFeedbackCapture,
375
- assessFeedbackLoop,
376
229
  generateAgentReadinessReport,
377
230
  reportToText,
378
231
  };
@@ -36,13 +36,18 @@ const BOT_PATTERNS = [
36
36
  /\bbaiduspider\b/i,
37
37
  /\bduckduckbot\b/i,
38
38
  /\bapplebot\b/i,
39
+ /slurp/i,
40
+ /petalbot/i,
41
+ /mediapartners/i,
39
42
  // LLM / AI crawlers — these started exploding in 2024+
40
43
  /\bgptbot\b/i,
41
- /\bchatgpt-user\b/i,
44
+ /chatgpt/i,
42
45
  /\boai-searchbot\b/i,
43
- /\bperplexitybot\b/i,
44
- /\banthropic-ai\b/i,
46
+ /perplexity/i,
47
+ /anthropic/i,
45
48
  /\bclaude(?:bot|-web)\b/i,
49
+ /claude-searchbot/i,
50
+ /google-extended/i,
46
51
  /\bccbot\b/i,
47
52
  /\bcohere-ai\b/i,
48
53
  /\bbytespider\b/i,
@@ -86,6 +91,8 @@ const BOT_PATTERNS = [
86
91
  /\blibwww-perl\b/i,
87
92
  /\bjava\//i,
88
93
  /\bgo-http-client\b/i,
94
+ /scrapy/i,
95
+ /httpclient/i,
89
96
  /\bruby\b/i,
90
97
  // API/test tools
91
98
  /\bpostman(?:runtime)?\b/i,
@@ -158,8 +165,59 @@ function isProbablyBot(headers) {
158
165
  return classifyRequester(headers).isBot;
159
166
  }
160
167
 
168
+ // --- Visitor classification (analytics filtering) ---------------------------
169
+ // Classifies telemetry/analytics traffic as bot / owner / real_user.
170
+ // Owner traffic is identified via the THUMBGATE_OWNER_EMAILS env var
171
+ // (comma-separated, case-insensitive). There is no built-in default:
172
+ // operators opt in via config, so the shipped artifact carries no
173
+ // personal data.
174
+
175
+ function getOwnerEmails(env = process.env) {
176
+ const raw = env.THUMBGATE_OWNER_EMAILS;
177
+ if (!raw) return [];
178
+ return raw
179
+ .split(',')
180
+ .map((entry) => entry.trim().toLowerCase())
181
+ .filter(Boolean);
182
+ }
183
+
184
+ function classifyVisitor(req) {
185
+ const ua = (req.headers && req.headers['user-agent']) || '';
186
+ const email = req.email || (req.query && req.query.email) || '';
187
+
188
+ for (const pattern of BOT_PATTERNS) {
189
+ if (pattern.test(ua)) {
190
+ return { type: 'bot', reason: `UA matches: ${pattern}`, userAgent: ua };
191
+ }
192
+ }
193
+ if (!ua || ua.length < 10) {
194
+ return { type: 'bot', reason: 'Empty or short user-agent', userAgent: ua };
195
+ }
196
+ if (email) {
197
+ const normalized = email.toLowerCase();
198
+ if (getOwnerEmails().some((ownerEmail) => normalized.includes(ownerEmail))) {
199
+ return { type: 'owner', reason: 'Email matches configured owner list', userAgent: ua };
200
+ }
201
+ }
202
+ return { type: 'real_user', reason: 'No bot pattern matched', userAgent: ua };
203
+ }
204
+
205
+ function shouldExcludeFromAnalytics(req) {
206
+ const classification = req.visitorClass || classifyVisitor(req);
207
+ return classification.type === 'bot';
208
+ }
209
+
210
+ function botFilterMiddleware(req, res, next) {
211
+ req.visitorClass = classifyVisitor(req);
212
+ next();
213
+ }
214
+
161
215
  module.exports = {
162
216
  classifyRequester,
163
217
  isProbablyBot,
218
+ classifyVisitor,
219
+ shouldExcludeFromAnalytics,
220
+ botFilterMiddleware,
221
+ getOwnerEmails,
164
222
  BOT_PATTERNS,
165
223
  };
@@ -14,7 +14,6 @@
14
14
  */
15
15
 
16
16
  const { captureFeedback } = require('./feedback-loop');
17
- const { isGenericFeedbackText, normalizeFeedbackSignal } = require('./feedback-quality');
18
17
  const { loadOptionalModule } = require('./private-core-boundary');
19
18
  // `history-distiller` is a PRIVATE_CORE_MODULE — present in this checkout and
20
19
  // in ThumbGate-Core, but intentionally excluded from the public npm tarball.
@@ -48,21 +47,15 @@ const RST = '\x1b[0m';
48
47
  */
49
48
  function processInlineFeedback({ signal, context, chatHistory, whatWentWrong, whatWorked } = {}) {
50
49
  const isDown = signal === 'down' || signal === 'negative';
51
- const normalizedQualitySignal = normalizeFeedbackSignal(isDown ? 'down' : 'up');
52
- const providedContext = String(context || '').trim();
53
- const genericContext = isGenericFeedbackText(providedContext, normalizedQualitySignal);
54
- const effectiveWhatWentWrong = whatWentWrong || (isDown && providedContext && !genericContext ? providedContext : undefined);
55
- const effectiveWhatWorked = whatWorked || (!isDown && providedContext && !genericContext ? providedContext : undefined);
56
50
 
57
51
  // 1. Capture the feedback
58
52
  let feedbackResult;
59
53
  try {
60
54
  feedbackResult = captureFeedback({
61
55
  signal: isDown ? 'down' : 'up',
62
- context: providedContext,
63
- chatHistory,
64
- whatWentWrong: effectiveWhatWentWrong,
65
- whatWorked: effectiveWhatWorked,
56
+ context: context || (isDown ? 'Thumbs down from CLI' : 'Thumbs up from CLI'),
57
+ whatWentWrong: whatWentWrong || undefined,
58
+ whatWorked: whatWorked || undefined,
66
59
  });
67
60
  } catch (err) {
68
61
  feedbackResult = { accepted: false, reason: err.message };
@@ -104,10 +97,7 @@ function formatCliOutput(result) {
104
97
  process.stderr.write(`✅ Feedback captured (${feedbackId}${memoryId ? `, ${memoryId}` : ''})\n`);
105
98
  }
106
99
  } else {
107
- const clarification = result.feedbackResult && result.feedbackResult.needsClarification
108
- ? ` ${result.feedbackResult.prompt || result.feedbackResult.message || ''}`.trimEnd()
109
- : '';
110
- lines.push(`${R}Feedback not accepted: ${(result.feedbackResult && result.feedbackResult.reason) || 'unknown'}${clarification ? ` — ${clarification}` : ''}${RST}`);
100
+ lines.push(`${R}Feedback not accepted: ${(result.feedbackResult && result.feedbackResult.reason) || 'unknown'}${RST}`);
111
101
  }
112
102
 
113
103
  // Distilled lesson (if thumbs down)
@@ -642,6 +642,13 @@ const CLI_COMMANDS = [
642
642
  { name: 'info', type: 'boolean', description: 'Show Pro feature list' },
643
643
  ],
644
644
  },
645
+ {
646
+ name: 'diagnostic',
647
+ aliases: ['workflow-diagnostic', 'sprint-diagnostic'],
648
+ description: '$499 Workflow Hardening Diagnostic for one repeated AI-agent workflow failure',
649
+ group: 'ops',
650
+ flags: [],
651
+ },
645
652
 
646
653
  {
647
654
  name: 'workflow',
@@ -7,11 +7,11 @@
7
7
  * the full durable-execution runtime. Gives each external call (HTTP,
8
8
  * LanceDB, LLM) a uniform retry + idempotency wrapper:
9
9
  *
10
- * const result = await runStep('directSocial.publishPost', {
10
+ * const result = await runStep('zernio.publishPost', {
11
11
  * retries: 3,
12
12
  * idempotencyKey: idempotencyKey(content, platforms),
13
13
  * }, async ({ attempt }) => {
14
- * return directSocialFetch('POST', '/posts', body, { idempotencyKey: ... });
14
+ * return zernioFetch('POST', '/posts', body, { idempotencyKey: ... });
15
15
  * });
16
16
  *
17
17
  * Why a custom helper instead of Vercel Workflows / Temporal / Inngest?
@@ -98,7 +98,7 @@ function idempotencyKey(...parts) {
98
98
  * `fn` resolves to, or throws the last error after exhausting retries /
99
99
  * hitting a non-retryable verdict.
100
100
  *
101
- * @param {string} name Step name, used in logs. e.g. 'directSocial.publishPost'.
101
+ * @param {string} name Step name, used in logs. e.g. 'zernio.publishPost'.
102
102
  * @param {object|function} options { retries, backoffMs, classify, onRetry, onFail, logger }
103
103
  * (may be passed directly as the callback shorthand)
104
104
  * @param {function({attempt:number}):Promise} fn The actual work.
@@ -315,34 +315,6 @@ function getMemoryFirewallModule() {
315
315
  }
316
316
  }
317
317
 
318
- function launchSelfHarnessOptimizer() {
319
- if (process.env.THUMBGATE_DISABLE_SELF_HARNESS_OPTIMIZER === '1') {
320
- return { launched: false, reason: 'disabled' };
321
- }
322
-
323
- const explicitOptimizerPath = process.env.THUMBGATE_SELF_HARNESS_OPTIMIZER_PATH
324
- ? path.resolve(process.env.THUMBGATE_SELF_HARNESS_OPTIMIZER_PATH)
325
- : null;
326
- const localOptimizerPath = path.join(process.cwd(), 'scripts', 'self-harness-optimizer.js');
327
- const packageOptimizerPath = path.join(__dirname, 'self-harness-optimizer.js');
328
- const optimizerPath = explicitOptimizerPath
329
- || (fs.existsSync(localOptimizerPath) ? localOptimizerPath : null)
330
- || (fs.existsSync(packageOptimizerPath) ? packageOptimizerPath : null);
331
-
332
- if (!optimizerPath || !fs.existsSync(optimizerPath)) {
333
- return { launched: false, reason: 'missing' };
334
- }
335
-
336
- const { spawn } = require('child_process');
337
- const child = spawn(process.execPath, [optimizerPath], {
338
- detached: true,
339
- stdio: 'ignore',
340
- env: process.env,
341
- });
342
- child.unref();
343
- return { launched: true, path: optimizerPath };
344
- }
345
-
346
318
 
347
319
  function appendJSONL(filePath, record) {
348
320
  ensureDir(path.dirname(filePath));
@@ -1528,11 +1500,17 @@ function captureFeedback(params) {
1528
1500
  });
1529
1501
  } catch { /* activation telemetry is non-critical */ }
1530
1502
 
1531
- // Trigger Self-Harness Optimizer to propagate the new rules to prompt
1532
- // files & validate. Use spawn instead of fork: fork creates an IPC
1533
- // pipe that keeps CLI feedback capture alive after the durable write.
1503
+ // Trigger Self-Harness Optimizer to propagate the new rules to prompt files & validate
1534
1504
  try {
1535
- launchSelfHarnessOptimizer();
1505
+ const { fork } = require('child_process');
1506
+ const localOptimizerPath = path.join(process.cwd(), 'scripts', 'self-harness-optimizer.js');
1507
+ const packageOptimizerPath = path.join(__dirname, 'self-harness-optimizer.js');
1508
+
1509
+ if (fs.existsSync(localOptimizerPath)) {
1510
+ fork(localOptimizerPath, [], { stdio: 'ignore', detached: true }).unref();
1511
+ } else if (fs.existsSync(packageOptimizerPath)) {
1512
+ fork(packageOptimizerPath, [], { stdio: 'ignore', detached: true }).unref();
1513
+ }
1536
1514
  } catch (err) {
1537
1515
  console.error('Failed to trigger self-harness optimizer:', err);
1538
1516
  }