thumbgate 1.23.1 → 1.23.2

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 (40) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.well-known/llms.txt +26 -11
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +69 -34
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/mcp/server-stdio.js +1 -1
  8. package/adapters/opencode/opencode.json +1 -1
  9. package/bin/cli.js +39 -16
  10. package/bin/postinstall.js +11 -22
  11. package/config/gate-templates.json +72 -0
  12. package/config/github-about.json +1 -1
  13. package/config/post-deploy-marketing-pages.json +6 -1
  14. package/package.json +5 -5
  15. package/public/agent-manager.html +3 -3
  16. package/public/agents-cost-savings.html +3 -3
  17. package/public/ai-malpractice-prevention.html +278 -7
  18. package/public/blog.html +3 -3
  19. package/public/codex-enterprise.html +3 -3
  20. package/public/codex-plugin.html +4 -4
  21. package/public/compare.html +6 -6
  22. package/public/dashboard.html +211 -126
  23. package/public/guide.html +5 -5
  24. package/public/index.html +156 -47
  25. package/public/learn.html +24 -10
  26. package/public/lessons.html +2 -2
  27. package/public/numbers.html +6 -6
  28. package/public/pricing.html +6 -5
  29. package/public/pro.html +1 -0
  30. package/scripts/billing.js +17 -0
  31. package/scripts/commercial-offer.js +4 -1
  32. package/scripts/dashboard.js +53 -1
  33. package/scripts/gates-engine.js +3 -3
  34. package/scripts/plausible-server-events.js +2 -1
  35. package/scripts/rate-limiter.js +16 -12
  36. package/scripts/seo-gsd.js +167 -1
  37. package/scripts/telemetry-analytics.js +310 -0
  38. package/scripts/visitor-journey.js +172 -0
  39. package/src/api/server.js +65 -29
  40. package/adapters/chatgpt/openapi.yaml +0 -1705
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>ThumbGate Dashboard — Gate Stats, Agent Inventory, Remediations</title>
7
7
  <meta name="description" content="Live ThumbGate dashboard showing gate enforcement stats, agent surface inventory, actionable remediations, prevention impact, and system health for pre-action checks.">
8
- <link rel="canonical" href="https://thumbgate-production.up.railway.app/dashboard">
8
+ <link rel="canonical" href="https://thumbgate.ai/dashboard">
9
9
  <link rel="icon" type="image/png" href="/thumbgate-icon.png">
10
10
  <link rel="apple-touch-icon" href="/assets/brand/thumbgate-mark.svg">
11
11
  <meta name="robots" content="noindex">
@@ -18,7 +18,7 @@
18
18
  "name": "ThumbGate Dashboard",
19
19
  "applicationCategory": "DeveloperApplication",
20
20
  "operatingSystem": "Cross-platform",
21
- "url": "https://thumbgate-production.up.railway.app/dashboard",
21
+ "url": "https://thumbgate.ai/dashboard",
22
22
  "dateModified": "2026-04-20",
