thumbgate 1.26.2 β 1.26.4
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/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +2 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +138 -18
- package/config/gates/default.json +11 -2
- package/package.json +2 -4
- package/public/index.html +2 -2
- package/public/numbers.html +2 -2
- package/scripts/agent-readiness.js +20 -3
- package/scripts/cli-feedback.js +4 -2
- package/scripts/commercial-offer.js +0 -3
- package/scripts/feedback-quality.js +87 -0
- package/scripts/gates-engine.js +183 -18
- package/scripts/install-shim.js +14 -11
- package/src/api/server.js +8 -47
- package/scripts/operational-dashboard.js +0 -160
- package/scripts/operational-summary.js +0 -178
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate-marketplace",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.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.26.
|
|
17
|
+
"version": "1.26.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.26.
|
|
4
|
+
"version": "1.26.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky",
|
|
7
7
|
"email": "ig5973700@gmail.com",
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.26.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.26.0", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.26.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.26.0", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -77,6 +77,7 @@ const {
|
|
|
77
77
|
recordActionAttempt,
|
|
78
78
|
isRepeatAttempt,
|
|
79
79
|
} = require('../../scripts/noop-detect');
|
|
80
|
+
const { recordAuditEvent } = require('../../scripts/audit-trail');
|
|
80
81
|
const {
|
|
81
82
|
recordReceipt,
|
|
82
83
|
getReceiptForAction,
|
|
@@ -230,7 +231,7 @@ const {
|
|
|
230
231
|
finalizeSession: finalizeFeedbackSession,
|
|
231
232
|
} = require('../../scripts/feedback-session');
|
|
232
233
|
|
|
233
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.26.
|
|
234
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.26.0' };
|
|
234
235
|
const COMMERCE_CATEGORIES = [
|
|
235
236
|
'product_recommendation',
|
|
236
237
|
'brand_compliance',
|
package/bin/cli.js
CHANGED
|
@@ -239,6 +239,21 @@ function parseArgs(argv) {
|
|
|
239
239
|
return args;
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function parseTtlMs(value, fallbackMs = 5 * 60 * 1000) {
|
|
243
|
+
if (value === undefined || value === null || value === true || value === '') return fallbackMs;
|
|
244
|
+
const raw = String(value).trim().toLowerCase();
|
|
245
|
+
const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
|
|
246
|
+
if (!match) return fallbackMs;
|
|
247
|
+
const amount = Number(match[1]);
|
|
248
|
+
if (!Number.isFinite(amount) || amount <= 0) return fallbackMs;
|
|
249
|
+
const unit = match[2] || 'ms';
|
|
250
|
+
const factor = unit === 'h' ? 60 * 60 * 1000
|
|
251
|
+
: unit === 'm' ? 60 * 1000
|
|
252
|
+
: unit === 's' ? 1000
|
|
253
|
+
: 1;
|
|
254
|
+
return Math.round(amount * factor);
|
|
255
|
+
}
|
|
256
|
+
|
|
242
257
|
function readStdinText() {
|
|
243
258
|
try {
|
|
244
259
|
return fs.readFileSync(0, 'utf8');
|
|
@@ -1003,10 +1018,21 @@ function capture() {
|
|
|
1003
1018
|
}
|
|
1004
1019
|
|
|
1005
1020
|
let signal = (args.feedback || '').toLowerCase();
|
|
1021
|
+
let consumedSignalArgs = 0;
|
|
1006
1022
|
if (!signal && positionalArgs[0]) {
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1023
|
+
const { detectFeedbackSignal } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
1024
|
+
const oneWord = positionalArgs[0];
|
|
1025
|
+
const twoWords = positionalArgs.slice(0, 2).join(' ');
|
|
1026
|
+
const detected = detectFeedbackSignal(twoWords) || detectFeedbackSignal(oneWord);
|
|
1027
|
+
if (detected) {
|
|
1028
|
+
signal = detected.signal;
|
|
1029
|
+
consumedSignalArgs = detectFeedbackSignal(twoWords) ? Math.min(2, positionalArgs.length) : 1;
|
|
1030
|
+
} else {
|
|
1031
|
+
const firstPos = positionalArgs[0].toLowerCase();
|
|
1032
|
+
if (['up', 'down', 'thumbsup', 'thumbsdown', 'thumbs_up', 'thumbs_down', 'positive', 'negative'].some(v => firstPos.includes(v))) {
|
|
1033
|
+
signal = firstPos;
|
|
1034
|
+
consumedSignalArgs = 1;
|
|
1035
|
+
}
|
|
1010
1036
|
}
|
|
1011
1037
|
}
|
|
1012
1038
|
|
|
@@ -1026,19 +1052,25 @@ function capture() {
|
|
|
1026
1052
|
}
|
|
1027
1053
|
|
|
1028
1054
|
let context = args.context || '';
|
|
1029
|
-
if (!context &&
|
|
1055
|
+
if (!context && consumedSignalArgs > 0) {
|
|
1056
|
+
context = positionalArgs.slice(consumedSignalArgs).join(' ');
|
|
1057
|
+
} else if (!context && positionalArgs[1]) {
|
|
1030
1058
|
context = positionalArgs[1];
|
|
1031
1059
|
}
|
|
1032
1060
|
|
|
1033
1061
|
let whatWentWrong = args['what-went-wrong'];
|
|
1034
|
-
if (!whatWentWrong && positionalArgs
|
|
1062
|
+
if (!whatWentWrong && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 1) {
|
|
1063
|
+
whatWentWrong = positionalArgs.slice(consumedSignalArgs + 1).join(' ');
|
|
1064
|
+
} else if (!whatWentWrong && positionalArgs[2]) {
|
|
1035
1065
|
whatWentWrong = positionalArgs[2];
|
|
1036
1066
|
} else if (!whatWentWrong && normalized === 'down' && context) {
|
|
1037
1067
|
whatWentWrong = context;
|
|
1038
1068
|
}
|
|
1039
1069
|
|
|
1040
1070
|
let whatToChange = args['what-to-change'];
|
|
1041
|
-
if (!whatToChange && positionalArgs
|
|
1071
|
+
if (!whatToChange && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 2) {
|
|
1072
|
+
whatToChange = positionalArgs.slice(consumedSignalArgs + 2).join(' ');
|
|
1073
|
+
} else if (!whatToChange && positionalArgs[3]) {
|
|
1042
1074
|
whatToChange = positionalArgs[3];
|
|
1043
1075
|
} else if (!whatToChange && normalized === 'down' && context) {
|
|
1044
1076
|
whatToChange = `avoid: ${context}`;
|
|
@@ -2375,6 +2407,43 @@ function optimize() {
|
|
|
2375
2407
|
doOptimize();
|
|
2376
2408
|
}
|
|
2377
2409
|
|
|
2410
|
+
function cleanup() {
|
|
2411
|
+
console.log('Cleaning up ThumbGate processes...');
|
|
2412
|
+
try {
|
|
2413
|
+
const { execSync } = require('child_process');
|
|
2414
|
+
// Kill all 'thumbgate serve' and 'thumbgate dashboard' processes except this one
|
|
2415
|
+
const pids = execSync("ps aux | grep 'thumbgate' | grep -v 'grep' | awk '{print $2}'", { encoding: 'utf8' })
|
|
2416
|
+
.split('\n')
|
|
2417
|
+
.filter(Boolean)
|
|
2418
|
+
.map(Number)
|
|
2419
|
+
.filter(pid => pid !== process.pid);
|
|
2420
|
+
|
|
2421
|
+
if (pids.length > 0) {
|
|
2422
|
+
console.log(`Killing ${pids.length} process(es): ${pids.join(', ')}`);
|
|
2423
|
+
pids.forEach(pid => {
|
|
2424
|
+
try { process.kill(pid, 'SIGTERM'); } catch (_) {}
|
|
2425
|
+
});
|
|
2426
|
+
// Give them a moment to die
|
|
2427
|
+
execSync('sleep 1');
|
|
2428
|
+
} else {
|
|
2429
|
+
console.log('No other ThumbGate processes found.');
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// Check port 3456 specifically
|
|
2433
|
+
try {
|
|
2434
|
+
const portPid = execSync("lsof -ti :3456", { encoding: 'utf8' }).trim();
|
|
2435
|
+
if (portPid) {
|
|
2436
|
+
console.log(`Killing process ${portPid} holding port 3456`);
|
|
2437
|
+
try { process.kill(Number(portPid), 'SIGKILL'); } catch (_) {}
|
|
2438
|
+
}
|
|
2439
|
+
} catch (_) { /* port already free */ }
|
|
2440
|
+
|
|
2441
|
+
console.log('β
Cleanup complete. Run "npx thumbgate pro" to restart the dashboard.');
|
|
2442
|
+
} catch (err) {
|
|
2443
|
+
console.error(`Cleanup failed: ${err.message}`);
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2378
2447
|
function serve() {
|
|
2379
2448
|
try {
|
|
2380
2449
|
const { repairCodexHooks } = require(path.join(PKG_ROOT, 'scripts', 'codex-self-heal'));
|
|
@@ -2411,11 +2480,21 @@ function install() {
|
|
|
2411
2480
|
}
|
|
2412
2481
|
|
|
2413
2482
|
async function gateCheck() {
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2483
|
+
try {
|
|
2484
|
+
const payload = readStdinText();
|
|
2485
|
+
const input = payload ? JSON.parse(payload) : {};
|
|
2486
|
+
const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
2487
|
+
const output = await gatesEngine.runAsync(input);
|
|
2488
|
+
process.stdout.write(output + '\n');
|
|
2489
|
+
} catch (err) {
|
|
2490
|
+
process.stderr.write(`gate-check error: ${err.message}\n`);
|
|
2491
|
+
process.stdout.write(JSON.stringify({
|
|
2492
|
+
hookSpecificOutput: {
|
|
2493
|
+
hookEventName: 'PreToolUse',
|
|
2494
|
+
additionalContext: `[ThumbGate Error] ${err.message}`,
|
|
2495
|
+
}
|
|
2496
|
+
}) + '\n');
|
|
2497
|
+
}
|
|
2419
2498
|
}
|
|
2420
2499
|
|
|
2421
2500
|
function cacheUpdate() {
|
|
@@ -2442,9 +2521,14 @@ function statuslineRender() {
|
|
|
2442
2521
|
|
|
2443
2522
|
function hookAutoCapture() {
|
|
2444
2523
|
syncActiveProjectContext();
|
|
2445
|
-
const prompt = process.env.CLAUDE_USER_PROMPT
|
|
2524
|
+
const prompt = process.env.CLAUDE_USER_PROMPT
|
|
2525
|
+
|| process.env.THUMBGATE_USER_PROMPT
|
|
2526
|
+
|| process.env.CODEX_USER_PROMPT
|
|
2527
|
+
|| process.env.USER_PROMPT
|
|
2528
|
+
|| readStdinText().trim();
|
|
2446
2529
|
const { evaluatePromptGuard } = require(path.join(PKG_ROOT, 'scripts', 'prompt-guard'));
|
|
2447
2530
|
const { processInlineFeedback, formatCliOutput } = require(path.join(PKG_ROOT, 'scripts', 'cli-feedback'));
|
|
2531
|
+
const { detectFeedbackSignal } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
2448
2532
|
const { loadOptionalModule } = require(path.join(PKG_ROOT, 'scripts', 'private-core-boundary'));
|
|
2449
2533
|
const { recordConversationEntry, readRecentConversationWindow } = loadOptionalModule(
|
|
2450
2534
|
path.join(PKG_ROOT, 'scripts', 'feedback-history-distiller'),
|
|
@@ -2466,14 +2550,12 @@ function hookAutoCapture() {
|
|
|
2466
2550
|
return;
|
|
2467
2551
|
}
|
|
2468
2552
|
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
const isDown = /(thumbs?\s*down|that failed|that was wrong|fix this)/i.test(lower);
|
|
2472
|
-
if (!isUp && !isDown) {
|
|
2553
|
+
const detected = detectFeedbackSignal(prompt);
|
|
2554
|
+
if (!detected) {
|
|
2473
2555
|
return;
|
|
2474
2556
|
}
|
|
2475
2557
|
|
|
2476
|
-
const signal =
|
|
2558
|
+
const signal = detected.signal;
|
|
2477
2559
|
const conversationWindow = readRecentConversationWindow({ limit: 8 });
|
|
2478
2560
|
const result = processInlineFeedback({
|
|
2479
2561
|
signal,
|
|
@@ -2673,6 +2755,30 @@ function startApi() {
|
|
|
2673
2755
|
}
|
|
2674
2756
|
}
|
|
2675
2757
|
|
|
2758
|
+
function breakGlass() {
|
|
2759
|
+
const args = parseArgs(process.argv.slice(3));
|
|
2760
|
+
const positionalReason = process.argv.slice(3).find((arg) => !arg.startsWith('--'));
|
|
2761
|
+
const reason = String(args.reason || positionalReason || '').trim();
|
|
2762
|
+
if (!reason) {
|
|
2763
|
+
console.error('Usage: npx thumbgate break-glass --reason "why this recovery is needed" [--ttl=5m] [--json]');
|
|
2764
|
+
process.exit(1);
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
const ttlMs = parseTtlMs(args.ttl, 5 * 60 * 1000);
|
|
2768
|
+
const { breakGlassEmergency } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
2769
|
+
const result = breakGlassEmergency({ reason, ttlMs });
|
|
2770
|
+
if (args.json) {
|
|
2771
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2772
|
+
return;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
console.log('ThumbGate break-glass active.');
|
|
2776
|
+
console.log(` Reason : ${result.reason}`);
|
|
2777
|
+
console.log(` Expires : ${result.expiresAt}`);
|
|
2778
|
+
console.log(' Unlocked : hook settings edits, pr_create_allowed, pr_threads_checked');
|
|
2779
|
+
console.log(' Still gated: local-only scope, force-push, protected branch push, unsafe chmod, broad rm -rf');
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2676
2782
|
function help() {
|
|
2677
2783
|
const v = pkgVersion();
|
|
2678
2784
|
const helpArgs = process.argv.slice(3);
|
|
@@ -2695,6 +2801,7 @@ function help() {
|
|
|
2695
2801
|
console.log(' explore Interactive TUI for lessons, gates, stats');
|
|
2696
2802
|
console.log(' dashboard Open the local ThumbGate dashboard');
|
|
2697
2803
|
console.log(' doctor Audit runtime isolation + bootstrap context');
|
|
2804
|
+
console.log(' break-glass --reason="..." Short TTL recovery if gates over-fire');
|
|
2698
2805
|
console.log(' brain [--write] Build the agent-readable context brain (lessons + rules + gates)');
|
|
2699
2806
|
console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
|
|
2700
2807
|
console.log(' subscribe <email> Get the 5-min setup guide + weekly tips by email');
|
|
@@ -2747,6 +2854,7 @@ function help() {
|
|
|
2747
2854
|
console.log(' default: machine-wide (~/.claude/settings.json β shared dashboard)');
|
|
2748
2855
|
console.log(' --project: per-repo (<cwd>/.claude/settings.json β isolated dashboard)');
|
|
2749
2856
|
console.log(' --no-hooks: MCP only, skip hook wiring');
|
|
2857
|
+
console.log(' break-glass Short TTL recovery if gates over-fire');
|
|
2750
2858
|
console.log(' cfo Hosted billing summary (local fallback JSON)');
|
|
2751
2859
|
console.log(' billing:setup Generate operator key + print Railway setup instructions');
|
|
2752
2860
|
console.log(' repair-github-marketplace Repair legacy GitHub Marketplace amount mappings');
|
|
@@ -2842,6 +2950,11 @@ const SUBCOMMAND_HELP = {
|
|
|
2842
2950
|
lessons: 'Usage: npx thumbgate lessons [--query="..."] [--limit=N]\n\nSearch the lesson database (Pro feature).',
|
|
2843
2951
|
search: 'Usage: npx thumbgate search <query>\n\nSearch ThumbGate knowledge base (Pro feature).',
|
|
2844
2952
|
'gate-check': 'Usage: npx thumbgate gate-check\n\nPreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict.',
|
|
2953
|
+
'break-glass': 'Usage: npx thumbgate break-glass --reason="why" [--ttl=5m] [--json]\n\nShort-lived recovery path for over-firing gates. Allows hook settings edits and satisfies PR-create/thread-check gates without disabling core destructive-action protections.',
|
|
2954
|
+
serve: 'Usage: npx thumbgate serve\n\nStart the MCP stdio server. This is for agent runtimes, not the local HTTP dashboard.',
|
|
2955
|
+
mcp: 'Usage: npx thumbgate mcp\n\nAlias for `thumbgate serve`.',
|
|
2956
|
+
dashboard: 'Usage: npx thumbgate dashboard [--window=today|7d|30d]\n\nPrint the operational dashboard summary. Use `npx thumbgate start-api` for the local HTTP dashboard on :3456.',
|
|
2957
|
+
'start-api': 'Usage: npx thumbgate start-api\n\nStart the local ThumbGate HTTP API/dashboard. Defaults to PORT=8787; use PORT=3456 for statusline localhost links.',
|
|
2845
2958
|
'export-dpo': 'Usage: npx thumbgate export-dpo [--format=jsonl|csv]\n\nExport feedback as DPO training pairs (Pro feature).',
|
|
2846
2959
|
status: 'Usage: npx thumbgate status\n\nShow ThumbGate system health and active configuration.',
|
|
2847
2960
|
watch: 'Usage: npx thumbgate watch\n\nWatch for feedback changes and auto-regenerate prevention rules.',
|
|
@@ -3004,6 +3117,9 @@ switch (COMMAND) {
|
|
|
3004
3117
|
case 'mcp':
|
|
3005
3118
|
serve();
|
|
3006
3119
|
break;
|
|
3120
|
+
case 'cleanup':
|
|
3121
|
+
cleanup();
|
|
3122
|
+
break;
|
|
3007
3123
|
case 'gate-check':
|
|
3008
3124
|
gateCheck().catch((err) => {
|
|
3009
3125
|
console.error(err && err.message ? err.message : err);
|
|
@@ -3058,7 +3174,7 @@ switch (COMMAND) {
|
|
|
3058
3174
|
}
|
|
3059
3175
|
case 'brain': {
|
|
3060
3176
|
const brainArgs = parseArgs(process.argv.slice(3));
|
|
3061
|
-
process.
|
|
3177
|
+
process.exitCode = cmdBrain(brainArgs);
|
|
3062
3178
|
break;
|
|
3063
3179
|
}
|
|
3064
3180
|
case 'billing:setup':
|
|
@@ -3371,6 +3487,10 @@ switch (COMMAND) {
|
|
|
3371
3487
|
case 'status':
|
|
3372
3488
|
status();
|
|
3373
3489
|
break;
|
|
3490
|
+
case 'break-glass':
|
|
3491
|
+
case 'breakglass':
|
|
3492
|
+
breakGlass();
|
|
3493
|
+
break;
|
|
3374
3494
|
case 'funnel':
|
|
3375
3495
|
funnel();
|
|
3376
3496
|
break;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"id": "task-scope-required",
|
|
24
24
|
"layer": "Decisions",
|
|
25
25
|
"toolNames": ["Bash"],
|
|
26
|
-
"pattern": "^(git\\s+(add|commit|push)|gh\\s+pr\\s+(create|merge)|gh\\s+release\\s+create|git\\s+tag\\b|npm\\s+publish|yarn\\s+publish|pnpm\\s+publish)",
|
|
26
|
+
"pattern": "^(git\\s+(add|commit|push)|gh\\s+pr\\s+(create|merge)|gh\\s+api\\b(?=.*(?:/pulls\\b|repos/[^\\s]+/[^\\s]+/pulls\\b))(?=.*(?:-f\\b|--field\\b|-F\\b|--raw-field\\b|--method\\s+POST\\b|-X\\s+POST\\b))|gh\\s+release\\s+create|git\\s+tag\\b|npm\\s+publish|yarn\\s+publish|pnpm\\s+publish)",
|
|
27
27
|
"requireTaskScope": true,
|
|
28
28
|
"action": "block",
|
|
29
29
|
"message": "Git write, PR, release, and publish operations require an explicit task scope.",
|
|
@@ -72,6 +72,15 @@
|
|
|
72
72
|
"message": "PR creation requires explicit 'pr_create_allowed' satisfaction with evidence of user permission.",
|
|
73
73
|
"severity": "high"
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
"id": "gh-api-pr-create-restricted",
|
|
77
|
+
"layer": "Identity",
|
|
78
|
+
"pattern": "gh\\s+api\\b(?=.*(?:/pulls\\b|repos/[^\\s]+/[^\\s]+/pulls\\b))(?=.*(?:-f\\b|--field\\b|-F\\b|--raw-field\\b|--method\\s+POST\\b|-X\\s+POST\\b))",
|
|
79
|
+
"action": "block",
|
|
80
|
+
"unless": "pr_create_allowed",
|
|
81
|
+
"message": "GitHub API PR creation requires explicit 'pr_create_allowed' satisfaction with evidence of user permission. Use the same approval path as gh pr create.",
|
|
82
|
+
"severity": "high"
|
|
83
|
+
},
|
|
75
84
|
{
|
|
76
85
|
"id": "gh-pr-merge-restricted",
|
|
77
86
|
"layer": "Identity",
|
|
@@ -85,7 +94,7 @@
|
|
|
85
94
|
"id": "branch-governance-required",
|
|
86
95
|
"layer": "Decisions",
|
|
87
96
|
"toolNames": ["Bash"],
|
|
88
|
-
"pattern": "^(gh\\s+pr\\s+(create|merge)|gh\\s+release\\s+create|git\\s+tag\\b|npm\\s+publish|yarn\\s+publish|pnpm\\s+publish)",
|
|
97
|
+
"pattern": "^(gh\\s+pr\\s+(create|merge)|gh\\s+api\\b(?=.*(?:/pulls\\b|repos/[^\\s]+/[^\\s]+/pulls\\b))(?=.*(?:-f\\b|--field\\b|-F\\b|--raw-field\\b|--method\\s+POST\\b|-X\\s+POST\\b))|gh\\s+release\\s+create|git\\s+tag\\b|npm\\s+publish|yarn\\s+publish|pnpm\\s+publish)",
|
|
89
98
|
"requireBranchGovernance": true,
|
|
90
99
|
"action": "block",
|
|
91
100
|
"message": "PR, release, and publish actions require explicit branch governance.",
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.4",
|
|
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": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "https://github.com/IgorGanapolsky/ThumbGate.git"
|
|
8
|
+
"url": "git+https://github.com/IgorGanapolsky/ThumbGate.git"
|
|
9
9
|
},
|
|
10
10
|
"bugs": {
|
|
11
11
|
"url": "https://github.com/IgorGanapolsky/ThumbGate/issues"
|
|
@@ -135,8 +135,6 @@
|
|
|
135
135
|
"scripts/natural-language-harness.js",
|
|
136
136
|
"scripts/noop-detect.js",
|
|
137
137
|
"scripts/obsidian-export.js",
|
|
138
|
-
"scripts/operational-dashboard.js",
|
|
139
|
-
"scripts/operational-summary.js",
|
|
140
138
|
"scripts/operational-integrity.js",
|
|
141
139
|
"scripts/oss-pr-opportunity-scout.js",
|
|
142
140
|
"scripts/otel-declarative-config.js",
|
package/public/index.html
CHANGED
|
@@ -20,7 +20,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
20
20
|
<meta property="og:image" content="https://thumbgate.ai/og.png">
|
|
21
21
|
<meta name="twitter:card" content="summary_large_image">
|
|
22
22
|
<meta name="twitter:image" content="https://thumbgate.ai/og.png">
|
|
23
|
-
<meta name="thumbgate-version" content="1.26.
|
|
23
|
+
<meta name="thumbgate-version" content="1.26.0">
|
|
24
24
|
<meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agentic development cycle, AC/DC framework, Guide Generate Verify Solve, agent enforcement layer, save LLM tokens, reduce Claude API cost, reduce OpenAI cost, AI agent token savings, prevent LLM retries, prevent hallucination retries, stop AI token waste, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
|
|
25
25
|
<link rel="canonical" href="__APP_ORIGIN__/">
|
|
26
26
|
<link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
|
|
@@ -1594,7 +1594,7 @@ __GA_BOOTSTRAP__
|
|
|
1594
1594
|
<a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
|
|
1595
1595
|
<a href="/blog">Blog</a>
|
|
1596
1596
|
</div>
|
|
1597
|
-
<span class="footer-copy">Β© 2026 ThumbGate Β· MIT License Β· npm v1.26.
|
|
1597
|
+
<span class="footer-copy">Β© 2026 ThumbGate Β· MIT License Β· npm v1.26.0</span>
|
|
1598
1598
|
</div>
|
|
1599
1599
|
</footer>
|
|
1600
1600
|
|
package/public/numbers.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"alternateName": "thumbgate",
|
|
26
26
|
"applicationCategory": "DeveloperApplication",
|
|
27
27
|
"operatingSystem": "Cross-platform, Node.js >=18.18.0",
|
|
28
|
-
"softwareVersion": "1.26.
|
|
28
|
+
"softwareVersion": "1.26.0",
|
|
29
29
|
"url": "https://thumbgate.ai/numbers",
|
|
30
30
|
"dateModified": "2026-05-07",
|
|
31
31
|
"creator": {
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
<main class="container">
|
|
203
203
|
<h1>The Numbers</h1>
|
|
204
204
|
<p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
|
|
205
|
-
<div class="freshness">Updated: 2026-05-07 Β· Version 1.26.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 Β· Version 1.26.0</div>
|
|
206
206
|
<div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
|
|
207
207
|
|
|
208
208
|
<h2>Gate enforcement</h2>
|
|
@@ -94,9 +94,26 @@ function detectRuntimeIsolation() {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function
|
|
97
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
98
|
+
try {
|
|
99
|
+
let curr = path.resolve(startDir);
|
|
100
|
+
while (true) {
|
|
101
|
+
const indicators = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', '.mcp.json', '.git'];
|
|
102
|
+
if (indicators.some((f) => fs.existsSync(path.join(curr, f)))) {
|
|
103
|
+
return curr;
|
|
104
|
+
}
|
|
105
|
+
const parent = path.dirname(curr);
|
|
106
|
+
if (parent === curr) break;
|
|
107
|
+
curr = parent;
|
|
108
|
+
}
|
|
109
|
+
} catch (_) { /* fallback to startDir */ }
|
|
110
|
+
return startDir;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function collectBootstrapFiles(projectRoot) {
|
|
114
|
+
const effectiveRoot = projectRoot || findProjectRoot();
|
|
98
115
|
const files = BOOTSTRAP_FILES.map((file) => {
|
|
99
|
-
const absolutePath = path.join(
|
|
116
|
+
const absolutePath = path.join(effectiveRoot, file.path);
|
|
100
117
|
return {
|
|
101
118
|
id: file.id,
|
|
102
119
|
path: file.path,
|
|
@@ -118,7 +135,7 @@ function collectBootstrapFiles(projectRoot = PROJECT_ROOT) {
|
|
|
118
135
|
missingRequired,
|
|
119
136
|
recommendation: missingRequired.length === 0
|
|
120
137
|
? 'Bootstrap context is present.'
|
|
121
|
-
: `Add missing bootstrap files: ${missingRequired.join(', ')}`,
|
|
138
|
+
: `Add missing bootstrap files to project root (${effectiveRoot}): ${missingRequired.join(', ')}`,
|
|
122
139
|
};
|
|
123
140
|
}
|
|
124
141
|
|
package/scripts/cli-feedback.js
CHANGED
|
@@ -88,11 +88,13 @@ function formatCliOutput(result) {
|
|
|
88
88
|
if (result.feedbackResult && result.feedbackResult.accepted !== false) {
|
|
89
89
|
lines.push(`${isDown ? R : G}${BD}${isDown ? 'π Thumbs down recorded' : 'π Thumbs up recorded'}${RST}`);
|
|
90
90
|
const feedbackId = (result.feedbackResult.feedbackEvent && result.feedbackResult.feedbackEvent.id) || result.feedbackResult.id;
|
|
91
|
+
const memoryId = (result.feedbackResult.memoryRecord && result.feedbackResult.memoryRecord.id) || result.feedbackResult.memoryId;
|
|
91
92
|
if (feedbackId) {
|
|
92
|
-
lines.push(`${D} ID: ${feedbackId}${RST}`);
|
|
93
|
+
lines.push(`${D} Feedback ID: ${feedbackId}${RST}`);
|
|
94
|
+
if (memoryId) lines.push(`${D} Memory ID : ${memoryId}${RST}`);
|
|
93
95
|
// Echo feedback ID to stderr so it's visible directly in the terminal,
|
|
94
96
|
// not hidden behind Claude Code's "ctrl+o to expand" MCP call collapse.
|
|
95
|
-
process.stderr.write(`β
Feedback captured (${feedbackId})\n`);
|
|
97
|
+
process.stderr.write(`β
Feedback captured (${feedbackId}${memoryId ? `, ${memoryId}` : ''})\n`);
|
|
96
98
|
}
|
|
97
99
|
} else {
|
|
98
100
|
lines.push(`${R}Feedback not accepted: ${(result.feedbackResult && result.feedbackResult.reason) || 'unknown'}${RST}`);
|
|
@@ -68,8 +68,6 @@ function buildCaptureReceipt({ signal, feedbackId, memoryId, actionType } = {})
|
|
|
68
68
|
'',
|
|
69
69
|
` Solo Pro : ${PRO_PRICE_LABEL} for hosted sync, search, dashboard, and exports`,
|
|
70
70
|
` Upgrade : ${trackedProUrl('cli_capture_receipt', actionType || normalizedSignal.toLowerCase())}`,
|
|
71
|
-
` Team path : ${TEAM_PRICE_LABEL}; start with one repeated workflow failure`,
|
|
72
|
-
' https://thumbgate.ai/#workflow-sprint-intake',
|
|
73
71
|
'',
|
|
74
72
|
];
|
|
75
73
|
return lines.join('\n');
|
|
@@ -102,7 +100,6 @@ function buildStatsReceipt(stats = {}) {
|
|
|
102
100
|
lines.push(' Show the buyer : npx thumbgate cost');
|
|
103
101
|
lines.push(' Pro sync value : keep these lessons/rules visible across laptops, CI, containers, and agent runtimes');
|
|
104
102
|
lines.push(` Solo Pro : ${trackedProUrl('cli_stats_receipt', 'proof_seen')}`);
|
|
105
|
-
lines.push(' Team workflow : https://thumbgate.ai/#workflow-sprint-intake');
|
|
106
103
|
lines.push('');
|
|
107
104
|
return lines.join('\n');
|
|
108
105
|
}
|
|
@@ -62,6 +62,92 @@ function normalizeFeedbackText(value) {
|
|
|
62
62
|
.trim();
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function editDistance(a, b) {
|
|
66
|
+
const left = String(a || '');
|
|
67
|
+
const right = String(b || '');
|
|
68
|
+
const dp = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
|
|
69
|
+
for (let i = 0; i <= left.length; i++) dp[i][0] = i;
|
|
70
|
+
for (let j = 0; j <= right.length; j++) dp[0][j] = j;
|
|
71
|
+
for (let i = 1; i <= left.length; i++) {
|
|
72
|
+
for (let j = 1; j <= right.length; j++) {
|
|
73
|
+
const cost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
74
|
+
dp[i][j] = Math.min(
|
|
75
|
+
dp[i - 1][j] + 1,
|
|
76
|
+
dp[i][j - 1] + 1,
|
|
77
|
+
dp[i - 1][j - 1] + cost,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return dp[left.length][right.length];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isNearThumbToken(token) {
|
|
85
|
+
const value = String(token || '');
|
|
86
|
+
if (value.length < 4) return false;
|
|
87
|
+
return editDistance(value, 'thumb') <= 1 || editDistance(value, 'thumbs') <= 2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isNearUpToken(token) {
|
|
91
|
+
const value = String(token || '');
|
|
92
|
+
return value === 'up' || editDistance(value, 'up') <= 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isNearDownToken(token) {
|
|
96
|
+
const value = String(token || '');
|
|
97
|
+
if (value.length < 2) return false;
|
|
98
|
+
return editDistance(value, 'down') <= 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function detectFeedbackSignal(value) {
|
|
102
|
+
const raw = String(value || '');
|
|
103
|
+
if (/[πππ»ππΌππ½ππΎππΏ]/u.test(raw)) return { signal: 'down', confidence: 'emoji', match: 'π' };
|
|
104
|
+
if (/[πππ»ππΌππ½ππΎππΏ]/u.test(raw)) return { signal: 'up', confidence: 'emoji', match: 'π' };
|
|
105
|
+
|
|
106
|
+
const normalized = normalizeFeedbackText(raw);
|
|
107
|
+
if (!normalized) return null;
|
|
108
|
+
|
|
109
|
+
const exactDown = [
|
|
110
|
+
/\bthumbs?\s*down\b/,
|
|
111
|
+
/\bthumbs?down\b/,
|
|
112
|
+
/\bthat failed\b/,
|
|
113
|
+
/\bit failed\b/,
|
|
114
|
+
/\bthat was wrong\b/,
|
|
115
|
+
/\bfix this\b/,
|
|
116
|
+
];
|
|
117
|
+
if (exactDown.some((pattern) => pattern.test(normalized))) {
|
|
118
|
+
return { signal: 'down', confidence: 'exact', match: normalized };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const exactUp = [
|
|
122
|
+
/\bthumbs?\s*up\b/,
|
|
123
|
+
/\bthumbs?up\b/,
|
|
124
|
+
/\bthat worked\b/,
|
|
125
|
+
/\bit worked\b/,
|
|
126
|
+
/\blooks good\b/,
|
|
127
|
+
/\bgood job\b/,
|
|
128
|
+
/\bgood work\b/,
|
|
129
|
+
/\bnice work\b/,
|
|
130
|
+
/\bperfect\b/,
|
|
131
|
+
/\blgtm\b/,
|
|
132
|
+
];
|
|
133
|
+
if (exactUp.some((pattern) => pattern.test(normalized))) {
|
|
134
|
+
return { signal: 'up', confidence: 'exact', match: normalized };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const words = normalized.split(/\s+/).filter(Boolean);
|
|
138
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
139
|
+
if (!isNearThumbToken(words[i])) continue;
|
|
140
|
+
if (isNearDownToken(words[i + 1])) {
|
|
141
|
+
return { signal: 'down', confidence: 'fuzzy', match: `${words[i]} ${words[i + 1]}` };
|
|
142
|
+
}
|
|
143
|
+
if (isNearUpToken(words[i + 1])) {
|
|
144
|
+
return { signal: 'up', confidence: 'fuzzy', match: `${words[i]} ${words[i + 1]}` };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
65
151
|
function isGenericFeedbackText(value, signal) {
|
|
66
152
|
const normalized = normalizeFeedbackText(value);
|
|
67
153
|
if (!normalized) return false;
|
|
@@ -131,6 +217,7 @@ function buildClarificationMessage(params = {}) {
|
|
|
131
217
|
|
|
132
218
|
module.exports = {
|
|
133
219
|
GENERIC_PHRASE_RULES,
|
|
220
|
+
detectFeedbackSignal,
|
|
134
221
|
normalizeFeedbackSignal,
|
|
135
222
|
normalizeFeedbackText,
|
|
136
223
|
isGenericFeedbackText,
|