thumbgate 1.21.2 → 1.23.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.
Files changed (37) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +1 -0
  5. package/adapters/chatgpt/openapi.yaml +10 -0
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/mcp/server-stdio.js +109 -1
  8. package/adapters/opencode/opencode.json +1 -1
  9. package/bin/cli.js +247 -30
  10. package/config/mcp-allowlists.json +12 -6
  11. package/openapi/openapi.yaml +10 -0
  12. package/package.json +29 -5
  13. package/public/agent-manager.html +1 -1
  14. package/public/agents-cost-savings.html +151 -0
  15. package/public/ai-malpractice-prevention.html +183 -0
  16. package/public/codex-enterprise.html +123 -0
  17. package/public/codex-plugin.html +1 -1
  18. package/public/dashboard.html +18 -5
  19. package/public/index.html +13 -6
  20. package/public/lessons.html +34 -0
  21. package/public/numbers.html +2 -2
  22. package/public/pricing.html +1 -1
  23. package/scripts/auto-wire-hooks.js +14 -0
  24. package/scripts/build-metadata.js +32 -13
  25. package/scripts/cli-telemetry.js +6 -1
  26. package/scripts/gate-stats.js +89 -0
  27. package/scripts/gates-engine.js +133 -6
  28. package/scripts/hook-runtime.js +9 -3
  29. package/scripts/meta-agent-loop.js +32 -0
  30. package/scripts/pro-local-dashboard.js +4 -4
  31. package/scripts/rate-limiter.js +7 -1
  32. package/scripts/self-healing-check.js +193 -0
  33. package/scripts/silent-failure-cluster.js +512 -0
  34. package/scripts/telemetry-analytics.js +38 -0
  35. package/scripts/tool-registry.js +18 -0
  36. package/scripts/workflow-sentinel.js +6 -1
  37. package/src/api/server.js +311 -36
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.21.2",
3
+ "version": "1.23.0",
4
4
  "owner": {
5
5
  "name": "Igor Ganapolsky",
6
6
  "email": "ig5973700@gmail.com"
@@ -14,7 +14,7 @@
14
14
  "source": "npm",
15
15
  "package": "thumbgate"
16
16
  },
