thumbgate 1.26.0 → 1.26.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.
- 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 +62 -31
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +83 -6
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +390 -14
- package/config/mcp-allowlists.json +3 -0
- package/package.json +13 -2
- package/public/agents-cost-savings.html +2 -0
- package/public/index.html +10 -2
- package/public/numbers.html +2 -2
- package/scripts/action-receipts.js +324 -0
- package/scripts/cli-schema.js +24 -0
- package/scripts/dashboard.js +6 -1
- package/scripts/gates-engine.js +28 -9
- package/scripts/llm-client.js +90 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/meta-agent-loop.js +9 -5
- package/scripts/noop-detect.js +285 -0
- package/scripts/operational-dashboard.js +160 -0
- package/scripts/operational-summary.js +178 -0
- package/scripts/plan-gate.js +11 -0
- package/scripts/repeat-metric.js +121 -0
- package/scripts/silent-failure-cluster.js +22 -3
- package/scripts/tool-registry.js +50 -0
package/bin/cli.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
'use strict';
|
|
30
30
|
|
|
31
31
|
const fs = require('fs');
|
|
32
|
+
const os = require('os');
|
|
32
33
|
const path = require('path');
|
|
33
34
|
const crypto = require('crypto');
|
|
34
35
|
const { execSync, execFileSync } = require('child_process');
|
|
@@ -610,6 +611,91 @@ function detectAgent(projectDir) {
|
|
|
610
611
|
return null;
|
|
611
612
|
}
|
|
612
613
|
|
|
614
|
+
async function setupVertex() {
|
|
615
|
+
const { execSync } = require('child_process');
|
|
616
|
+
console.log(`\nthumbgate setup-vertex v${pkgVersion()}`);
|
|
617
|
+
console.log(' Zero-friction Google Cloud & Vertex AI onboarding...');
|
|
618
|
+
console.log('');
|
|
619
|
+
|
|
620
|
+
// 1. Detect gcloud CLI
|
|
621
|
+
let activeAccount = '';
|
|
622
|
+
let activeProject = '';
|
|
623
|
+
try {
|
|
624
|
+
activeAccount = execSync('gcloud config get-value account', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
625
|
+
activeProject = execSync('gcloud config get-value project', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
|
|
626
|
+
} catch (_) {
|
|
627
|
+
console.log(' ⚠️ Google Cloud SDK (gcloud CLI) not detected or not logged in.');
|
|
628
|
+
console.log(' To automate setup, install the Google Cloud CLI and run: gcloud auth login');
|
|
629
|
+
console.log(' Otherwise, manually set the following variables in your .env file:');
|
|
630
|
+
console.log(' THUMBGATE_PROVIDER_MODE=vertex');
|
|
631
|
+
console.log(' VERTEX_PROJECT_ID=<your-gcp-project-id>');
|
|
632
|
+
console.log('');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!activeAccount) {
|
|
637
|
+
console.log(' ⚠️ No active Google Cloud account set. Run: gcloud auth login');
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
console.log(` Active Account : \x1b[36m${activeAccount}\x1b[0m`);
|
|
642
|
+
if (!activeProject) {
|
|
643
|
+
console.log(' ⚠️ No active Google Cloud project set. Run: gcloud config set project <PROJECT_ID>');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
console.log(` Active Project : \x1b[36m${activeProject}\x1b[0m`);
|
|
647
|
+
|
|
648
|
+
// Validate project ID matches GCP format before use in shell
|
|
649
|
+
if (!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(activeProject)) {
|
|
650
|
+
console.log(' ⚠️ Invalid GCP project ID format. Aborting.');
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// 2. Auto-enable Vertex AI API
|
|
655
|
+
console.log(' ⚙️ Enabling Vertex AI API in your project (this can take a few seconds)...');
|
|
656
|
+
try {
|
|
657
|
+
execSync(`gcloud services enable aiplatform.googleapis.com --project=${activeProject}`, { stdio: 'inherit' });
|
|
658
|
+
console.log(' ✅ Vertex AI API successfully enabled.');
|
|
659
|
+
} catch (err) {
|
|
660
|
+
console.log(' ⚠️ Could not programmatically enable Vertex AI API. Please make sure your billing is open.');
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// 3. Write env config to .env
|
|
664
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
665
|
+
let envContent = '';
|
|
666
|
+
if (fs.existsSync(envPath)) {
|
|
667
|
+
envContent = fs.readFileSync(envPath, 'utf8');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Helper to update or append env values
|
|
671
|
+
function updateEnvKey(content, key, value) {
|
|
672
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
673
|
+
if (regex.test(content)) {
|
|
674
|
+
return content.replace(regex, `${key}=${value}`);
|
|
675
|
+
}
|
|
676
|
+
return content.trim() + `\n${key}=${value}\n`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
let updatedContent = updateEnvKey(envContent, 'THUMBGATE_PROVIDER_MODE', 'vertex');
|
|
680
|
+
updatedContent = updateEnvKey(updatedContent, 'VERTEX_PROJECT_ID', activeProject);
|
|
681
|
+
|
|
682
|
+
fs.writeFileSync(envPath, updatedContent, 'utf8');
|
|
683
|
+
console.log(' ✅ Wrote configuration to local .env file.');
|
|
684
|
+
|
|
685
|
+
// 4. Print gorgeous success activation box
|
|
686
|
+
console.log('');
|
|
687
|
+
console.log(' ╭──────────────────────────────────────────────────────────╮');
|
|
688
|
+
console.log(' │ 🎉 Vertex AI Setup Complete — ZERO FRICTION! │');
|
|
689
|
+
console.log(' │ │');
|
|
690
|
+
console.log(' │ ThumbGate is now fully wired to your GCP environment. │');
|
|
691
|
+
console.log(' │ All agent checks will route securely via Vertex AI. │');
|
|
692
|
+
console.log(' │ │');
|
|
693
|
+
console.log(' │ Try a test run: │');
|
|
694
|
+
console.log(' │ npx thumbgate feedback-self-test │');
|
|
695
|
+
console.log(' ╰──────────────────────────────────────────────────────────╯');
|
|
696
|
+
console.log('');
|
|
697
|
+
}
|
|
698
|
+
|
|
613
699
|
function quickStart() {
|
|
614
700
|
const qsArgs = parseArgs(process.argv.slice(3));
|
|
615
701
|
const projectDir = process.cwd();
|
|
@@ -845,17 +931,12 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
845
931
|
// ---------------------------------------------------------------------------
|
|
846
932
|
console.log('');
|
|
847
933
|
console.log(' ╭──────────────────────────────────────────────────────────╮');
|
|
848
|
-
console.log(' │ NEXT:
|
|
934
|
+
console.log(' │ NEXT: Prove feedback capture works │');
|
|
849
935
|
console.log(' │ │');
|
|
850
|
-
console.log(' │
|
|
936
|
+
console.log(' │ npx thumbgate feedback-self-test │');
|
|
851
937
|
console.log(' │ │');
|
|
852
|
-
console.log(' │
|
|
853
|
-
console.log(' │
|
|
854
|
-
console.log(' │ --what-went-wrong="ran rm on .env" \\ │');
|
|
855
|
-
console.log(' │ --what-to-change="never delete .env files" │');
|
|
856
|
-
console.log(' │ │');
|
|
857
|
-
console.log(' │ ThumbGate auto-promotes this to a prevention rule │');
|
|
858
|
-
console.log(' │ that blocks the mistake from happening again. │');
|
|
938
|
+
console.log(' │ Then dogfood it in chat: │');
|
|
939
|
+
console.log(' │ thumbs down: agent skipped verification │');
|
|
859
940
|
console.log(' ╰──────────────────────────────────────────────────────────╯');
|
|
860
941
|
console.log('');
|
|
861
942
|
printInitConversionPrompt(onboardingEmail);
|
|
@@ -906,13 +987,35 @@ function capture() {
|
|
|
906
987
|
return;
|
|
907
988
|
}
|
|
908
989
|
|
|
909
|
-
|
|
990
|
+
// Parse pure positional arguments
|
|
991
|
+
const rawArgv = process.argv.slice(3);
|
|
992
|
+
const positionalArgs = [];
|
|
993
|
+
const BOOLEAN_FLAGS = new Set(['--json', '--verbose', '--quiet', '--dry-run', '--stats', '--summary', '--no-nudge', '--help']);
|
|
994
|
+
for (let i = 0; i < rawArgv.length; i++) {
|
|
995
|
+
const arg = rawArgv[i];
|
|
996
|
+
if (arg.startsWith('--')) {
|
|
997
|
+
if (!arg.includes('=') && !BOOLEAN_FLAGS.has(arg)) {
|
|
998
|
+
i++;
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
positionalArgs.push(arg);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
let signal = (args.feedback || '').toLowerCase();
|
|
1006
|
+
if (!signal && positionalArgs[0]) {
|
|
1007
|
+
const firstPos = positionalArgs[0].toLowerCase();
|
|
1008
|
+
if (['up', 'down', 'thumbsup', 'thumbsdown', 'thumbs_up', 'thumbs_down', 'positive', 'negative'].some(v => firstPos.includes(v))) {
|
|
1009
|
+
signal = firstPos;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
910
1013
|
const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some(v => signal.includes(v)) ? 'up'
|
|
911
1014
|
: ['down', 'thumbsdown', 'thumbs_down', 'negative'].some(v => signal.includes(v)) ? 'down'
|
|
912
1015
|
: signal;
|
|
913
1016
|
|
|
914
1017
|
if (normalized !== 'up' && normalized !== 'down') {
|
|
915
|
-
console.error('Missing or unrecognized --feedback=up|down');
|
|
1018
|
+
console.error('Missing or unrecognized --feedback=up|down (or positional argument)');
|
|
916
1019
|
process.exit(1);
|
|
917
1020
|
}
|
|
918
1021
|
|
|
@@ -922,11 +1025,30 @@ function capture() {
|
|
|
922
1025
|
process.exit(1);
|
|
923
1026
|
}
|
|
924
1027
|
|
|
1028
|
+
let context = args.context || '';
|
|
1029
|
+
if (!context && positionalArgs[1]) {
|
|
1030
|
+
context = positionalArgs[1];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
let whatWentWrong = args['what-went-wrong'];
|
|
1034
|
+
if (!whatWentWrong && positionalArgs[2]) {
|
|
1035
|
+
whatWentWrong = positionalArgs[2];
|
|
1036
|
+
} else if (!whatWentWrong && normalized === 'down' && context) {
|
|
1037
|
+
whatWentWrong = context;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
let whatToChange = args['what-to-change'];
|
|
1041
|
+
if (!whatToChange && positionalArgs[3]) {
|
|
1042
|
+
whatToChange = positionalArgs[3];
|
|
1043
|
+
} else if (!whatToChange && normalized === 'down' && context) {
|
|
1044
|
+
whatToChange = `avoid: ${context}`;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
925
1047
|
const result = captureFeedback({
|
|
926
1048
|
signal: normalized,
|
|
927
|
-
context:
|
|
928
|
-
whatWentWrong:
|
|
929
|
-
whatToChange:
|
|
1049
|
+
context: context,
|
|
1050
|
+
whatWentWrong: whatWentWrong,
|
|
1051
|
+
whatToChange: whatToChange,
|
|
930
1052
|
whatWorked: args['what-worked'],
|
|
931
1053
|
tags: args.tags,
|
|
932
1054
|
gateAction: gateAction || undefined,
|
|
@@ -989,6 +1111,116 @@ function capture() {
|
|
|
989
1111
|
}
|
|
990
1112
|
}
|
|
991
1113
|
|
|
1114
|
+
function readJsonlEntries(filePath) {
|
|
1115
|
+
try {
|
|
1116
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
1117
|
+
.split(/\r?\n/)
|
|
1118
|
+
.map((line) => line.trim())
|
|
1119
|
+
.filter(Boolean)
|
|
1120
|
+
.map((line) => JSON.parse(line));
|
|
1121
|
+
} catch (_) {
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
function shellSingleQuote(value) {
|
|
1127
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function feedbackSelfTest() {
|
|
1131
|
+
const args = parseArgs(process.argv.slice(3));
|
|
1132
|
+
const signalArg = String(args.feedback || args.signal || 'down').toLowerCase();
|
|
1133
|
+
const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some((v) => signalArg.includes(v)) ? 'up'
|
|
1134
|
+
: ['down', 'thumbsdown', 'thumbs_down', 'negative'].some((v) => signalArg.includes(v)) ? 'down'
|
|
1135
|
+
: signalArg;
|
|
1136
|
+
|
|
1137
|
+
if (normalized !== 'up' && normalized !== 'down') {
|
|
1138
|
+
console.error('feedback-self-test needs --feedback=up|down when overriding the default.');
|
|
1139
|
+
process.exit(1);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
|
|
1143
|
+
const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
|
|
1144
|
+
const feedbackDir = args['feedback-dir']
|
|
1145
|
+
? path.resolve(CWD, args['feedback-dir'])
|
|
1146
|
+
: args.persist
|
|
1147
|
+
? null
|
|
1148
|
+
: fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-feedback-self-test-'));
|
|
1149
|
+
const isolated = Boolean(feedbackDir);
|
|
1150
|
+
|
|
1151
|
+
if (feedbackDir) process.env.THUMBGATE_FEEDBACK_DIR = feedbackDir;
|
|
1152
|
+
process.env.THUMBGATE_NO_NUDGE = '1';
|
|
1153
|
+
|
|
1154
|
+
try {
|
|
1155
|
+
const { captureFeedback, getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1156
|
+
const context = args.context || `feedback self-test: typed thumbs ${normalized} reaches ThumbGate capture`;
|
|
1157
|
+
const result = captureFeedback({
|
|
1158
|
+
signal: normalized,
|
|
1159
|
+
context,
|
|
1160
|
+
whatWentWrong: normalized === 'down'
|
|
1161
|
+
? (args['what-went-wrong'] || 'Need proof that feedback capture is wired in this runtime')
|
|
1162
|
+
: undefined,
|
|
1163
|
+
whatToChange: normalized === 'down'
|
|
1164
|
+
? (args['what-to-change'] || 'Run a one-command self-test before claiming thumbs feedback is captured')
|
|
1165
|
+
: undefined,
|
|
1166
|
+
whatWorked: normalized === 'up'
|
|
1167
|
+
? (args['what-worked'] || 'Feedback capture persisted and was verified by a self-test')
|
|
1168
|
+
: undefined,
|
|
1169
|
+
tags: args.tags || 'self-test,dogfood,feedback-capture',
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
const paths = getFeedbackPaths();
|
|
1173
|
+
const feedbackRows = readJsonlEntries(paths.FEEDBACK_LOG_PATH);
|
|
1174
|
+
const memoryRows = readJsonlEntries(paths.MEMORY_LOG_PATH);
|
|
1175
|
+
const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
|
|
1176
|
+
const memoryId = result.memoryRecord && result.memoryRecord.id;
|
|
1177
|
+
const feedbackStored = Boolean(feedbackId && feedbackRows.some((row) => row.id === feedbackId));
|
|
1178
|
+
const memoryStored = Boolean(memoryId && memoryRows.some((row) => row.id === memoryId));
|
|
1179
|
+
const ok = Boolean(result.accepted && feedbackStored && memoryStored);
|
|
1180
|
+
|
|
1181
|
+
const payload = {
|
|
1182
|
+
ok,
|
|
1183
|
+
command: 'feedback-self-test',
|
|
1184
|
+
signal: normalized,
|
|
1185
|
+
accepted: Boolean(result.accepted),
|
|
1186
|
+
feedbackId: feedbackId || null,
|
|
1187
|
+
memoryId: memoryId || null,
|
|
1188
|
+
feedbackStored,
|
|
1189
|
+
memoryStored,
|
|
1190
|
+
isolated,
|
|
1191
|
+
feedbackDir: paths.FEEDBACK_DIR,
|
|
1192
|
+
nextDogfoodCommand: `npx thumbgate capture --feedback=${normalized} --context=${shellSingleQuote(context)}`,
|
|
1193
|
+
};
|
|
1194
|
+
|
|
1195
|
+
if (args.json) {
|
|
1196
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1197
|
+
} else if (ok) {
|
|
1198
|
+
console.log('\nThumbGate feedback self-test: PASS');
|
|
1199
|
+
console.log('─'.repeat(50));
|
|
1200
|
+
console.log(` Captured : ${normalized} (${feedbackId})`);
|
|
1201
|
+
console.log(` Stored lesson: ${memoryId}`);
|
|
1202
|
+
console.log(` Storage : ${paths.FEEDBACK_DIR}`);
|
|
1203
|
+
console.log(` Mode : ${isolated ? 'isolated test store' : 'active ThumbGate store'}`);
|
|
1204
|
+
console.log('\nDogfood in chat with:');
|
|
1205
|
+
console.log(` thumbs ${normalized}: ${context}`);
|
|
1206
|
+
} else {
|
|
1207
|
+
console.log('\nThumbGate feedback self-test: FAIL');
|
|
1208
|
+
console.log('─'.repeat(50));
|
|
1209
|
+
console.log(` Accepted : ${payload.accepted}`);
|
|
1210
|
+
console.log(` Feedback stored: ${feedbackStored}`);
|
|
1211
|
+
console.log(` Memory stored : ${memoryStored}`);
|
|
1212
|
+
console.log(` Storage : ${paths.FEEDBACK_DIR}`);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (!ok) process.exit(2);
|
|
1216
|
+
} finally {
|
|
1217
|
+
if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
|
|
1218
|
+
else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
|
|
1219
|
+
if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
|
|
1220
|
+
else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
992
1224
|
function stats() {
|
|
993
1225
|
trackEvent('cli_stats', { command: 'stats' });
|
|
994
1226
|
const args = parseArgs(process.argv.slice(3));
|
|
@@ -2456,12 +2688,14 @@ function help() {
|
|
|
2456
2688
|
console.log('');
|
|
2457
2689
|
console.log('Common commands:');
|
|
2458
2690
|
console.log(' init Detect agent and wire ThumbGate hooks');
|
|
2691
|
+
console.log(' feedback-self-test Prove thumbs capture works locally');
|
|
2459
2692
|
console.log(' capture --feedback=up|down --context="<text>" Capture a thumbs signal as a stored lesson');
|
|
2460
2693
|
console.log(' stats Approval rate, recent trend, blocked-pattern count');
|
|
2461
2694
|
console.log(' lessons [query] Search promoted lessons');
|
|
2462
2695
|
console.log(' explore Interactive TUI for lessons, gates, stats');
|
|
2463
2696
|
console.log(' dashboard Open the local ThumbGate dashboard');
|
|
2464
2697
|
console.log(' doctor Audit runtime isolation + bootstrap context');
|
|
2698
|
+
console.log(' brain [--write] Build the agent-readable context brain (lessons + rules + gates)');
|
|
2465
2699
|
console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
|
|
2466
2700
|
console.log(' subscribe <email> Get the 5-min setup guide + weekly tips by email');
|
|
2467
2701
|
console.log('');
|
|
@@ -2557,6 +2791,7 @@ function help() {
|
|
|
2557
2791
|
|
|
2558
2792
|
console.log('Examples:');
|
|
2559
2793
|
console.log(' npx thumbgate init');
|
|
2794
|
+
console.log(' npx thumbgate feedback-self-test');
|
|
2560
2795
|
console.log(' npx thumbgate status --json');
|
|
2561
2796
|
console.log(' npx thumbgate explore lessons --json');
|
|
2562
2797
|
console.log(' npx thumbgate explore gates --json');
|
|
@@ -2598,6 +2833,8 @@ const _wantsHelp = _cliSubArgs.includes('--help') || _cliSubArgs.includes('-h');
|
|
|
2598
2833
|
const SUBCOMMAND_HELP = {
|
|
2599
2834
|
capture: 'Usage: npx thumbgate capture --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
|
|
2600
2835
|
feedback: 'Usage: npx thumbgate feedback --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
|
|
2836
|
+
'feedback-self-test': 'Usage: npx thumbgate feedback-self-test [--json] [--persist] [--feedback=up|down]\n\nCapture a synthetic thumbs signal and verify feedback-log + memory-log writes. Defaults to an isolated test store; use --persist to dogfood the active ThumbGate store.',
|
|
2837
|
+
dogfood: 'Usage: npx thumbgate dogfood [--json] [--persist] [--feedback=up|down]\n\nAlias for feedback-self-test.',
|
|
2601
2838
|
stats: 'Usage: npx thumbgate stats\n\nShow gate enforcement statistics: blocked/warned counts, active gates, time saved.',
|
|
2602
2839
|
trial: 'Usage: npx thumbgate trial\n\nShow Pro trial status, remaining days, and upgrade path.',
|
|
2603
2840
|
pro: 'Usage: npx thumbgate pro [--activate <key>]\n\nLaunch the local Pro dashboard or activate a Pro license key.',
|
|
@@ -2613,6 +2850,8 @@ const SUBCOMMAND_HELP = {
|
|
|
2613
2850
|
suggest: 'Usage: npx thumbgate suggest <gate-id>\n\nSuggest fixes for a specific gate based on lesson history.',
|
|
2614
2851
|
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.',
|
|
2615
2852
|
savings: 'Usage: npx thumbgate savings [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nAlias for `thumbgate cost`.',
|
|
2853
|
+
'setup-vertex': 'Usage: npx thumbgate setup-vertex\n\nAuto-enable Vertex AI API on GCP and write secure credentials to local .env.',
|
|
2854
|
+
brain: 'Usage: npx thumbgate brain [--write] [--json] [--limit=N]\n\nBuild the agent-readable "context brain" — a single artifact consolidating this\nrepo\'s lessons, prevention rules, active gates, and project context for a coding\nagent to read BEFORE acting. --write saves it to .thumbgate/BRAIN.md (versioned,\ndeterministic). --json emits the structured model. --limit caps lessons (default 15).',
|
|
2616
2855
|
};
|
|
2617
2856
|
|
|
2618
2857
|
if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
|
|
@@ -2620,6 +2859,128 @@ if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
|
|
|
2620
2859
|
process.exit(0);
|
|
2621
2860
|
}
|
|
2622
2861
|
|
|
2862
|
+
// -----------------------------------------------------------------------------
|
|
2863
|
+
// brain — consolidate ThumbGate's institutional memory (lessons + prevention
|
|
2864
|
+
// rules + active gates + project context) into a single, versioned,
|
|
2865
|
+
// agent-readable "context brain". The persistent context a coding agent should
|
|
2866
|
+
// read BEFORE acting, so it stops repeating mistakes. Composes the existing
|
|
2867
|
+
// explore-subcommands primitives; output is deterministic (no volatile
|
|
2868
|
+
// timestamp) so `.thumbgate/BRAIN.md` diffs only when the underlying memory
|
|
2869
|
+
// changes.
|
|
2870
|
+
// -----------------------------------------------------------------------------
|
|
2871
|
+
function buildBrainModel(opts = {}) {
|
|
2872
|
+
const { exploreLessons, exploreRules, exploreGates } = require(path.join(PKG_ROOT, 'scripts', 'explore-subcommands'));
|
|
2873
|
+
let feedbackDir;
|
|
2874
|
+
try {
|
|
2875
|
+
const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
2876
|
+
feedbackDir = getFeedbackPaths().FEEDBACK_DIR;
|
|
2877
|
+
} catch (_) { feedbackDir = path.join(CWD, '.thumbgate'); }
|
|
2878
|
+
const limit = Math.max(1, Number(opts.limit) || 15);
|
|
2879
|
+
const safe = (fn, fallback) => { try { return fn(); } catch (_) { return fallback; } };
|
|
2880
|
+
const lessons = safe(() => exploreLessons({ feedbackDir, pkgRoot: PKG_ROOT, limit, json: true }), { lessons: [], total: 0 });
|
|
2881
|
+
const rules = safe(() => exploreRules({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { rules: [], total: 0 });
|
|
2882
|
+
const gates = safe(() => exploreGates({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { gates: [], total: 0 });
|
|
2883
|
+
const instructionFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.cursorrules', '.thumbgate/config.json']
|
|
2884
|
+
.filter((f) => { try { return fs.existsSync(path.join(CWD, f)); } catch (_) { return false; } });
|
|
2885
|
+
return { lessons, rules, gates, project: { root: CWD, instructionFiles } };
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
function renderBrainMarkdown(model) {
|
|
2889
|
+
const out = [];
|
|
2890
|
+
out.push('# ThumbGate Context Brain');
|
|
2891
|
+
out.push('');
|
|
2892
|
+
out.push('<!-- The persistent context a coding agent should read BEFORE acting in this repo.');
|
|
2893
|
+
out.push(' Generated by `npx thumbgate brain`. Rebuild after capturing feedback. -->');
|
|
2894
|
+
out.push('');
|
|
2895
|
+
out.push('> Institutional memory for AI coding agents working here: what has been');
|
|
2896
|
+
out.push('> learned, what to avoid, and what is enforced. Read this first.');
|
|
2897
|
+
out.push('');
|
|
2898
|
+
|
|
2899
|
+
out.push('## What this codebase taught its agents (lessons)');
|
|
2900
|
+
out.push('');
|
|
2901
|
+
const lessons = (model.lessons && model.lessons.lessons) || [];
|
|
2902
|
+
if (!lessons.length) {
|
|
2903
|
+
out.push('_No lessons captured yet. Run `npx thumbgate capture --feedback=down --context="what failed"`._');
|
|
2904
|
+
} else {
|
|
2905
|
+
for (const l of lessons) {
|
|
2906
|
+
const sig = String(l.signal || '').toLowerCase();
|
|
2907
|
+
const mark = (sig.includes('positive') || sig === 'up') ? '✅' : ((sig.includes('negative') || sig === 'down') ? '⛔' : '•');
|
|
2908
|
+
const enforced = l.confidence === 'active' ? ' _(enforced)_' : '';
|
|
2909
|
+
const ctx = String(l.context || '').replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
2910
|
+
if (ctx) out.push(`- ${mark} ${ctx}${enforced}`);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
out.push('');
|
|
2914
|
+
|
|
2915
|
+
out.push('## Guardrails — do NOT repeat these (prevention rules)');
|
|
2916
|
+
out.push('');
|
|
2917
|
+
const rules = (model.rules && model.rules.rules) || [];
|
|
2918
|
+
if (!rules.length) {
|
|
2919
|
+
out.push('_No prevention rules yet._');
|
|
2920
|
+
} else {
|
|
2921
|
+
for (const r of rules) {
|
|
2922
|
+
out.push(`- **${String(r.title || '').trim()}**`);
|
|
2923
|
+
const body = String(r.body || '').split('\n')[0].trim();
|
|
2924
|
+
if (body && body !== r.title) out.push(` - ${body.slice(0, 200)}`);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
out.push('');
|
|
2928
|
+
|
|
2929
|
+
out.push('## Active enforcement (gates)');
|
|
2930
|
+
out.push('');
|
|
2931
|
+
const gates = (model.gates && model.gates.gates) || [];
|
|
2932
|
+
if (!gates.length) {
|
|
2933
|
+
out.push('_No active gates._');
|
|
2934
|
+
} else {
|
|
2935
|
+
for (const g of gates) {
|
|
2936
|
+
const occ = g.occurrences ? ` (${g.occurrences}×)` : '';
|
|
2937
|
+
out.push(`- \`${g.pattern || g.id}\` → **${g.action}**${occ}`);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
out.push('');
|
|
2941
|
+
|
|
2942
|
+
out.push('## Project context');
|
|
2943
|
+
out.push('');
|
|
2944
|
+
out.push(`- Repo root: \`${model.project.root}\``);
|
|
2945
|
+
const files = model.project.instructionFiles;
|
|
2946
|
+
out.push(`- Agent instruction files: ${files.length ? files.map((f) => '`' + f + '`').join(', ') : '_none detected_'}`);
|
|
2947
|
+
out.push('');
|
|
2948
|
+
out.push('---');
|
|
2949
|
+
const lt = (model.lessons && model.lessons.total) || 0;
|
|
2950
|
+
const rt = (model.rules && model.rules.total) || 0;
|
|
2951
|
+
const gt = (model.gates && model.gates.total) || 0;
|
|
2952
|
+
out.push(`_${lt} lessons · ${rt} rules · ${gt} gates. Rebuild with \`npx thumbgate brain --write\`._`);
|
|
2953
|
+
out.push('');
|
|
2954
|
+
return out.join('\n');
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
function cmdBrain(args = {}) {
|
|
2958
|
+
const model = buildBrainModel({ limit: args.limit });
|
|
2959
|
+
if (args.json) { console.log(JSON.stringify(model, null, 2)); return 0; }
|
|
2960
|
+
const md = renderBrainMarkdown(model);
|
|
2961
|
+
if (args.write) {
|
|
2962
|
+
const dir = path.join(CWD, '.thumbgate');
|
|
2963
|
+
const target = path.join(dir, 'BRAIN.md');
|
|
2964
|
+
try {
|
|
2965
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2966
|
+
fs.writeFileSync(target, md);
|
|
2967
|
+
} catch (err) {
|
|
2968
|
+
// Surface a clean, actionable error instead of an uncaught stack trace
|
|
2969
|
+
// (e.g. permission denied, read-only filesystem).
|
|
2970
|
+
console.error(`Could not write ${target}: ${err && err.message ? err.message : err}`);
|
|
2971
|
+
return 1;
|
|
2972
|
+
}
|
|
2973
|
+
const lt = (model.lessons && model.lessons.total) || 0;
|
|
2974
|
+
const rt = (model.rules && model.rules.total) || 0;
|
|
2975
|
+
const gt = (model.gates && model.gates.total) || 0;
|
|
2976
|
+
console.log(`\u{1f9e0} Wrote context brain to .thumbgate/BRAIN.md (${lt} lessons · ${rt} rules · ${gt} gates).`);
|
|
2977
|
+
console.log(' Point your agent at it: add "Read .thumbgate/BRAIN.md first" to CLAUDE.md / AGENTS.md.');
|
|
2978
|
+
return 0;
|
|
2979
|
+
}
|
|
2980
|
+
process.stdout.write(md);
|
|
2981
|
+
return 0;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2623
2984
|
switch (COMMAND) {
|
|
2624
2985
|
case '--version':
|
|
2625
2986
|
case '-v':
|
|
@@ -2666,6 +3027,16 @@ switch (COMMAND) {
|
|
|
2666
3027
|
capture();
|
|
2667
3028
|
upgradeNudge();
|
|
2668
3029
|
break;
|
|
3030
|
+
case 'feedback-self-test':
|
|
3031
|
+
case 'dogfood':
|
|
3032
|
+
feedbackSelfTest();
|
|
3033
|
+
break;
|
|
3034
|
+
case 'setup-vertex':
|
|
3035
|
+
setupVertex().catch((err) => {
|
|
3036
|
+
console.error(err && err.message ? err.message : err);
|
|
3037
|
+
process.exit(1);
|
|
3038
|
+
});
|
|
3039
|
+
break;
|
|
2669
3040
|
case 'stats':
|
|
2670
3041
|
stats();
|
|
2671
3042
|
upgradeNudge();
|
|
@@ -2685,6 +3056,11 @@ switch (COMMAND) {
|
|
|
2685
3056
|
// a test runner stubs process.exit (flagged by gitar-bot on PR #2281).
|
|
2686
3057
|
break;
|
|
2687
3058
|
}
|
|
3059
|
+
case 'brain': {
|
|
3060
|
+
const brainArgs = parseArgs(process.argv.slice(3));
|
|
3061
|
+
process.exit(cmdBrain(brainArgs));
|
|
3062
|
+
break;
|
|
3063
|
+
}
|
|
2688
3064
|
case 'billing:setup':
|
|
2689
3065
|
require(path.join(PKG_ROOT, 'scripts', 'billing-setup'));
|
|
2690
3066
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.2",
|
|
4
4
|
"description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 36 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
|
|
5
5
|
"homepage": "https://thumbgate.ai",
|
|
6
6
|
"repository": {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"scripts/access-anomaly-detector.js",
|
|
19
|
+
"scripts/action-receipts.js",
|
|
19
20
|
"scripts/activation-tracker.js",
|
|
20
21
|
"scripts/agent-audit-trace.js",
|
|
21
22
|
"scripts/agent-design-governance.js",
|
|
@@ -132,7 +133,10 @@
|
|
|
132
133
|
"scripts/multimodal-retrieval-plan.js",
|
|
133
134
|
"scripts/native-messaging-audit.js",
|
|
134
135
|
"scripts/natural-language-harness.js",
|
|
136
|
+
"scripts/noop-detect.js",
|
|
135
137
|
"scripts/obsidian-export.js",
|
|
138
|
+
"scripts/operational-dashboard.js",
|
|
139
|
+
"scripts/operational-summary.js",
|
|
136
140
|
"scripts/operational-integrity.js",
|
|
137
141
|
"scripts/oss-pr-opportunity-scout.js",
|
|
138
142
|
"scripts/otel-declarative-config.js",
|
|
@@ -156,6 +160,7 @@
|
|
|
156
160
|
"scripts/rag-precision-guardrails.js",
|
|
157
161
|
"scripts/rate-limiter.js",
|
|
158
162
|
"scripts/reasoning-efficiency-guardrails.js",
|
|
163
|
+
"scripts/repeat-metric.js",
|
|
159
164
|
"scripts/reward-hacking-guardrails.js",
|
|
160
165
|
"scripts/risk-scorer.js",
|
|
161
166
|
"scripts/rlaif-self-audit.js",
|
|
@@ -343,7 +348,7 @@
|
|
|
343
348
|
"social:prospect:bluesky:dry": "node scripts/social-bluesky-prospecting.js --dry-run",
|
|
344
349
|
"social:reply-publish:bluesky:dry": "node scripts/social-reply-monitor-bluesky.js --publish-approved --dry-run",
|
|
345
350
|
"test:python": "python3 -m pytest tests/*.py",
|
|
346
|
-
"test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:mcp-tool-annotations && npm run test:mcp-oauth && npm run test:mcp-oauth-flow && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:lesson-semantic-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:predictive-credible-range && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-archived-product-guard && npm run test:postgres-guard && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export && npm run test:stripe-checkout-diagnostic && npm run test:stripe-business-identity-probe && npm run test:revenue-observability-doctor && npm run test:public-bundle-ratchet && npm run test:stripe-payment-link-update && npm run test:ci-cd-hygiene-audit && npm run test:verify-marketing-pages-deployed && npm run test:install-email-capture && npm run test:install-shim && npm run test:hook-runtime-subcommands && npm run test:implementation-notes && npm run test:daily-block-cap && npm run test:free-to-paid-conversion-units && npm run test:metrics-real-endpoint && npm run test:cli-trial-and-help && npm run test:cost-cli && npm run test:silent-failure-cluster && npm run test:proof:truth && node --test tests/adaptive-reliability.test.js && npm run test:mcp-oauth-reviewer",
|
|
351
|
+
"test": "npm run test:python && npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:operational-integrity && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher && npm run test:autoresearch && npm run test:ops && npm run test:session-analyzer && npm run test:tessl && npm run test:gates && npm run test:evoskill && npm run test:gates-hardening && npm run test:workers && npm run test:social-analytics && npm run test:memalign && npm run test:xmemory-lite && npm run test:filesystem-search && npm run test:zernio && npm run test:platform-limits && npm run test:post-video && npm run test:post-everywhere-instagram && npm run test:post-everywhere-channels && npm run test:post-everywhere-zernio-default && npm run test:zernio-canonical-pollers && npm run test:zernio-status && npm run test:obsidian-export && npm run test:lesson-db && npm run test:lesson-rotation && npm run test:memory-dedup && npm run test:feedback-quality && npm run test:sync-version && npm run test:check-congruence && npm run test:tool-registry && npm run test:repeat-metric && npm run test:noop-detect && npm run test:action-receipts && npm run test:feedback-to-rules && npm run test:memory-firewall && npm run test:memory-scope-readiness && npm run test:belief-update && npm run test:hosted-config && npm run test:operational-summary && npm run test:operational-dashboard && npm run test:operator-artifacts && npm run test:operator-key-auth && npm run test:cloudflare-sandbox && npm run test:mcp-config && npm run test:mcp-tool-annotations && npm run test:mcp-oauth && npm run test:mcp-oauth-flow && npm run test:plan-gate && npm run test:pulse && npm run test:semantic-layer && npm run test:data-pipeline && npm run test:optimize-context && npm run test:principle-extractor && npm run test:analytics-window && npm run test:funnel-analytics && npm run test:experiment-tracker && npm run test:build-metadata && npm run test:context-engine && npm run test:hf-papers && npm run test:marketing-experiment && npm run test:seo-gsd && npm run test:verify-run && npm run test:export-dpo-pairs && npm run test:export-hf-dataset && npm run test:license && npm run test:bot-detector && npm run test:audit-pr-bot-contamination && npm run test:stripe-bootstrap-saas-catalog && npm run test:postinstall && npm run test:funnel-invariants && npm run test:cli-telemetry && npm run test:pro-parity && npm run test:model-tier-router && npm run test:computer-use-firewall && npm run test:skill-exporter && npm run test:statusline && npm run test:evolution && npm run test:org-dashboard && npm run test:multi-hop-recall && npm run test:synthetic-dpo && npm run test:thumbgate-skill && npm run test:learn-hub && npm run test:feedback-fallback && npm run test:metaclaw && npm run test:server-lock && npm run test:control-tower && npm run test:pii-scanner && npm run test:data-governance && npm run test:lesson-inference && npm run test:semantic-dedup && npm run test:fs-utils && npm run test:cli-schema && npm run test:explore && npm run test:lesson-reranker && npm run test:lesson-retrieval && npm run test:lesson-semantic-retrieval && npm run test:cross-encoder && npm run test:reflector-agent && npm run test:feedback-session && npm run test:feedback-history-distiller && npm run test:hallucination-detector && npm run test:history-distiller && npm run test:predictive-insights && npm run test:predictive-credible-range && npm run test:prove-predictive-insights && npm run test:statusbar-cli && npm run test:generate-instagram-card && npm run test:instagram-thumbgate-post && npm run test:publish-instagram-thumbgate && npm run test:lesson-synthesis && npm run test:lesson-canonical && npm run test:background-governance && npm run test:memory-migration && npm run test:prompt-dlp && npm run test:ephemeral-store && npm run test:agent-security && npm run test:skill-progressive && npm run test:per-step-scoring && npm run test:weekly-auto-post && npm run test:social-post-hourly && npm run test:social-quality-gate && npm run test:a2ui-engine && npm run test:gate-satisfy && npm run test:money-watcher && npm run test:budget && npm run test:quick-start && npm run test:utm && npm run test:product-feedback && npm run test:feedback-root-consolidator && npm run test:engagement-audit && npm run test:install-growth-automation && npm run test:publish-thumbgate-launch && npm run test:community-course-platform-launch-kit && npm run test:reconcile-thumbgate-campaign && npm run test:reddit-publisher && npm run test:schedule-thumbgate-campaign && npm run test:social-reply-monitor && npm run test:social-dedupe-cleanup && npm run test:sync-launch-assets && npm run test:ai-search-visibility && npm run test:perplexity && npm run test:security-scanner && npm run test:llm-client && npm run test:managed-lesson-agent && npm run test:self-distill && npm run test:meta-agent && npm run test:harness-selector && npm run test:thumbgate-bench && npm run test:seo-guides && npm run test:enforcement-loop && npm run test:cli-agent-experience && npm run test:bot-detection && npm run test:checkout-archived-product-guard && npm run test:postgres-guard && npm run test:checkout-bot-guard && npm run test:checkout-pro-confirmation-gate && npm run test:session-health && npm run test:session-episodes && npm run test:spec-gate && npm run test:decision-trace && npm run test:dashboard-insights && npm run test:telemetry-tracked-link-slug && npm run test:prompt-eval && npm run test:demo-voiceover && npm run test:gate-coherence && npm run test:gate-eval && npm run test:high-roi && npm run test:public-static-assets && npm run test:token-savings && npm run test:numbers-page && npm run test:workflow-gate-checkpoint && npm run test:lesson-export-import && npm run test:landing-page-claims && npm run test:competitive-positioning-marketing && npm run test:medium-weekly && npm run test:dashboard-deeplink-e2e && npm run test:public-package-parity && npm run test:token-savings-dashboard && npm run test:cursor-wiring && npm run test:pretooluse-injection && npm run test:recent-corrective-context && npm run test:durability-step && npm run test:mailer && npm run test:brand-assets && npm run test:enforcement-teeth && npm run test:bayes-optimal-gate && npm run test:swarm-coordinator && npm run test:session-report && npm run test:agent-reasoning-traces && npm run test:judge-reward && npm run test:llm-behavior-monitor && npm run test:prompting-os && npm run test:single-use-credential-gate && npm run test:structured-prompt-driven && npm run test:require-evidence-gate && npm run test:rule-validator && npm run test:bluesky-atproto && npm run test:social-reply-monitor-bluesky && npm run test:bluesky-delete-replies && npm run test:architect-kit-memory-bridge && npm run test:sonar-review-hotspots && npm run test:actionable-remediations && npm run test:gemini-embedding-policy && npm run test:agent-design-governance && npm run test:public-core-boundary && npm run test:hook-stop-verify-deploy && npm run test:hook-stop-anti-claim && npm run test:plausible-server-events && npm run test:activation-tracker && npm run test:unified-revenue-rollup && npm run test:conversion-rate-stats && npm run test:external-customer-audit && npm run test:telemetry-export && npm run test:stripe-checkout-diagnostic && npm run test:stripe-business-identity-probe && npm run test:revenue-observability-doctor && npm run test:public-bundle-ratchet && npm run test:stripe-payment-link-update && npm run test:ci-cd-hygiene-audit && npm run test:verify-marketing-pages-deployed && npm run test:install-email-capture && npm run test:install-shim && npm run test:hook-runtime-subcommands && npm run test:implementation-notes && npm run test:daily-block-cap && npm run test:free-to-paid-conversion-units && npm run test:metrics-real-endpoint && npm run test:cli-trial-and-help && npm run test:cost-cli && npm run test:silent-failure-cluster && npm run test:proof:truth && node --test tests/adaptive-reliability.test.js && npm run test:mcp-oauth-reviewer && npm run test:dfcx-gate && npm run test:dfcx-gate-server && npm run test:vertex-scorer",
|
|
347
352
|
"test:hook-stop-verify-deploy": "node --test tests/hook-stop-verify-deploy.test.js",
|
|
348
353
|
"test:hook-stop-anti-claim": "node --test tests/hook-stop-anti-claim.test.js",
|
|
349
354
|
"test:plausible-server-events": "node --test tests/plausible-server-events.test.js tests/plausible-poller.test.js",
|
|
@@ -396,6 +401,12 @@
|
|
|
396
401
|
"test:sync-version": "node --test tests/sync-version.test.js",
|
|
397
402
|
"test:check-congruence": "node --test tests/check-congruence.test.js",
|
|
398
403
|
"test:tool-registry": "node --test tests/tool-registry.test.js",
|
|
404
|
+
"test:dfcx-gate": "node --test tests/dfcx-webhook-gate.test.js",
|
|
405
|
+
"test:dfcx-gate-server": "node --test tests/dfcx-gate-server.test.js",
|
|
406
|
+
"test:vertex-scorer": "node --test tests/vertex-scorer.test.js",
|
|
407
|
+
"test:repeat-metric": "node --test tests/repeat-metric.test.js",
|
|
408
|
+
"test:noop-detect": "node --test tests/noop-detect.test.js",
|
|
409
|
+
"test:action-receipts": "node --test tests/action-receipts.test.js",
|
|
399
410
|
"test:learn-hub": "node --test tests/learn-hub.test.js",
|
|
400
411
|
"test:feedback-to-rules": "node --test tests/feedback-to-rules.test.js",
|
|
401
412
|
"test:memory-firewall": "node --test tests/memory-firewall.test.js",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
<tr><td>Allocate spend to teams / features</td><td>✅</td><td>Per-gate breakdown via <code>byGate</code></td></tr>
|
|
107
107
|
<tr><td>Stop a known-bad tool call before it hits the model</td><td>❌</td><td>✅ — PreToolUse gate fires, no API call made</td></tr>
|
|
108
108
|
<tr><td>Promote a one-off failure into a permanent gate</td><td>❌</td><td>✅ — feedback loop + lesson DB</td></tr>
|
|
109
|
+
<tr><td>Surface gate candidates from <em>silent</em> failures (where no human ever gave thumbs-down)</td><td>❌</td><td>✅ — unsupervised silent-failure clustering, on by default</td></tr>
|
|
109
110
|
<tr><td>Print conservative $ saved per day</td><td>❌</td><td>✅ — <code>thumbgate cost</code></td></tr>
|
|
110
111
|
<tr><td>K8s pod-level allocation, finance-grade reporting</td><td>✅ (that's their core)</td><td>❌ (not our layer)</td></tr>
|
|
111
112
|
</tbody>
|
|
@@ -117,6 +118,7 @@
|
|
|
117
118
|
<li><strong>Every block is one fewer round trip.</strong> A blocked tool call doesn't reach the model. There's no "ThumbGate intercepted but the request still cost you" — the agent's tool-call execution is replaced with the gate's verdict, and the agent's next reasoning step takes the verdict as context instead of the failed result.</li>
|
|
118
119
|
<li><strong>The avoided retry loop is the bulk of the saving.</strong> Failed tool calls don't just cost the call — they cost the model's next reasoning turn (which sees the failure and tries again), and often a third turn (which tries a different approach). Conservative 2k input + 600 output assumes one retry; in practice it's often more.</li>
|
|
119
120
|
<li><strong>The numbers come from your local <code>gate-stats.json</code>.</strong> Not from a marketing model, not from "what enterprises like you saved." Your machine, your gates, your blocks.</li>
|
|
121
|
+
<li><strong>You don't have to give thumbs-down for the system to learn.</strong> ThumbGate's unsupervised <em>silent-failure clustering</em> runs by default — it mines your conversation logs for tool calls that failed (non-zero exit code or matched error patterns) but never got an explicit thumbs-down, clusters them by tool + argument shape, and surfaces them as gate candidates. The same false-positive-rate guardrail that vets human-feedback candidates vets these. Lazy users still get the savings. Opt out with <code>THUMBGATE_SILENT_FAILURE_CLUSTERING=0</code> if you want HITL-only.</li>
|
|
120
122
|
</ol>
|
|
121
123
|
|
|
122
124
|
<h2>Get the number on your machine</h2>
|