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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +1 -0
- package/adapters/chatgpt/openapi.yaml +10 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +109 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +247 -30
- package/config/mcp-allowlists.json +12 -6
- package/openapi/openapi.yaml +10 -0
- package/package.json +29 -5
- package/public/agent-manager.html +1 -1
- package/public/agents-cost-savings.html +151 -0
- package/public/ai-malpractice-prevention.html +183 -0
- package/public/codex-enterprise.html +123 -0
- package/public/codex-plugin.html +1 -1
- package/public/dashboard.html +18 -5
- package/public/index.html +13 -6
- package/public/lessons.html +34 -0
- package/public/numbers.html +2 -2
- package/public/pricing.html +1 -1
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/build-metadata.js +32 -13
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +133 -6
- package/scripts/hook-runtime.js +9 -3
- package/scripts/meta-agent-loop.js +32 -0
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/rate-limiter.js +7 -1
- package/scripts/self-healing-check.js +193 -0
- package/scripts/silent-failure-cluster.js +512 -0
- package/scripts/telemetry-analytics.js +38 -0
- package/scripts/tool-registry.js +18 -0
- package/scripts/workflow-sentinel.js +6 -1
- package/src/api/server.js +311 -36
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate-marketplace",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
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 ${
|
|
144
|
-
`\n 💡 Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${
|
|
145
|
-
`\n 💡 ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\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
|
|
156
|
-
|
|
157
|
-
` ${
|
|
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(
|
|
750
|
-
|
|
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
|
-
|
|
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(' │
|
|
756
|
-
console.log(' │
|
|
757
|
-
console.log(' │
|
|
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 {
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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 {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
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;
|