23
23
  "creator": {
24
24
  "@type": "Person",
@@ -539,6 +539,91 @@
539
539
  </div>
540
540
 
541
541
  <script>
542
+ // TG_TOKEN_SAVINGS — browser shim that mirrors scripts/token-savings.js
543
+ // so loadDemo()'s TG_TOKEN_SAVINGS.compute(blockedCalls, deflectedBots)
544
+ // call resolves. Pre-2026-05-26 the dashboard referenced this global
545
+ // without ever defining it, silently halting loadDemo() at line ~1452.
546
+ var TG_TOKEN_SAVINGS = (function () {
547
+ var DEFAULT_MODEL_MIX = { 'claude-sonnet-4-5': 0.80, 'claude-opus-4-6': 0.15, 'claude-haiku-4-5': 0.05 };
548
+ var DEFAULT_MODEL_PRICES = {
549
+ 'claude-sonnet-4-5': { input: 3.0, output: 15.0 },
550
+ 'claude-opus-4-6': { input: 5.0, output: 25.0 },
551
+ 'claude-haiku-4-5': { input: 1.0, output: 5.0 },
552
+ 'gpt-4o': { input: 2.50, output: 10.0 }
553
+ };
554
+ var DEFAULT_AVG_INPUT_TOKENS = 2000;
555
+ var DEFAULT_AVG_OUTPUT_TOKENS = 600;
556
+ function clamp(n, fb) { var v = Number(n); return (isFinite(v) && v >= 0) ? v : fb; }
557
+ function blended(mix, prices) {
558
+ var i = 0, o = 0, w = 0;
559
+ for (var m in mix) {
560
+ var ww = clamp(mix[m], 0); if (ww <= 0) continue;
561
+ var p = prices[m]; if (!p) continue;
562
+ i += clamp(p.input, 0) * ww; o += clamp(p.output, 0) * ww; w += ww;
563
+ }
564
+ return w > 0 ? { input: i / w, output: o / w } : { input: 0, output: 0 };
565
+ }
566
+ function fmtTokens(n) {
567
+ if (!isFinite(n) || n <= 0) return '0';
568
+ if (n >= 1e9) return (n / 1e9).toFixed(1) + 'B';
569
+ if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
570
+ if (n >= 1e3) return (n / 1e3).toFixed(0) + 'K';
571
+ return String(Math.round(n));
572
+ }
573
+ function fmtDollars(n) {
574
+ if (!isFinite(n)) return '$0.00';
575
+ if (n >= 1000) return '$' + Math.round(n).toLocaleString('en-US');
576
+ if (n >= 100) return '$' + n.toFixed(0);
577
+ if (n >= 10) return '$' + n.toFixed(1);
578
+ if (n >= 0.01) return '$' + n.toFixed(2);
579
+ if (n > 0) return '$' + n.toFixed(4);
580
+ return '$0.00';
581
+ }
582
+ function compute(blockedCalls, deflectedBots, opts) {
583
+ opts = opts || {};
584
+ var total = clamp(blockedCalls, 0) + clamp(deflectedBots, 0);
585
+ var avgIn = clamp(opts.avgInputTokensPerBlock, DEFAULT_AVG_INPUT_TOKENS);
586
+ var avgOut = clamp(opts.avgOutputTokensPerBlock, DEFAULT_AVG_OUTPUT_TOKENS);
587
+ var mix = (opts.modelMix && Object.keys(opts.modelMix).length) ? opts.modelMix : DEFAULT_MODEL_MIX;
588
+ var prices = opts.modelPrices || DEFAULT_MODEL_PRICES;
589
+ var b = blended(mix, prices);
590
+ var ti = total * avgIn, to = total * avgOut;
591
+ var dollars = (ti * b.input + to * b.output) / 1e6;
592
+ return {
593
+ blockedCalls: clamp(blockedCalls, 0),
594
+ deflectedBots: clamp(deflectedBots, 0),
595
+ tokensSavedInput: ti,
596
+ tokensSavedOutput: to,
597
+ tokensSavedTotal: ti + to,
598
+ dollarsSaved: dollars,
599
+ dollarsSavedDisplay: fmtDollars(dollars),
600
+ tokensSavedDisplay: fmtTokens(ti + to),
601
+ blendedPricePer1M: b,
602
+ modelMix: mix
603
+ };
604
+ }
605
+ return { compute: compute, formatDollars: fmtDollars, formatTokens: fmtTokens };
606
+ })();
607
+
608
+ // renderTopBlockedGates — defensive stub for the loadDemo() legacy direct
609
+ // call. The current dashboard layout surfaces top-blocked gates via the
610
+ // team panel (renderTeam → team.topBlockedGates) and has no dedicated
611
+ // #topBlockedGates element. Stub keeps loadDemo() from throwing while
612
+ // also rendering into a target element if one is ever added.
613
+ function renderTopBlockedGates(gates) {
614
+ if (!Array.isArray(gates)) return;
615
+ var target = document.getElementById('topBlockedGates')
616
+ || document.getElementById('topBlockedList');
617
+ if (!target || typeof escHtml !== 'function') return;
618
+ target.innerHTML = gates.map(function (g) {
619
+ return '<div class="blocked-row"><div><div class="blocked-name">'
620
+ + escHtml(g.name || '')
621
+ + '</div></div><div class="blocked-meta">'
622
+ + (g.blocked || 0)
623
+ + '</div></div>';
624
+ }).join('');
625
+ }
626
+
542
627
  let API_KEY = '';
543
628
  let currentSource = 'all';
544
629
  let isDemo = false;
@@ -1441,71 +1526,65 @@ function loadDemo() {
1441
1526
  // Demo badge in nav
1442
1527
  var logo = document.querySelector('.nav-logo');
1443
1528
  if (!document.getElementById('demoBadge')) {
1444
- logo.insertAdjacentHTML('afterend', '<span class="demo-badge" id="demoBadge">Demo Mode</span>');
1529
+ logo.insertAdjacentHTML('afterend', '<span class="demo-badge" id="demoBadge">Demo Mode — Jamie M., Partner · Litigation Intake</span>');
1445
1530
  }
1446
1531
  // Stats
1447
- document.getElementById('statTotal').textContent = '297';
1448
- document.getElementById('statPositive').textContent = '37';
1449
- document.getElementById('statNegative').textContent = '260';
1450
- document.getElementById('statGates').textContent = '21';
1532
+ document.getElementById('statTotal').textContent = '184';
1533
+ document.getElementById('statPositive').textContent = '91';
1534
+ document.getElementById('statNegative').textContent = '93';
1535
+ document.getElementById('statGates').textContent = '14';
1451
1536
  setSelectedCard('all');
1452
- // Demo: 260 negative-feedback events have produced ~180 actual gate blocks
1453
- // and ~85 bot deflections on /checkout/pro. Kept as illustrative defaults
1454
- // so the savings card never reads "0" in demo mode.
1455
- var demoSavings = TG_TOKEN_SAVINGS.compute(180, 85);
1537
+ var demoSavings = TG_TOKEN_SAVINGS.compute(93, 42);
1456
1538
  var tEl = document.getElementById('statTokensSaved');
1457
1539
  var dEl = document.getElementById('statDollarsSaved');
1458
1540
  if (tEl) tEl.textContent = demoSavings.tokensSavedDisplay;
1459
1541
  if (dEl) dEl.textContent = '≈ ' + demoSavings.dollarsSavedDisplay + ' saved';
1460
1542
  renderTopBlockedGates([
1461
- { name: 'gate:no-force-push', blocked: 47 },
1462
- { name: 'gate:no-drop-prod', blocked: 31 },
1463
- { name: 'gate:no-hallucinated-import', blocked: 28 },
1464
- { name: 'gate:no-secret-commit', blocked: 19 },
1465
- { name: 'gate:require-tests-before-done', blocked: 14 },
1466
- { name: 'gate:no-figma-claim-without-check', blocked: 9 },
1543
+ { name: 'gate:block-unauthorized-practice', blocked: 34 },
1544
+ { name: 'gate:require-conflict-clearance', blocked: 22 },
1545
+ { name: 'gate:block-privileged-egress', blocked: 17 },
1546
+ { name: 'gate:require-approved-disclaimer', blocked: 11 },
1547
+ { name: 'gate:restrict-model-endpoint', blocked: 6 },
1548
+ { name: 'gate:require-attorney-review', blocked: 3 },
1467
1549
  ]);
1468
1550
  renderReviewDelta({
1469
1551
  hasBaseline: true,
1470
- reviewedAt: '2025-06-18T09:00:00Z',
1471
- previousHead: '9f1a2c3d',
1472
- currentHead: '5c7e9b1a',
1473
- feedbackAdded: 6,
1474
- negativeAdded: 4,
1475
- lessonsAdded: 2,
1476
- blocksAdded: 5,
1477
- warnsAdded: 1,
1478
- headline: 'Since your last review: 6 feedback events · 4 negative · 2 lessons · 5 gate blocks.',
1552
+ reviewedAt: '2026-05-22T09:00:00Z',
1553
+ previousHead: 'a4c1e7f2',
1554
+ currentHead: 'b8d3f9a1',
1555
+ feedbackAdded: 8,
1556
+ negativeAdded: 5,
1557
+ lessonsAdded: 3,
1558
+ blocksAdded: 7,
1559
+ warnsAdded: 2,
1560
+ headline: 'Since your last review: 8 intake events · 5 blocked · 3 lessons · 7 gate enforcements.',
1479
1561
  latestFeedback: {
1480
- title: 'Claimed fix worked without running tests',
1481
- timestamp: '2025-06-22T10:20:00Z'
1562
+ title: 'Intake agent provided jurisdictional recommendation without attorney review',
1563
+ timestamp: '2026-05-25T14:20:00Z'
1482
1564
  },
1483
1565
  latestLesson: {
1484
- title: 'MISTAKE: Claimed Figma compliance without visual verification',
1485
- timestamp: '2025-06-21T08:10:00Z'
1566
+ title: 'BLOCKED: Advice-shaped response detected routed to attorney review queue',
1567
+ timestamp: '2026-05-24T11:10:00Z'
1486
1568
  }
1487
1569
  });
1488
1570
  document.getElementById('reviewCheckpointBtn').disabled = true;
1489
1571
  document.getElementById('reviewCheckpointBtn').textContent = 'Connect to save your own review checkpoint';
1490
- // Sample memories — realistic scenarios from real agent-driven development
1572
+ // Sample memories — legal AI intake scenarios for law firm pilot demo
1491
1573
  var demoResults = [
1492
- { signal: 'down', title: 'Claimed fix worked without running tests', context: 'Agent announced "fixed and pushed" but never ran the test suite. CI failed on 3 tests. Check now requires test evidence before any completion claim.', tags: ['anti-lying', 'verification-gap', 'ci', 'trust-breach'], timestamp: '2025-06-22T10:20:00Z' },
1493
- { signal: 'down', title: 'Force-pushed to main and lost teammate commits', context: 'Used git push --force on main to fix a rebase. Overwrote 3 commits from another team member. Had to recover from reflog. Check now blocks all --force pushes to protected branches.', tags: ['git', 'destructive', 'auto-blocked'], timestamp: '2025-06-20T09:15:00Z' },
1494
- { signal: 'up', title: 'Pre-action check caught .env commit', context: 'Check blocked a git add that included .env with production API keys. Saved from leaking secrets to a public repo.', tags: ['security', 'prevention', 'secrets'], timestamp: '2025-06-19T11:30:00Z' },
1495
- { signal: 'down', title: 'PR scope creep 72 changed files for a 2-file fix', context: 'Agent included unrelated formatting changes, config files, and lock file updates in a PR that should have touched 2 files. User had to manually separate the changes.', tags: ['pr-scope', 'scope-creep', 'user-frustration', 'git-workflow'], timestamp: '2025-06-18T14:30:00Z' },
1496
- { signal: 'up', title: 'Full PR review cycle with on-device verification', context: 'Fixed 3 review findings, verified on physical Android device with fresh debug build, confirmed Delete Account flow end-to-end before marking PR ready.', tags: ['pr-review', 'on-device-verification', 'evidence-based', 'metric:ROI'], timestamp: '2025-06-17T16:45:00Z' },
1497
- { signal: 'down', title: 'Claimed Figma compliance without visual verification', context: 'Said UI matched Figma designs without actually checking. User pointed out wrong colors, missing spacing, and incorrect font weights. Should have used screenshot comparison.', tags: ['anti-lying', 'figma', 'visual-verification', 'trust-breach'], timestamp: '2025-06-16T08:05:00Z' },
1498
- { signal: 'down', title: 'Deployed without running tests', context: 'Pushed a refactor to main without running the test suite. Two integration tests failed in CI, blocking the release for 3 hours.', tags: ['ci', 'testing', 'deployment'], timestamp: '2025-06-15T09:15:00Z' },
1499
- { signal: 'up', title: 'Atomic commits improved bisect speed', context: 'Splitting a large feature into 8 atomic commits made git bisect find the regression in under 2 minutes instead of manual debugging.', tags: ['git', 'debugging', 'evidence-based'], timestamp: '2025-06-14T16:45:00Z' },
1500
- { signal: 'down', title: 'Deleted production database table', context: 'AI agent ran DROP TABLE on a staging-named table that was actually aliased to production. Added a gate requiring explicit confirmation for any destructive SQL.', tags: ['database', 'destructive', 'critical', 'auto-blocked'], timestamp: '2025-06-13T08:05:00Z' },
1501
- { signal: 'up', title: 'Read file before editing prevented breaking change', context: 'Always read the target file before making changes. Agent caught that the function signature had changed upstream and adapted the edit accordingly.', tags: ['code-quality', 'prevention', 'evidence-based'], timestamp: '2025-06-12T14:30:00Z' },
1502
- { signal: 'down', title: 'Pushed to wrong branch created new PR instead of updating existing', context: 'Agent pushed changes to a new branch instead of the open PR branch. Had to cherry-pick commits and force-close the duplicate PR.', tags: ['git-workflow', 'branch-strategy', 'delegation', 'user-frustration'], timestamp: '2025-06-11T10:20:00Z' },
1503
- { signal: 'up', title: 'ADO tickets split correctly with parallel execution', context: 'Split frontend/backend tasks, corrected area path and iteration, created investigation subtask, and updated PR description all done efficiently in parallel.', tags: ['ado-workflow', 'parallel-execution', 'triage', 'metric:ROI'], timestamp: '2025-06-10T13:00:00Z' },
1504
- { signal: 'down', title: 'Delegated debugging to QA instead of investigating', context: 'Told user to ask QA engineer for error logs instead of investigating the issue directly. Wasted 30 minutes of back-and-forth when the fix was a one-line config change.', tags: ['delegation', 'user-frustration', 'debugging', 'speed'], timestamp: '2025-06-09T15:30:00Z' },
1505
- { signal: 'down', title: 'iOS build failed forgot pod install after adding dependency', context: 'Added a new npm package but did not run pod install or rebuild the native project. Build failed on iOS physical device. Metro bundler showed stale cache.', tags: ['ios-build', 'metro', 'speed', 'mobile'], timestamp: '2025-06-08T09:45:00Z' },
1506
- { signal: 'up', title: 'Prevention rule stopped repeat mistake', context: 'Gate auto-generated from 3 prior "forgot pod install" failures now blocks any npm install that touches native modules without a follow-up pod install step.', tags: ['prevention', 'ios-build', 'auto-generated', 'metric:ROI'], timestamp: '2025-06-07T11:00:00Z' },
1507
- { signal: 'down', title: 'Claimed performance fix without benchmarking', context: 'Said the optimization reduced render time by 40% but never ran a before/after benchmark. Actual measurement showed no improvement. Gate now requires metrics evidence.', tags: ['anti-lying', 'performance', 'verification-gap', 'measure-first'], timestamp: '2025-06-06T14:20:00Z' },
1508
- { signal: 'up', title: 'DPO export caught systematic failure pattern', context: 'Exported 47 preference pairs revealing that 80% of negative feedback involved claiming work was done without verification. Led to a new mandatory evidence gate.', tags: ['dpo-export', 'evidence-based', 'prevention', 'metric:ROI'], timestamp: '2025-06-05T10:15:00Z' }
1574
+ { signal: 'down', title: 'Intake agent provided outcome prediction to prospect', context: 'Prospect asked about wrongful termination in Florida. Agent said "you likely have a strong case." ThumbGate blocked the advice-shaped response before delivery under ABA Rule 5.5.', tags: ['upl-risk', 'aba-rule-5.5', 'auto-blocked', 'critical'], timestamp: '2026-05-25T14:20:00Z' },
1575
+ { signal: 'up', title: 'Conflict pre-check stopped intake before facts collected', context: 'Adverse party matched the synthetic conflict list. ThumbGate halted intake before privileged facts were disclosed, preventing a Rule 1.7/1.9 conflict issue.', tags: ['conflict-check', 'aba-rule-1.7', 'aba-rule-1.9', 'prevention'], timestamp: '2026-05-25T11:45:00Z' },
1576
+ { signal: 'down', title: 'Agent attempted to send case summary to external CRM', context: 'Agent tried to push matter IDs and consultation notes to an external CRM. ThumbGate blocked the outbound call for privileged-content markers.', tags: ['privilege-egress', 'aba-rule-1.6', 'auto-blocked', 'critical'], timestamp: '2026-05-24T16:30:00Z' },
1577
+ { signal: 'up', title: 'Safe handoff collected neutral facts only', context: 'After a UPL block, the agent pivoted to neutral routing facts: name, contact, practice area, and callback time. No legal conclusion was delivered.', tags: ['safe-handoff', 'compliant-response', 'aba-rule-5.5', 'metric:ROI'], timestamp: '2026-05-24T11:10:00Z' },
1578
+ { signal: 'down', title: 'Agent made jurisdictional recommendation without review', context: 'Prospect asked where to file. Agent gave a venue recommendation. ThumbGate blocked it because jurisdiction guidance requires attorney supervision.', tags: ['upl-risk', 'jurisdiction', 'aba-rule-5.5', 'auto-blocked'], timestamp: '2026-05-23T15:20:00Z' },
1579
+ { signal: 'up', title: 'Audit export preserved full decision chain', context: 'Export showed matched rule, proposed reply, deny decision, attorney-review route, and UTC timestamp. Compliance can replay the decision.', tags: ['audit-trail', 'compliance', 'aba-opinion-512', 'metric:ROI'], timestamp: '2026-05-23T09:00:00Z' },
1580
+ { signal: 'down', title: 'Response missing firm-approved disclaimer', context: 'Agent drafted a prospect reply without the required non-engagement disclaimer. ThumbGate blocked delivery until approved text was added.', tags: ['disclaimer-missing', 'non-engagement', 'aba-rule-5.5', 'auto-blocked'], timestamp: '2026-05-22T14:45:00Z' },
1581
+ { signal: 'up', title: 'Model endpoint restricted to approved provider', context: 'Agent tried a consumer model endpoint. ThumbGate enforced the allowlist and rerouted to the firm-approved Azure OpenAI endpoint.', tags: ['model-governance', 'data-handling', 'aba-opinion-512', 'prevention'], timestamp: '2026-05-22T10:30:00Z' },
1582
+ { signal: 'down', title: 'Agent disclosed opposing counsel information to prospect', context: 'Agent referenced opposing-counsel information from another matter. ThumbGate blocked the cross-matter leak under Rule 1.6.', tags: ['privilege-egress', 'cross-matter', 'aba-rule-1.6', 'critical'], timestamp: '2026-05-21T16:15:00Z' },
1583
+ { signal: 'up', title: 'Conflict check cleared intake proceeded safely', context: 'Adverse party cleared the synthetic fixture. ThumbGate allowed intake and logged timestamp plus rule version.', tags: ['conflict-check', 'cleared', 'aba-rule-1.7', 'compliant-response'], timestamp: '2026-05-21T11:00:00Z' },
1584
+ { signal: 'down', title: 'Intake agent attempted to schedule consultation directly', context: 'Agent tried to book a consultation without attorney review. ThumbGate blocked the routing action pending supervisor approval.', tags: ['routing-governance', 'attorney-review', 'auto-blocked'], timestamp: '2026-05-20T13:30:00Z' },
1585
+ { signal: 'up', title: 'Egress rerouted to in-tenant system successfully', context: 'Agent needed to store intake notes. ThumbGate rerouted the write to the firm tenant, keeping privileged content inside the boundary.', tags: ['privilege-egress', 'in-tenant-reroute', 'aba-rule-1.6', 'metric:ROI'], timestamp: '2026-05-20T09:45:00Z' },
1586
+ { signal: 'up', title: 'DPO export identified systematic UPL pattern', context: 'Exported 34 blocked intake pairs. 76% were outcome predictions after "do I have a case?" prompts, driving revised intake scripts.', tags: ['dpo-export', 'upl-pattern', 'prevention', 'metric:ROI'], timestamp: '2026-05-19T14:00:00Z' },
1587
+ { signal: 'down', title: 'Agent used non-approved model for privilege-sensitive query', context: 'Privileged intake query was routed to an endpoint without enterprise DPA. ThumbGate blocked and logged it for security review.', tags: ['model-governance', 'privilege-risk', 'aba-opinion-512', 'auto-blocked'], timestamp: '2026-05-18T11:20:00Z' }
1509
1588
  ];
1510
1589
  isDemo = true;
1511
1590
  demoData = demoResults;
@@ -1519,14 +1598,14 @@ function loadDemo() {
1519
1598
  'style="flex:none;background:#b85c2d;color:#fff;padding:8px 18px;border-radius:8px;text-decoration:none;font-weight:700;font-size:13px;white-space:nowrap;">Go Pro — $19/mo</a>' +
1520
1599
  '</div>';
1521
1600
  document.getElementById('searchResults').innerHTML = upgradeBanner + teaserHtml;
1522
- // Sample gates
1601
+ // Sample gates — legal AI intake enforcement
1523
1602
  var demoGates = [
1524
- { name: 'no-force-push', pattern: 'Block git push --force to main or master branches', action: 'block' },
1525
- { name: 'read-before-edit', pattern: 'Must read file contents before any edit_file call', action: 'block' },
1526
- { name: 'test-before-deploy', pattern: 'Run npm test before any deployment or push to main', action: 'block' },
1527
- { name: 'no-secrets-in-commits', pattern: 'Block git add on .env, credentials, or API key files', action: 'block' },
1528
- { name: 'evidence-before-claim', pattern: 'Run verification commands before claiming task completion', action: 'block' },
1529
- { name: 'atomic-commits', pattern: 'Each commit should contain a single logical change', action: 'warn' }
1603
+ { name: 'block-unauthorized-practice', pattern: 'Block advice-shaped intake replies from non-attorney agents (ABA Rule 5.5)', action: 'block' },
1604
+ { name: 'require-conflict-clearance', pattern: 'Require adverse-party clearance before facts or scheduling (Rules 1.7/1.9/1.10)', action: 'block' },
1605
+ { name: 'block-privileged-egress', pattern: 'Block outbound calls with matter IDs or attorney-client markers (Rule 1.6)', action: 'block' },
1606
+ { name: 'require-approved-disclaimer', pattern: 'Require firm-approved non-engagement disclaimer before delivery', action: 'block' },
1607
+ { name: 'restrict-model-endpoint', pattern: 'Allow only enterprise-approved model endpoints (ABA Opinion 512)', action: 'block' },
1608
+ { name: 'require-attorney-review', pattern: 'Route case assignment and scheduling through attorney review', action: 'warn' }
1530
1609
  ];
1531
1610
  document.getElementById('gatesList').innerHTML = demoGates.map(function(g) {
1532
1611
  return '<div class="gate-card"><div><div class="gate-name">' + escHtml(g.name) + '</div>' +
@@ -1534,141 +1613,144 @@ function loadDemo() {
1534
1613
  '<span class="gate-action ' + g.action + '">' + g.action.toUpperCase() + '</span></div>';
1535
1614
  }).join('');
1536
1615
  renderTeam({
1537
- totalAgents: 8,
1538
- activeAgents: 5,
1616
+ totalAgents: 4,
1617
+ activeAgents: 3,
1539
1618
  windowHours: 24,
1540
- orgAdherenceRate: 92.4,
1619
+ orgAdherenceRate: 96.1,
1541
1620
  riskAgents: [
1542
- { id: 'claude-reviewer', project: 'checkout-flow', branch: 'fix/stripe-timeout', adherenceRate: 67.5 },
1543
- { id: 'codex-second-pass', project: 'dashboard', branch: 'feat/team-rollout', adherenceRate: 74.2 }
1621
+ { id: 'intake-chatbot-prod', project: 'client-intake', branch: 'litigation-practice', adherenceRate: 88.2 },
1622
+ { id: 'intake-chatbot-staging', project: 'client-intake', branch: 'ip-practice-pilot', adherenceRate: 91.7 }
1544
1623
  ],
1545
1624
  topBlockedGates: [
1546
- { gateId: 'evidence-before-done', blocked: 12, warned: 1 },
1547
- { gateId: 'never-force-push-main', blocked: 7, warned: 0 }
1625
+ { gateId: 'block-unauthorized-practice', blocked: 34, warned: 0 },
1626
+ { gateId: 'require-conflict-clearance', blocked: 22, warned: 3 }
1548
1627
  ]
1549
1628
  }, {
1550
1629
  northStar: {
1551
- weeklyTeamsRunningProofBackedWorkflows: 3,
1630
+ weeklyTeamsRunningProofBackedWorkflows: 2,
1552
1631
  paidTeamRuns: 1
1553
1632
  }
1554
1633
  });
1555
1634
  renderPredictive({
1556
1635
  upgradePropensity: {
1557
- pro: { band: 'high', score: 0.71 },
1558
- team: { band: 'medium', score: 0.54 }
1636
+ pro: { band: 'high', score: 0.84 },
1637
+ team: { band: 'high', score: 0.72 }
1559
1638
  },
1560
1639
  revenueForecast: {
1561
- predictedBookedRevenueCents: 12800,
1562
- incrementalOpportunityCents: 4900
1640
+ predictedBookedRevenueCents: 8400000,
1641
+ incrementalOpportunityCents: 3200000
1563
1642
  },
1564
1643
  anomalySummary: {
1565
1644
  count: 2,
1566
1645
  severity: 'warning'
1567
1646
  },
1568
1647
  topCreators: [
1569
- { key: 'reach_vb', opportunityRevenueCents: 3100 }
1648
+ { key: 'innovation_team', opportunityRevenueCents: 4800000 }
1570
1649
  ],
1571
1650
  topSources: [
1572
- { key: 'producthunt', opportunityRevenueCents: 1800 }
1651
+ { key: 'direct_outreach', opportunityRevenueCents: 2400000 }
1573
1652
  ],
1574
1653
  anomalies: [
1575
- { type: 'pricing_resistance', message: 'Price sensitivity dominates current loss reasons.', severity: 'warning' },
1576
- { type: 'creator_underperformance', message: 'Creator reach_vb is generating intent without revenue conversion.', severity: 'warning' }
1654
+ { type: 'upl_risk_concentration', message: 'UPL blocks concentrate in litigation intake — add practice-area rules.', severity: 'warning' },
1655
+ { type: 'egress_pattern', message: 'Privilege egress attempts cluster after hours add reviewer coverage.', severity: 'warning' }
1577
1656
  ]
1578
1657
  });
1579
1658
  renderAgentInventory({
1580
- profile: 'default',
1581
- tier: 'builder',
1582
- configuredServerCount: 3,
1583
- observedToolCount: 4,
1659
+ profile: 'legal-intake',
1660
+ tier: 'enterprise',
1661
+ configuredServerCount: 2,
1662
+ observedToolCount: 5,
1584
1663
  observedTools: [
1585
- { toolName: 'Bash', evaluations: 120, intercepted: 19, allow: 101, deny: 12, warn: 7 },
1586
- { toolName: 'Edit', evaluations: 64, intercepted: 6, allow: 58, deny: 2, warn: 4 },
1587
- { toolName: 'Read', evaluations: 42, intercepted: 0, allow: 42, deny: 0, warn: 0 }
1664
+ { toolName: 'intake_reply', evaluations: 89, intercepted: 34, allow: 55, deny: 34, warn: 0 },
1665
+ { toolName: 'conflict_lookup', evaluations: 67, intercepted: 22, allow: 45, deny: 19, warn: 3 },
1666
+ { toolName: 'send_to_crm', evaluations: 41, intercepted: 17, allow: 24, deny: 14, warn: 3 },
1667
+ { toolName: 'schedule_consultation', evaluations: 28, intercepted: 3, allow: 25, deny: 0, warn: 3 },
1668
+ { toolName: 'export_audit', evaluations: 19, intercepted: 0, allow: 19, deny: 0, warn: 0 }
1588
1669
  ],
1589
1670
  policySources: [
1590
- { source: 'gates-engine', count: 23 },
1591
- { source: 'secret-guard', count: 6 },
1592
- { source: 'workflow-sentinel', count: 4 }
1671
+ { source: 'legal-intake-rules', count: 14 },
1672
+ { source: 'privilege-guard', count: 6 },
1673
+ { source: 'conflict-sentinel', count: 4 }
1593
1674
  ]
1594
1675
  });
1595
1676
  renderActionableRemediations([
1596
1677
  {
1597
- title: 'tighten verification before response · recent-signals',
1598
- rationale: 'Recent approval rate has dropped below the lifetime baseline, so fast completion claims need tighter proof.',
1599
- action: 'tighten-verification-before-response',
1678
+ title: 'add practice-area-specific UPL rules · litigation-intake',
1679
+ rationale: 'Litigation intake generates 76% of UPL blocks. Add targeted rules to reduce false positives.',
1680
+ action: 'add-practice-area-rules',
1600
1681
  },
1601
1682
  {
1602
- title: 'audit domain failures · git-workflow',
1603
- rationale: 'Git workflow events are overrepresented in high-risk outcomes and deserve a stronger pre-action gate.',
1604
- action: 'audit-domain-failures',
1683
+ title: 'extend conflict check to affiliated entities · conflict-detection',
1684
+ rationale: 'Conflict fixture covers named parties only. Add affiliates and parents.',
1685
+ action: 'extend-conflict-coverage',
1605
1686
  },
1606
1687
  {
1607
- title: 'review and update skill · github',
1608
- rationale: 'GitHub-related failures have crossed the negative-rate threshold, so the skill needs a correction pass.',
1609
- action: 'review-and-update-skill',
1688
+ title: 'schedule attorney reviewer for off-hours intake · privilege-egress',
1689
+ rationale: 'Egress attempts cluster after 6pm. Add a backup reviewer queue.',
1690
+ action: 'schedule-off-hours-reviewer',
1610
1691
  }
1611
1692
  ]);
1612
1693
  renderSettingsStatus({
1613
1694
  activeLayers: [
1614
1695
  { scope: 'defaults', exists: true, leafCount: 7, sourcePath: null },
1615
- { scope: 'user', exists: true, leafCount: 2, sourcePath: '~/.thumbgate/settings.json' },
1616
- { scope: 'project', exists: true, leafCount: 2, sourcePath: '.thumbgate/settings.json' },
1617
- { scope: 'local', exists: false, leafCount: 0, sourcePath: '.thumbgate/settings.local.json' },
1618
- { scope: 'managed', exists: true, leafCount: 3, sourcePath: 'config/thumbgate-settings.managed.json' }
1696
+ { scope: 'firm', exists: true, leafCount: 6, sourcePath: 'config/firm-intake-policy.json' },
1697
+ { scope: 'practice-area', exists: true, leafCount: 4, sourcePath: 'config/litigation-rules.json' },
1698
+ { scope: 'pilot', exists: true, leafCount: 3, sourcePath: '.thumbgate/pilot-settings.json' },
1699
+ { scope: 'managed', exists: true, leafCount: 5, sourcePath: 'config/thumbgate-settings.managed.json' }
1619
1700
  ],
1620
1701
  resolvedSettings: {
1621
- mcp: { defaultProfile: 'essential', readonlySessionProfile: 'readonly' },
1702
+ intake: { defaultProfile: 'legal-intake', enforcementMode: 'strict' },
1622
1703
  harnesses: { enabled: true, allowRuntimeExecution: true }
1623
1704
  },
1624
1705
  routingPreview: {
1625
- dashboardTool: { profile: 'readonly', reason: 'dashboard is available in the most restrictive profile that contains it' },
1626
- defaultSession: { profile: 'essential', reason: 'default auto-routing essential profile from settings hierarchy' },
1627
- reviewSession: { profile: 'readonly', reason: 'read-only session detected — routing to readonly profile' }
1706
+ intakeAgent: { profile: 'legal-intake', reason: 'all intake agents use strict legal gates' },
1707
+ auditExport: { profile: 'readonly', reason: 'audit export has no firm-system write access' },
1708
+ reviewQueue: { profile: 'attorney-review', reason: 'blocked responses route to attorney review' }
1628
1709
  },
1629
1710
  origins: [
1630
- { path: 'mcp.defaultProfile', value: 'essential', scope: 'project', sourcePath: '.thumbgate/settings.json' },
1631
- { path: 'mcp.readonlySessionProfile', value: 'readonly', scope: 'project', sourcePath: '.thumbgate/settings.json' },
1632
- { path: 'harnesses.allowRuntimeExecution', value: true, scope: 'project', sourcePath: '.thumbgate/settings.json' },
1633
- { path: 'dashboard.showPolicyOrigins', value: true, scope: 'managed', sourcePath: 'config/thumbgate-settings.managed.json' }
1711
+ { path: 'intake.defaultProfile', value: 'legal-intake', scope: 'firm', sourcePath: 'config/firm-intake-policy.json' },
1712
+ { path: 'intake.enforcementMode', value: 'strict', scope: 'firm', sourcePath: 'config/firm-intake-policy.json' },
1713
+ { path: 'gates.conflictFixturePath', value: 'fixtures/adverse-parties.json', scope: 'pilot', sourcePath: '.thumbgate/pilot-settings.json' },
1714
+ { path: 'gates.privilegeMarkers', value: ['attorney-client', 'work-product', 'matter-id'], scope: 'firm', sourcePath: 'config/firm-intake-policy.json' }
1634
1715
  ]
1635
1716
  });
1636
1717
  renderTemplates({
1637
1718
  total: 6,
1638
1719
  categories: {
1639
- 'Git Safety': 1,
1640
- 'Verification': 1,
1641
- 'Agent Honesty': 1,
1642
- 'Database Safety': 1,
1643
- 'Secrets Hygiene': 1,
1644
- 'Positive Reinforcement': 1
1720
+ 'UPL Prevention': 2,
1721
+ 'Conflict Detection': 1,
1722
+ 'Privilege Protection': 1,
1723
+ 'Model Governance': 1,
1724
+ 'Intake Routing': 1
1645
1725
  },
1646
1726
  templates: [
1647
- { name: 'Never force-push to main', category: 'Git Safety', signal: '👎', defaultAction: 'block', severity: 'critical', problem: 'Stops destructive history rewrites on protected branches before they land.', roi: 'Protects every shared repo from the fastest irreversible mistake.', rollout: 'Enable on every team repo on day one.', pattern: 'git\\s+push\\s+(--force|-f)' },
1648
- { name: 'Never skip tests before commit', category: 'Verification', signal: '👎', defaultAction: 'block', severity: 'high', problem: 'Requires proof before code leaves the laptop.', roi: 'Cuts review churn and CI rollback time across the team.', rollout: 'Pair with repository-specific test commands.', pattern: 'git\\s+commit' },
1649
- { name: 'Require evidence before saying done', category: 'Agent Honesty', signal: '👎', defaultAction: 'block', severity: 'high', problem: 'Prevents unsupported completion claims.', roi: 'Raises trust in autonomous runs.', rollout: 'Use anywhere proof matters more than speed.', pattern: 'completion_claim_without_verification' },
1650
- { name: 'Protect production SQL', category: 'Database Safety', signal: '👎', defaultAction: 'block', severity: 'critical', problem: 'Blocks destructive SQL operations on production-like targets.', roi: 'One saved incident pays for the rollout.', rollout: 'Turn on for any team touching live data.', pattern: '(drop|truncate|delete)\\s+.*production' }
1727
+ { name: 'Block unauthorized practice of law', category: 'UPL Prevention', signal: '👎', defaultAction: 'block', severity: 'critical', problem: 'Stops advice-shaped intake replies under ABA Rule 5.5.', roi: 'Prevents malpractice exposure before delivery.', rollout: 'Enable on every AI intake channel.', pattern: '(outcome prediction|jurisdictional recommendation|you (should|could|might) (file|sue|claim))' },
1728
+ { name: 'Require approved disclaimer', category: 'UPL Prevention', signal: '👎', defaultAction: 'block', severity: 'high', problem: 'Requires firm-approved non-engagement language.', roi: 'Reduces inadvertent client-relationship risk.', rollout: 'Load approved disclaimer during pilot.', pattern: '(intake_response|client_reply).*(missing|no).*(disclaimer)' },
1729
+ { name: 'Require conflict check before intake', category: 'Conflict Detection', signal: '👎', defaultAction: 'block', severity: 'critical', problem: 'Requires adverse-party clearance before sensitive facts.', roi: 'Catches conflicts before privileged facts cross walls.', rollout: 'Use conflicts API or synthetic pilot fixture.', pattern: '(collect_case_facts|intake_continue).*(missing|no).*(conflict|clearance)' },
1730
+ { name: 'Block privileged content egress', category: 'Privilege Protection', signal: '👎', defaultAction: 'block', severity: 'critical', problem: 'Blocks outbound actions with privilege markers.', roi: 'Prevents single-action privilege waiver.', rollout: 'Define markers; start with hard block.', pattern: '(send_email|api_call|external_request).*(privileged|attorney[_-]client|matter[_-]id)' },
1731
+ { name: 'Restrict model endpoint to approved list', category: 'Model Governance', signal: '👎', defaultAction: 'block', severity: 'high', problem: 'Blocks unapproved model endpoints.', roi: 'Keeps data inside approved vendor boundaries.', rollout: 'Allowlist endpoints during pilot.', pattern: '(model_call|llm_request).*(unapproved|unknown|consumer)' },
1732
+ { name: 'Require attorney review before routing', category: 'Intake Routing', signal: '👎', defaultAction: 'warn', severity: 'high', problem: 'Requires review before routing or scheduling.', roi: 'Prevents bad prospect routing.', rollout: 'Start strict; relax after pilot evidence.', pattern: '(route_to_attorney|schedule_consultation).*(no|missing).*(review|approval)' }
1651
1733
  ]
1652
1734
  });
1653
1735
  renderGeneratedView(buildDemoGeneratedViewSpec(currentGeneratedView));
1654
1736
  renderInsights({
1655
- gateStats: { blocked: 180 },
1737
+ gateStats: { blocked: 93 },
1656
1738
  tokenSavings: {
1657
1739
  dollarsSaved: 0,
1658
1740
  dollarsSavedDisplay: '$0.00',
1659
1741
  tokensSavedDisplay: '0',
1660
1742
  blockedCalls: 0,
1661
- modelMix: { 'sonnet-4.5': 0.8, 'opus-4.6': 0.15, 'haiku-4.5': 0.05 },
1662
- blendedPricePer1M: { input: 6.2, output: 24.1 }
1743
+ modelMix: { 'gpt-4o': 0.5, 'claude-sonnet-4.5': 0.35, 'azure-openai': 0.15 },
1744
+ blendedPricePer1M: { input: 5.0, output: 15.0 }
1663
1745
  },
1664
1746
  lessonPipeline: {
1665
1747
  stages: [
1666
- { id: 'feedback', label: 'Feedback Signals', count: 297, detail: '37 up / 260 down' },
1667
- { id: 'lessons', label: 'Lessons Distilled', count: 94, detail: '72 mistakes / 22 good patterns' },
1668
- { id: 'gates', label: 'Gates Promoted', count: 21, detail: '22% of lessons become gates' },
1669
- { id: 'blocked', label: 'Actions Blocked', count: 180, detail: 'Repeat mistakes prevented' }
1748
+ { id: 'feedback', label: 'Intake Events', count: 184, detail: '91 safe / 93 blocked' },
1749
+ { id: 'lessons', label: 'Rules Refined', count: 42, detail: '28 UPL patterns / 14 egress patterns' },
1750
+ { id: 'gates', label: 'Gates Active', count: 14, detail: '33% of rules become enforcement gates' },
1751
+ { id: 'blocked', label: 'Risks Prevented', count: 93, detail: 'UPL, conflict, and privilege violations stopped' }
1670
1752
  ],
1671
- rates: { feedbackToLesson: 32, lessonToGate: 22 }
1753
+ rates: { feedbackToLesson: 23, lessonToGate: 33 }
1672
1754
  },
1673
1755
  feedbackTimeSeries: {
1674
1756
  days: Array.from({ length: 30 }, function(_, index) {
@@ -1692,14 +1774,14 @@ function loadDemo() {
1692
1774
  },
1693
1775
  actionableRemediations: [
1694
1776
  {
1695
- title: 'tighten verification before response · recent-signals',
1696
- rationale: 'Recent approval rate has dropped below the lifetime baseline, so fast completion claims need tighter proof.',
1697
- action: 'tighten-verification-before-response'
1777
+ title: 'add litigation-specific UPL rules',
1778
+ rationale: '76% of UPL blocks come from litigation intake. Add targeted rules.',
1779
+ action: 'add-practice-area-rules'
1698
1780
  },
1699
1781
  {
1700
- title: 'audit domain failures · git-workflow',
1701
- rationale: 'Git workflow events are overrepresented in high-risk outcomes and deserve a stronger pre-action gate.',
1702
- action: 'audit-domain-failures'
1782
+ title: 'extend conflict checks to affiliates',
1783
+ rationale: 'Named-party fixture misses affiliates and parents.',
1784
+ action: 'extend-conflict-coverage'
1703
1785
  }
1704
1786
  ]
1705
1787
  });
@@ -1861,6 +1943,7 @@ function chartDefaults() {
1861
1943
  function renderFeedbackTrendChart(ts) {
1862
1944
  var canvas = document.getElementById('feedbackTrendChart');
1863
1945
  if (!canvas) return;
1946
+ if (typeof Chart === 'undefined') return;
1864
1947
  var days = ts.days || [];
1865
1948
  if (feedbackChart) feedbackChart.destroy();
1866
1949
 
@@ -1911,6 +1994,7 @@ function renderFeedbackTrendChart(ts) {
1911
1994
  function renderLessonTrendChart(ts) {
1912
1995
  var canvas = document.getElementById('lessonTrendChart');
1913
1996
  if (!canvas) return;
1997
+ if (typeof Chart === 'undefined') return;
1914
1998
  var days = ts.days || [];
1915
1999
  if (lessonChart) lessonChart.destroy();
1916
2000
 
@@ -1969,6 +2053,7 @@ function renderLessonTrendChart(ts) {
1969
2053
  function renderGateAuditChartFromData(gateAudit) {
1970
2054
  var canvas = document.getElementById('gateAuditChart');
1971
2055
  if (!canvas) return;
2056
+ if (typeof Chart === 'undefined') return;
1972
2057
  var days = gateAudit.days || [];
1973
2058
  if (gateAuditChart) gateAuditChart.destroy();
1974
2059
 
package/public/guide.html CHANGED
@@ -11,8 +11,8 @@
11
11
  <meta property="og:title" content="How to Stop AI Coding Agents From Repeating Mistakes">
12
12
  <meta property="og:description" content="Pre-action checks that physically block AI agents from repeating known mistakes. The complete guide.">
13
13
  <meta property="og:type" content="article">
14
- <meta property="og:url" content="https://thumbgate-production.up.railway.app/guide">
15
- <link rel="canonical" href="https://thumbgate-production.up.railway.app/guide">
14
+ <meta property="og:url" content="https://thumbgate.ai/guide">
15
+ <link rel="canonical" href="https://thumbgate.ai/guide">
16
16
 
17
17
  <script type="application/ld+json">
18
18
  {
@@ -28,11 +28,11 @@
28
28
  "publisher": {
29
29
  "@type": "Organization",
30
30
  "name": "ThumbGate",
31
- "url": "https://thumbgate-production.up.railway.app"
31
+ "url": "https://thumbgate.ai"
32
32
  },
33
33
  "datePublished": "2026-03-27",
34
34
  "dateModified": "2026-04-25",
35
- "mainEntityOfPage": "https://thumbgate-production.up.railway.app/guide",
35
+ "mainEntityOfPage": "https://thumbgate.ai/guide",
36
36
  "about": [
37
37
  {"@type": "Thing", "name": "AI coding agents"},
38
38
  {"@type": "Thing", "name": "Model Context Protocol"},
@@ -379,7 +379,7 @@ npx thumbgate init --agent gemini</code></pre>
379
379
  <a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/00w14neyUcXA5pL5e33sI0e" class="cta cta-secondary">Pay $499 diagnostic</a>
380
380
  <a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/fZu9AT76saPsg4pbCr3sI0f" class="cta cta-secondary">Pay $1500 sprint</a>
381
381
  <a href="https://thumbgate.ai/#workflow-sprint-intake" class="cta cta-secondary">Send workflow first</a>
382
- <p style="color:var(--muted); font-size:0.85rem;">Free: unlimited captures, 5 active prevention rules, hook blocking. Pro: dashboard, recall, lesson search, unlimited rules, DPO export. Team: intake first, then $49/seat/mo with a 3-seat minimum.</p>
382
+ <p style="color:var(--muted); font-size:0.85rem;">Free: 5 captures/day, 25 total captures, 3 active prevention rules, hook blocking. Pro: hosted sync, dashboard, recall, lesson search, unlimited captures/rules, DPO export. Team: intake first, then $49/seat/mo with a 3-seat minimum.</p>
383
383
 
384
384
  </div>
385
385
  </body>