17
- "version": "1.21.2",
17
+ "version": "1.23.0",
18
18
  "author": {
19
19
  "name": "Igor Ganapolsky",
20
20
  "email": "ig5973700@gmail.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "One 👎 becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
4
- "version": "1.21.2",
4
+ "version": "1.23.0",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
7
7
  "email": "ig5973700@gmail.com",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.21.2",
3
+ "version": "1.23.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
@@ -474,6 +474,7 @@ Pro ($19/mo or $149/yr) removes the rule cap and adds history-aware lesson recal
474
474
  - [Agent Workflow Contract](WORKFLOW.md) — the agent-run contract for all ThumbGate operations
475
475
  - [Ready for Agent Intake](https://github.com/IgorGanapolsky/ThumbGate/issues/new?template=ready-for-agent.yml) — ready-for-agent intake template
476
476
  - [SEO Guide: Claude Code Guardrails](docs/learn/claude-code-guardrails.md)
477
+ - [Unsupervised Learning Signals](docs/UL.md) — silent-failure clustering (experimental, behind `THUMBGATE_SILENT_FAILURE_CLUSTERING=1`; only useful on workspaces with ≥ 50 tool calls/day)
477
478
  - [ThumbGate-Core](https://github.com/IgorGanapolsky/ThumbGate-Core) — private core for hosted overlays, ranking, policy synthesis, billing intelligence, and org/team workflows
478
479
 
479
480
  ---
@@ -72,6 +72,11 @@ components:
72
72
  description: Optional domain tags. If omitted, ThumbGate infers one from the feedback text before promotion.
73
73
  skill:
74
74
  type: string
75
+ source:
76
+ type: string
77
+ enum: [chatgpt_gpt]
78
+ default: chatgpt_gpt
79
+ description: Attribution marker for ThumbGate analytics. The published ThumbGate GPT should send `chatgpt_gpt` so owner dashboards can distinguish GPT Action calls from local API calls.
75
80
  IntentPlanRequest:
76
81
  type: object
77
82
  required: [intentId]
@@ -880,6 +885,11 @@ paths:
880
885
  toolName:
881
886
  type: string
882
887
  description: Tool name is optional when provider-native tool call payload is supplied.
888
+ source:
889
+ type: string
890
+ enum: [chatgpt_gpt]
891
+ default: chatgpt_gpt
892
+ description: Attribution marker for ThumbGate analytics. The published ThumbGate GPT should send `chatgpt_gpt` so owner dashboards can distinguish GPT Action calls from local API calls.
883
893
  provider:
884
894
  type: string
885
895
  model:
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.23.0", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.23.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.21.2' };
219
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.23.0' };
220
220
  const COMMERCE_CATEGORIES = [
221
221
  'product_recommendation',
222
222
  'brand_compliance',
@@ -368,6 +368,111 @@ function buildRecallResponse(args = {}) {
368
368
  return toTextResult(text);
369
369
  }
370
370
 
371
+ function buildSuggestFixResponse(args = {}) {
372
+ const context = String(args.context || '').trim();
373
+ const rawLimit = Number(args.limit);
374
+ const limit = Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 5) : 3;
375
+
376
+ // If no context provided, return generic suggestion
377
+ if (!context) {
378
+ return toTextResult({
379
+ suggestions: [
380
+ {
381
+ action: 'Capture feedback about what went wrong so ThumbGate can learn and prevent recurrence.',
382
+ source: 'generic',
383
+ },
384
+ ],
385
+ query: '',
386
+ totalFound: 0,
387
+ });
388
+ }
389
+
390
+ // Search lessons via lesson-search module
391
+ const lessonModule = loadPrivateMcpModule('lessonSearch');
392
+ let lessonActions = [];
393
+ if (lessonModule) {
394
+ try {
395
+ const searchResult = lessonModule.searchLessons(context, { limit: 10 });
396
+ const results = Array.isArray(searchResult && searchResult.results) ? searchResult.results : [];
397
+ for (const result of results) {
398
+ const correctiveActions = (result.systemResponse && Array.isArray(result.systemResponse.correctiveActions))
399
+ ? result.systemResponse.correctiveActions
400
+ : [];
401
+ for (const action of correctiveActions) {
402
+ const text = String(action.text || '').trim();
403
+ if (text) {
404
+ lessonActions.push({
405
+ action: text,
406
+ source: action.source || `lesson:${result.id || 'unknown'}`,
407
+ score: result.score || 0,
408
+ });
409
+ }
410
+ }
411
+ // Also pick up lesson-level howToAvoid / actionNeeded when no explicit correctiveActions
412
+ if (correctiveActions.length === 0 && result.lesson) {
413
+ const text = result.lesson.howToAvoid || result.lesson.actionNeeded || '';
414
+ if (text) {
415
+ lessonActions.push({
416
+ action: String(text).trim(),
417
+ source: `lesson:${result.id || 'unknown'}`,
418
+ score: result.score || 0,
419
+ });
420
+ }
421
+ }
422
+ }
423
+ } catch {
424
+ // lesson search failure is non-fatal
425
+ }
426
+ }
427
+
428
+ // Search prevention rules directly via lesson-search module's helper
429
+ let ruleActions = [];
430
+ try {
431
+ const { readPreventionRuleMatches } = require('../../scripts/lesson-search');
432
+ const ruleMatches = readPreventionRuleMatches(context, limit);
433
+ for (const rule of ruleMatches) {
434
+ const text = rule.summary || rule.title || '';
435
+ if (text) {
436
+ ruleActions.push({
437
+ action: String(text).trim(),
438
+ source: `rule:${String(rule.title || 'prevention_rules').trim()}`,
439
+ score: rule.score || 0,
440
+ });
441
+ }
442
+ }
443
+ } catch {
444
+ // rule search failure is non-fatal
445
+ }
446
+
447
+ // Merge, deduplicate, sort by score, and take top `limit`
448
+ const seen = new Set();
449
+ const all = [...lessonActions, ...ruleActions]
450
+ .filter((item) => {
451
+ if (!item.action) return false;
452
+ const key = item.action.toLowerCase();
453
+ if (seen.has(key)) return false;
454
+ seen.add(key);
455
+ return true;
456
+ })
457
+ .sort((a, b) => (b.score || 0) - (a.score || 0))
458
+ .slice(0, limit)
459
+ .map(({ action, source }) => ({ action, source }));
460
+
461
+ // If nothing matched, add generic fallback
462
+ if (all.length === 0) {
463
+ all.push({
464
+ action: 'No matching lessons or rules found. Capture feedback via capture_feedback so ThumbGate can learn from this failure.',
465
+ source: 'generic',
466
+ });
467
+ }
468
+
469
+ return toTextResult({
470
+ suggestions: all,
471
+ query: context,
472
+ totalFound: all.length,
473
+ });
474
+ }
475
+
371
476
  function buildDiagnoseFailureResponse(args = {}) {
372
477
  let intentPlan = null;
373
478
  const requestedProfile = args.mcpProfile || getActiveMcpProfile();
@@ -579,6 +684,8 @@ async function callToolInner(name, args) {
579
684
  tags: Array.isArray(args.tags) ? args.tags : [],
580
685
  }));
581
686
  }
687
+ case 'suggest_fix':
688
+ return buildSuggestFixResponse(args);
582
689
  case 'retrieve_lessons': {
583
690
  // Cross-encoder reranking: retrieve more candidates, then rerank for precision
584
691
  const { retrieveWithRerankingSync } = loadOptionalModule(path.join(__dirname, '../../scripts/cross-encoder-reranker'), () => ({
@@ -1330,6 +1437,7 @@ module.exports = {
1330
1437
  acquireLock,
1331
1438
  toCaptureFeedbackTextResult,
1332
1439
  formatCorrectiveActionsReminder,
1440
+ buildSuggestFixResponse,
1333
1441
  __test__: {
1334
1442
  PRIVATE_MCP_MODULES,
1335
1443
  loadPrivateMcpModule,
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.21.2",
10
+ "thumbgate@1.23.0",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
package/bin/cli.js CHANGED
@@ -52,6 +52,25 @@ const PKG_ROOT = path.join(__dirname, '..');
52
52
 
53
53
  const PRO_URL = 'https://thumbgate-production.up.railway.app';
54
54
  const PRO_CHECKOUT_URL = PRO_MONTHLY_PAYMENT_LINK;
55
+ const TRIAL_DAYS = 14;
56
+
57
+ function checkoutUrlFor(source, content) {
58
+ try {
59
+ const url = new URL(PRO_CHECKOUT_URL);
60
+ url.searchParams.set('utm_source', source || 'cli');
61
+ url.searchParams.set('utm_medium', 'cli');
62
+ url.searchParams.set('utm_campaign', 'pro_conversion');
63
+ if (content) url.searchParams.set('utm_content', content);
64
+ return url.toString();
65
+ } catch (_) {
66
+ return PRO_CHECKOUT_URL;
67
+ }
68
+ }
69
+
70
+ function trialDeadlineLabel(now = new Date()) {
71
+ const deadline = new Date(now.getTime() + (TRIAL_DAYS * 24 * 60 * 60 * 1000));
72
+ return deadline.toISOString().slice(0, 10);
73
+ }
55
74
 
56
75
  function upgradeNudge() {
57
76
  if (process.env.THUMBGATE_NO_NUDGE === '1') return;
@@ -139,25 +158,46 @@ function proNudge(context) {
139
158
  const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
140
159
  if (isProTier()) return;
141
160
  } catch (_) { /* if rate-limiter is unavailable, fall through and nudge */ }
161
+ const checkoutUrl = checkoutUrlFor('cli_nudge', context || COMMAND || 'general');
142
162
  const messages = [
143
- `\n 💡 Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
144
- `\n 💡 Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
145
- `\n 💡 ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\n ${PRO_CHECKOUT_URL}\n`,
163
+ `\n 💡 Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${checkoutUrl}\n`,
164
+ `\n 💡 Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${checkoutUrl}\n`,
165
+ `\n 💡 ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\n ${checkoutUrl}\n`,
146
166
  ];
147
167
  // Rotate message daily — no Math.random (security policy)
148
168
  const msg = messages[Math.floor(Date.now() / 86400000) % messages.length];
149
169
  process.stderr.write(msg);
150
170
  }
151
171
 
152
- function limitNudge(action) {
172
+ function limitNudge(action, limitResult = {}) {
153
173
  if (process.env.THUMBGATE_NO_NUDGE === '1') return;
174
+ const checkoutUrl = checkoutUrlFor('cli_limit', action);
175
+ const usageLine = Number.isFinite(limitResult.used) && Number.isFinite(limitResult.limit)
176
+ ? ` Usage: ${limitResult.used}/${limitResult.limit} (${limitResult.limitType || 'limit'}).\n`
177
+ : '';
178
+ const reason = limitResult.message
179
+ ? String(limitResult.message).split('\n')[0]
180
+ : 'Free tier limit reached.';
154
181
  process.stderr.write(
155
- `\n ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro\n` +
156
- ` ${action} daily limit reached. Upgrade to Pro for unlimited usage — ${PRO_PRICE_LABEL}:\n` +
157
- ` ${PRO_CHECKOUT_URL}\n\n`
182
+ `\n 🔒 ${reason}\n` +
183
+ usageLine +
184
+ ` Upgrade to Pro (${PRO_PRICE_LABEL}) to continue ${action.replace(/_/g, ' ')}:\n` +
185
+ ` ${checkoutUrl}\n\n`
158
186
  );
159
187
  }
160
188
 
189
+ function printInitConversionPrompt(email) {
190
+ if (process.env.THUMBGATE_NO_NUDGE === '1') return;
191
+ const checkoutUrl = checkoutUrlFor('cli_init', email ? 'init_email' : 'init_no_email');
192
+ console.log('');
193
+ console.log(' ┌──────────────────────────────────────────────────────────┐');
194
+ console.log(` │ 14-day Pro trial active through ${trialDeadlineLabel()}. │`);
195
+ console.log(' │ Pro unlocks lesson search, recall, dashboard, exports. │');
196
+ console.log(' │ Add onboarding: npx thumbgate init --email you@company.com │');
197
+ console.log(` │ Upgrade: ${checkoutUrl}`);
198
+ console.log(' └──────────────────────────────────────────────────────────┘');
199
+ }
200
+
161
201
  function parseArgs(argv) {
162
202
  const args = {};
163
203
  argv.forEach((arg, index) => {
@@ -613,6 +653,18 @@ function quickStart() {
613
653
 
614
654
  function init(cliArgs = parseArgs(process.argv.slice(3))) {
615
655
  const args = { ...cliArgs };
656
+ if (args.help || args.h) {
657
+ console.log('Usage: npx thumbgate init [--agent <name>] [--wire-hooks] [--email you@company.com]');
658
+ console.log('');
659
+ console.log('Scaffold ThumbGate in the current project and wire detected agent integrations.');
660
+ console.log('');
661
+ console.log('Options:');
662
+ console.log(' --agent <name> Wire a specific agent: claude-code, codex, gemini, amp, cursor, cline');
663
+ console.log(' --wire-hooks Wire hooks only; do not scaffold project files');
664
+ console.log(' --email <email> Subscribe installer to the setup guide and trial reminders');
665
+ console.log(' --dry-run Show hook changes without writing them');
666
+ return;
667
+ }
616
668
 
617
669
  // --wire-hooks only mode: skip scaffolding, just wire hooks
618
670
  if (args['wire-hooks']) {
@@ -746,16 +798,48 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
746
798
  }
747
799
 
748
800
  console.log('');
749
- console.log(`thumbgate v${pkgVersion()} initialized.`);
750
- console.log('Run: npx thumbgate help');
801
+ console.log(`✅ thumbgate v${pkgVersion()} initialized.`);
802
+ const onboardingEmail = typeof args.email === 'string'
803
+ ? args.email.trim()
804
+ : (typeof args['onboarding-email'] === 'string' ? args['onboarding-email'].trim() : '');
805
+ if (onboardingEmail) {
806
+ try {
807
+ execFileSync(process.execPath, [__filename, 'subscribe', onboardingEmail], {
808
+ cwd: CWD,
809
+ env: process.env,
810
+ stdio: 'inherit',
811
+ });
812
+ trackEvent('cli_init_email_subscribed', { command: 'init', source: 'init_email' });
813
+ } catch (_) {
814
+ console.log(` Retry onboarding email: npx thumbgate subscribe ${onboardingEmail}`);
815
+ }
816
+ }
751
817
  trackEvent('cli_init', { command: 'init' });
752
- proNudge();
818
+
819
+ // ---------------------------------------------------------------------------
820
+ // Activation guide: the ONE thing the user should do next.
821
+ // 98.5% of init users never promote their first prevention rule.
822
+ // This is the funnel break — not conversion, not nudges — activation.
823
+ // ---------------------------------------------------------------------------
753
824
  console.log('');
754
- console.log(' ┌──────────────────────────────────────────────────────────┐');
755
- console.log(' │ Teams: shared enforcement, CI gates, audit trails │');
756
- console.log(' │ One correction protects every agent on your team. │');
757
- console.log(' │ https://thumbgate-production.up.railway.app/pro │');
758
- console.log(' └──────────────────────────────────────────────────────────┘');
825
+ console.log(' ╭──────────────────────────────────────────────────────────╮');
826
+ console.log(' │ NEXT: Create your first prevention rule (30 seconds) │');
827
+ console.log(' │ │');
828
+ console.log(' │ When your AI agent makes a mistake, capture it: │');
829
+ console.log(' │ │');
830
+ console.log(' │ npx thumbgate capture --feedback=down \\ │');
831
+ console.log(' │ --context="agent deleted prod config" \\ │');
832
+ console.log(' │ --what-went-wrong="ran rm on .env" \\ │');
833
+ console.log(' │ --what-to-change="never delete .env files" │');
834
+ console.log(' │ │');
835
+ console.log(' │ ThumbGate auto-promotes this to a prevention rule │');
836
+ console.log(' │ that blocks the mistake from happening again. │');
837
+ console.log(' ╰──────────────────────────────────────────────────────────╯');
838
+ console.log('');
839
+ printInitConversionPrompt(onboardingEmail);
840
+ if (!onboardingEmail) {
841
+ console.log(' Get trial reminders: npx thumbgate init --email you@company.com');
842
+ }
759
843
 
760
844
  try {
761
845
  const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
@@ -785,7 +869,7 @@ function capture() {
785
869
  const { getUsage } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
786
870
  const capLimit = checkLimit('capture_feedback');
787
871
  if (!capLimit.allowed) {
788
- limitNudge('capture_feedback');
872
+ limitNudge('capture_feedback', capLimit);
789
873
  process.exit(1);
790
874
  }
791
875
  trackEvent('cli_capture', { command: 'capture' });
@@ -878,7 +962,14 @@ function stats() {
878
962
  const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
879
963
  const data = analyzeFeedback();
880
964
 
965
+ // Gate enforcement stats — runtime intercepts + configured gates
966
+ let gateData = { blocked: 0, warned: 0, passed: 0, byGate: {} };
967
+ try { gateData = require(path.join(PKG_ROOT, 'scripts', 'gates-engine')).loadStats(); } catch {}
968
+ let gateConfigData = { totalGates: 0, autoPromotedGates: 0, estimatedHoursSaved: '0.0', topBlocked: null, firstTimeFixRate: null };
969
+ try { gateConfigData = require(path.join(PKG_ROOT, 'scripts', 'gate-stats')).calculateStats(); } catch {}
970
+
881
971
  const avgCostOfMistake = 2.50;
972
+ const totalInterceptions = gateData.blocked + gateData.warned;
882
973
  const payload = {
883
974
  total: data.total,
884
975
  positives: data.totalPositive,
@@ -888,6 +979,13 @@ function stats() {
888
979
  revenueAtRisk: Number((data.totalNegative * avgCostOfMistake).toFixed(2)),
889
980
  topTags: data.topTags || [],
890
981
  recentActivity: data.recentActivity || [],
982
+ gatesBlocked: gateData.blocked,
983
+ gatesWarned: gateData.warned,
984
+ totalGates: gateConfigData.totalGates,
985
+ autoPromotedGates: gateConfigData.autoPromotedGates,
986
+ estimatedHoursSaved: gateConfigData.estimatedHoursSaved,
987
+ topBlockedGate: gateConfigData.topBlocked ? gateConfigData.topBlocked.id : null,
988
+ firstTimeFixRate: gateConfigData.firstTimeFixRate,
891
989
  };
892
990
 
893
991
  if (args.json) {
@@ -901,6 +999,18 @@ function stats() {
901
999
  console.log(` Approval Rate : ${payload.approvalRate}%`);
902
1000
  console.log(` Recent Trend : ${payload.recentTrend}%`);
903
1001
 
1002
+ // Gate enforcement — the high-ROI section
1003
+ if (totalInterceptions > 0 || payload.totalGates > 0) {
1004
+ console.log('\n🛡️ PRE-ACTION GATE ENFORCEMENT');
1005
+ console.log(` Actions blocked : ${payload.gatesBlocked}`);
1006
+ console.log(` Actions warned : ${payload.gatesWarned}`);
1007
+ console.log(` Active gates : ${payload.totalGates} (${payload.autoPromotedGates} auto-promoted)`);
1008
+ if (payload.topBlockedGate) console.log(` Top blocker : ${payload.topBlockedGate}`);
1009
+ console.log(` Est. time saved : ~${payload.estimatedHoursSaved} hours`);
1010
+ const { formatFirstTimeFixRate } = require(path.join(PKG_ROOT, 'scripts', 'gate-stats'));
1011
+ console.log(` First-time fix : ${formatFirstTimeFixRate(payload.firstTimeFixRate)}`);
1012
+ }
1013
+
904
1014
  if (payload.negatives > 0) {
905
1015
  console.log('\n⚠️ REVENUE-AT-RISK ANALYSIS');
906
1016
  console.log(` Repeated Failures detected: ${payload.negatives}`);
@@ -1161,6 +1271,13 @@ function lessons() {
1161
1271
  const tags = String(args.tags || '').split(',').map((t) => t.trim()).filter(Boolean);
1162
1272
  const query = args.query || process.argv.slice(3).find((a) => !a.startsWith('--')) || '';
1163
1273
  const limit = Number(args.limit || 10);
1274
+ const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1275
+ const searchLimit = checkLimit('search_lessons');
1276
+ if (!searchLimit.allowed) {
1277
+ trackEvent('cli_upgrade_prompt', { command: 'lessons', action: 'search_lessons', limitType: searchLimit.limitType || null });
1278
+ limitNudge('search_lessons', searchLimit);
1279
+ process.exit(1);
1280
+ }
1164
1281
 
1165
1282
  // --remote: fetch from hosted Railway instance
1166
1283
  if (args.remote) {
@@ -1427,13 +1544,10 @@ function risk() {
1427
1544
  }
1428
1545
 
1429
1546
  function exportDpo() {
1430
- const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1431
- if (!isProTier(null)) {
1432
- process.stderr.write(
1433
- `\n 🔒 DPO Export requires Pro (${PRO_PRICE_LABEL}).\n` +
1434
- ` Your feedback would generate valuable training pairs.\n` +
1435
- ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
1436
- );
1547
+ const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1548
+ const exportLimit = checkLimit('export_dpo');
1549
+ if (!exportLimit.allowed) {
1550
+ limitNudge('export_dpo', exportLimit);
1437
1551
  process.exit(1);
1438
1552
  }
1439
1553
  const extraArgs = process.argv.slice(3).join(' ');
@@ -1450,13 +1564,10 @@ function exportDpo() {
1450
1564
  }
1451
1565
 
1452
1566
  function exportDatabricks() {
1453
- const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1454
- if (!isProTier(null)) {
1455
- process.stderr.write(
1456
- `\n 🔒 Databricks Export requires Pro (${PRO_PRICE_LABEL}).\n` +
1457
- ` Export feedback logs + proof artifacts for analytics.\n` +
1458
- ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
1459
- );
1567
+ const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1568
+ const exportLimit = checkLimit('export_databricks');
1569
+ if (!exportLimit.allowed) {
1570
+ limitNudge('export_databricks', exportLimit);
1460
1571
  process.exit(1);
1461
1572
  }
1462
1573
  const extraArgs = process.argv.slice(3).join(' ');
@@ -1684,6 +1795,25 @@ function gateStats() {
1684
1795
  console.log('\n' + formatStats(stats) + '\n');
1685
1796
  }
1686
1797
 
1798
+ function contextPacks() {
1799
+ const args = parseArgs(process.argv.slice(3));
1800
+ const { generateAutoContextPacks } = require(path.join(PKG_ROOT, 'scripts', 'auto-context-packs'));
1801
+ const result = generateAutoContextPacks();
1802
+ if (args.json) {
1803
+ console.log(JSON.stringify(result, null, 2));
1804
+ return;
1805
+ }
1806
+ console.log(`\nGenerated ${result.packCount} context pack(s):`);
1807
+ for (const p of result.packs) {
1808
+ console.log(` [${p.type}] ${p.name}`);
1809
+ console.log(` -> ${p.filePath}`);
1810
+ }
1811
+ if (result.packCount === 0) {
1812
+ console.log(' (No failure patterns found yet — capture some feedback first.)');
1813
+ }
1814
+ console.log('');
1815
+ }
1816
+
1687
1817
  function harnessAudit() {
1688
1818
  const args = parseArgs(process.argv.slice(3));
1689
1819
  const {
@@ -2416,6 +2546,39 @@ if (COMMAND === 'daemon' || COMMAND === 'serve-daemon') {
2416
2546
  process.exit(0);
2417
2547
  }
2418
2548
 
2549
+ // ---------------------------------------------------------------------------
2550
+ // Global --help interceptor: `thumbgate <cmd> --help` shows per-command help
2551
+ // instead of running the command. Commands with their own --help guard (init)
2552
+ // handle it internally; everything else falls through here.
2553
+ // ---------------------------------------------------------------------------
2554
+ const _cliSubArgs = process.argv.slice(3);
2555
+ const _wantsHelp = _cliSubArgs.includes('--help') || _cliSubArgs.includes('-h');
2556
+
2557
+ const SUBCOMMAND_HELP = {
2558
+ capture: 'Usage: npx thumbgate capture --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
2559
+ feedback: 'Usage: npx thumbgate feedback --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
2560
+ stats: 'Usage: npx thumbgate stats\n\nShow gate enforcement statistics: blocked/warned counts, active gates, time saved.',
2561
+ trial: 'Usage: npx thumbgate trial\n\nShow Pro trial status, remaining days, and upgrade path.',
2562
+ pro: 'Usage: npx thumbgate pro [--activate <key>]\n\nLaunch the local Pro dashboard or activate a Pro license key.',
2563
+ subscribe: 'Usage: npx thumbgate subscribe <email>\n\nSubscribe to the 5-minute setup guide + trial reminders.',
2564
+ lessons: 'Usage: npx thumbgate lessons [--query="..."] [--limit=N]\n\nSearch the lesson database (Pro feature).',
2565
+ search: 'Usage: npx thumbgate search <query>\n\nSearch ThumbGate knowledge base (Pro feature).',
2566
+ 'gate-check': 'Usage: npx thumbgate gate-check\n\nPreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict.',
2567
+ 'export-dpo': 'Usage: npx thumbgate export-dpo [--format=jsonl|csv]\n\nExport feedback as DPO training pairs (Pro feature).',
2568
+ status: 'Usage: npx thumbgate status\n\nShow ThumbGate system health and active configuration.',
2569
+ watch: 'Usage: npx thumbgate watch\n\nWatch for feedback changes and auto-regenerate prevention rules.',
2570
+ compact: 'Usage: npx thumbgate compact\n\nCompact the lesson database and reclaim disk space.',
2571
+ 'context-packs': 'Usage: npx thumbgate context-packs\n\nGenerate context packs from top failure patterns.',
2572
+ suggest: 'Usage: npx thumbgate suggest <gate-id>\n\nSuggest fixes for a specific gate based on lesson history.',
2573
+ cost: 'Usage: npx thumbgate cost [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nShow cumulative $ and tokens saved by PreToolUse gate blocks. Reads ~/.thumbgate/gate-stats.json.',
2574
+ savings: 'Usage: npx thumbgate savings [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nAlias for `thumbgate cost`.',
2575
+ };
2576
+
2577
+ if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
2578
+ console.log(SUBCOMMAND_HELP[COMMAND]);
2579
+ process.exit(0);
2580
+ }
2581
+
2419
2582
  switch (COMMAND) {
2420
2583
  case '--version':
2421
2584
  case '-v':
@@ -2470,6 +2633,17 @@ switch (COMMAND) {
2470
2633
  case 'revenue':
2471
2634
  cfo();
2472
2635
  break;
2636
+ case 'cost':
2637
+ case 'savings':
2638
+ case 'costs': {
2639
+ const { main: costMain } = require(path.join(PKG_ROOT, 'scripts', 'cost-cli'));
2640
+ process.exit(costMain(process.argv.slice(3)));
2641
+ // process.exit doesn't return, but keep an explicit break so the switch
2642
+ // cannot accidentally fall through to case 'billing:setup' if a future
2643
+ // refactor wraps costMain in try/finally that intercepts the exit, or
2644
+ // a test runner stubs process.exit (flagged by gitar-bot on PR #2281).
2645
+ break;
2646
+ }
2473
2647
  case 'billing:setup':
2474
2648
  require(path.join(PKG_ROOT, 'scripts', 'billing-setup'));
2475
2649
  break;
@@ -2486,6 +2660,11 @@ switch (COMMAND) {
2486
2660
  case 'search-lessons':
2487
2661
  lessons();
2488
2662
  break;
2663
+ case 'notes': {
2664
+ const { cli: notesCli } = require(path.join(PKG_ROOT, 'scripts', 'implementation-notes'));
2665
+ notesCli(process.argv.slice(3));
2666
+ break;
2667
+ }
2489
2668
  case 'lesson-health':
2490
2669
  case 'stale': {
2491
2670
  const { initDB } = require(path.join(PKG_ROOT, 'scripts', 'lesson-db'));
@@ -2627,6 +2806,9 @@ switch (COMMAND) {
2627
2806
  case 'rules':
2628
2807
  rules();
2629
2808
  break;
2809
+ case 'context-packs':
2810
+ contextPacks();
2811
+ break;
2630
2812
  case 'harness-audit':
2631
2813
  case 'harness':
2632
2814
  harnessAudit();
@@ -2720,6 +2902,41 @@ switch (COMMAND) {
2720
2902
  case 'self-heal':
2721
2903
  selfHeal();
2722
2904
  break;
2905
+ case 'trial': {
2906
+ // Show trial status — connects the 4K monthly npm installers to checkout
2907
+ const { isProTier, isInTrialPeriod, trialDaysRemaining, getInstallAgeDays } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
2908
+ const ageDays = getInstallAgeDays();
2909
+ const inTrial = isInTrialPeriod();
2910
+ const remaining = trialDaysRemaining();
2911
+ const isPro = isProTier();
2912
+ console.log('');
2913
+ console.log(' ThumbGate Pro Trial');
2914
+ console.log(' ──────────────────');
2915
+ if (isPro && !inTrial) {
2916
+ console.log(' Status: ✅ Pro (active license or API key)');
2917
+ } else if (inTrial) {
2918
+ const expiryDate = new Date(Date.now() + remaining * 86400000).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
2919
+ console.log(` Status: 🎁 Active (${remaining} day${remaining === 1 ? '' : 's'} remaining)`);
2920
+ console.log(` Expires: ${expiryDate}`);
2921
+ console.log('');
2922
+ console.log(' All Pro features unlocked:');
2923
+ console.log(' • Unlimited prevention rules (free tier: 5)');
2924
+ console.log(' • Recall, lesson search, DPO export');
2925
+ console.log(' • Cross-machine sync via API key');
2926
+ } else {
2927
+ console.log(' Status: ⏰ Expired');
2928
+ if (ageDays !== null) {
2929
+ console.log(` Installed: ${Math.floor(ageDays)} days ago`);
2930
+ }
2931
+ console.log('');
2932
+ console.log(' Free tier: 5 active rules, no recall/search/export');
2933
+ }
2934
+ console.log('');
2935
+ console.log(` Keep Pro: ${PRO_CHECKOUT_URL}`);
2936
+ console.log(` Or set: THUMBGATE_API_KEY=<your-key>`);
2937
+ console.log('');
2938
+ break;
2939
+ }
2723
2940
  case 'pro':
2724
2941
  pro();
2725
2942
  break;