thumbgate 1.21.1 โ 1.21.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/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +42 -81
- package/package.json +2 -6
- package/public/dashboard.html +3 -3
- package/public/index.html +2 -2
- package/public/lessons.html +0 -22
- package/public/numbers.html +2 -2
- package/scripts/audit.js +65 -0
- package/scripts/cli-schema.js +8 -0
- package/scripts/hook-runtime.js +12 -12
- package/scripts/statusline.sh +41 -30
- package/scripts/workflow-sentinel.js +1 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate-marketplace",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.2",
|
|
4
4
|
"owner": {
|
|
5
5
|
"name": "Igor Ganapolsky",
|
|
6
6
|
"email": "ig5973700@gmail.com"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"source": "npm",
|
|
15
15
|
"package": "thumbgate"
|
|
16
16
|
},
|
|
17
|
-
"version": "1.21.
|
|
17
|
+
"version": "1.21.2",
|
|
18
18
|
"author": {
|
|
19
19
|
"name": "Igor Ganapolsky",
|
|
20
20
|
"email": "ig5973700@gmail.com",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
3
|
"description": "One ๐ becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
|
|
4
|
-
"version": "1.21.
|
|
4
|
+
"version": "1.21.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky",
|
|
7
7
|
"email": "ig5973700@gmail.com",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.2",
|
|
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",
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.21.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.21.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -216,7 +216,7 @@ const {
|
|
|
216
216
|
finalizeSession: finalizeFeedbackSession,
|
|
217
217
|
} = require('../../scripts/feedback-session');
|
|
218
218
|
|
|
219
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.21.
|
|
219
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.21.2' };
|
|
220
220
|
const COMMERCE_CATEGORIES = [
|
|
221
221
|
'product_recommendation',
|
|
222
222
|
'brand_compliance',
|
package/bin/cli.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* npx thumbgate background-governance # background-agent run report + risk check
|
|
24
24
|
* npx thumbgate cfo # local operational billing summary
|
|
25
25
|
* npx thumbgate pro # solo dashboard + exports side lane
|
|
26
|
+
* npx thumbgate audit <file> # audit an agent transcript for repeat-mistake token waste
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
'use strict';
|
|
@@ -40,13 +41,6 @@ const {
|
|
|
40
41
|
resolveMcpEntry,
|
|
41
42
|
} = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
|
|
42
43
|
const { trackEvent } = require(path.join(__dirname, '..', 'scripts', 'cli-telemetry'));
|
|
43
|
-
const {
|
|
44
|
-
cacheUpdateHookCommand,
|
|
45
|
-
preToolHookCommand,
|
|
46
|
-
sessionStartHookCommand,
|
|
47
|
-
statuslineCommand,
|
|
48
|
-
userPromptHookCommand,
|
|
49
|
-
} = require(path.join(__dirname, '..', 'scripts', 'hook-runtime'));
|
|
50
44
|
const {
|
|
51
45
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
52
46
|
PRO_PRICE_LABEL,
|
|
@@ -395,84 +389,24 @@ function whichExists(cmd) {
|
|
|
395
389
|
|
|
396
390
|
function setupClaude() {
|
|
397
391
|
const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code', 'project');
|
|
392
|
+
const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
|
|
393
|
+
const hookResult = wireHooks({ agent: 'claude-code' });
|
|
398
394
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// same hook set. Before this, `install` shipped a Claude config with no
|
|
403
|
-
// PreToolUse gate โ the core ThumbGate feature was silently missing.
|
|
404
|
-
let wireChanged = false;
|
|
405
|
-
try {
|
|
406
|
-
const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
|
|
407
|
-
const wireResult = wireHooks({ agent: 'claude-code' });
|
|
408
|
-
if (wireResult.error) {
|
|
409
|
-
console.log(` Claude Code: ${wireResult.error}`);
|
|
410
|
-
} else if (wireResult.changed) {
|
|
411
|
-
wireChanged = true;
|
|
412
|
-
const lifecycles = [...new Set(wireResult.added.map((h) => h.lifecycle))].join(', ');
|
|
413
|
-
console.log(` Claude Code: wired gate hooks (${lifecycles})`);
|
|
414
|
-
}
|
|
415
|
-
} catch (err) {
|
|
416
|
-
console.log(` Claude Code: hook wiring skipped (${err.message})`);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Upsert Stop hook into .claude/settings.json for autonomous self-scoring
|
|
420
|
-
const settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
421
|
-
const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
|
|
422
|
-
|
|
423
|
-
let settings = { hooks: {} };
|
|
424
|
-
if (fs.existsSync(settingsPath)) {
|
|
425
|
-
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
|
|
426
|
-
}
|
|
427
|
-
settings.hooks = settings.hooks || {};
|
|
428
|
-
|
|
429
|
-
const stopAlreadyPresent = (settings.hooks.Stop || [])
|
|
430
|
-
.some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
|
|
431
|
-
|
|
432
|
-
let hooksChanged = false;
|
|
433
|
-
if (!stopAlreadyPresent) {
|
|
434
|
-
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
435
|
-
settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
|
|
436
|
-
hooksChanged = true;
|
|
437
|
-
console.log(' Claude Code: installed Stop hook');
|
|
395
|
+
if (hookResult.error) {
|
|
396
|
+
console.log(` Claude Code hooks: ${hookResult.error}`);
|
|
397
|
+
return mcpChanged;
|
|
438
398
|
}
|
|
439
399
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
hooksChanged = true;
|
|
448
|
-
}
|
|
449
|
-
const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
|
|
450
|
-
.some(entry => (entry.hooks || []).some(h => h.command === cacheHookCommand || (h.command && h.command.includes('cache-update'))));
|
|
451
|
-
|
|
452
|
-
if (!cacheAlreadyPresent) {
|
|
453
|
-
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
454
|
-
settings.hooks.PostToolUse.push({
|
|
455
|
-
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
456
|
-
hooks: [{ type: 'command', command: cacheHookCommand }]
|
|
457
|
-
});
|
|
458
|
-
hooksChanged = true;
|
|
459
|
-
console.log(' Claude Code: installed ThumbGate cache updater hook');
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Upsert statusLine for ThumbGate feedback display
|
|
463
|
-
const statuslineScript = statuslineCommand();
|
|
464
|
-
if (!settings.statusLine || settings.statusLine.command !== statuslineScript) {
|
|
465
|
-
settings.statusLine = { type: 'command', command: statuslineScript };
|
|
466
|
-
hooksChanged = true;
|
|
467
|
-
console.log(' Claude Code: installed ThumbGate status line');
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (hooksChanged) {
|
|
471
|
-
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
472
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
400
|
+
if (!hookResult.changed) {
|
|
401
|
+
console.log(` Claude Code hooks: already configured at ${hookResult.settingsPath}`);
|
|
402
|
+
} else {
|
|
403
|
+
for (const h of hookResult.added) {
|
|
404
|
+
console.log(` Claude Code: wired ${h.lifecycle} hook`);
|
|
405
|
+
}
|
|
406
|
+
console.log(` Claude Code settings: ${hookResult.settingsPath}`);
|
|
473
407
|
}
|
|
474
408
|
|
|
475
|
-
return mcpChanged ||
|
|
409
|
+
return mcpChanged || hookResult.changed;
|
|
476
410
|
}
|
|
477
411
|
|
|
478
412
|
function setupCodex() {
|
|
@@ -744,7 +678,11 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
744
678
|
let configured = 0;
|
|
745
679
|
|
|
746
680
|
const platforms = [
|
|
747
|
-
{ name: 'Claude Code', detect: [
|
|
681
|
+
{ name: 'Claude Code', detect: [
|
|
682
|
+
() => whichExists('claude'),
|
|
683
|
+
() => fs.existsSync(path.join(HOME, '.claude')),
|
|
684
|
+
() => fs.existsSync(path.join(CWD, '.claude')),
|
|
685
|
+
], setup: setupClaude },
|
|
748
686
|
{ name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
|
|
749
687
|
{ name: 'Gemini', detect: [() => whichExists('gemini'), () => fs.existsSync(path.join(HOME, '.gemini'))], setup: setupGemini },
|
|
750
688
|
{ name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
|
|
@@ -2884,6 +2822,29 @@ switch (COMMAND) {
|
|
|
2884
2822
|
}
|
|
2885
2823
|
break;
|
|
2886
2824
|
}
|
|
2825
|
+
case 'audit': {
|
|
2826
|
+
const auditFile = process.argv[3];
|
|
2827
|
+
if (!auditFile) {
|
|
2828
|
+
console.error('Usage: npx thumbgate audit <path-to-transcript.txt>');
|
|
2829
|
+
process.exit(1);
|
|
2830
|
+
}
|
|
2831
|
+
const { runAudit } = require(path.join(PKG_ROOT, 'scripts', 'audit'));
|
|
2832
|
+
const { results, totalWaste, error } = runAudit(auditFile);
|
|
2833
|
+
if (error) {
|
|
2834
|
+
console.error(error);
|
|
2835
|
+
process.exit(1);
|
|
2836
|
+
}
|
|
2837
|
+
console.log('\n๐ AI Bill Audit Results\n');
|
|
2838
|
+
if (results.length === 0) {
|
|
2839
|
+
console.log('โ
No repeat-offender patterns found. Your sessions are efficient!');
|
|
2840
|
+
} else {
|
|
2841
|
+
console.table(results);
|
|
2842
|
+
console.log('\n๐ฐ Total estimated monthly waste: $' + totalWaste);
|
|
2843
|
+
console.log('\nBlock these mistakes permanently with ThumbGate Pro:');
|
|
2844
|
+
console.log(PRO_CHECKOUT_URL);
|
|
2845
|
+
}
|
|
2846
|
+
break;
|
|
2847
|
+
}
|
|
2887
2848
|
case 'dashboard':
|
|
2888
2849
|
dashboard();
|
|
2889
2850
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.2",
|
|
4
4
|
"description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 33 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
|
|
5
5
|
"homepage": "https://thumbgate-production.up.railway.app",
|
|
6
6
|
"repository": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"scripts/analytics-window.js",
|
|
32
32
|
"scripts/async-job-runner.js",
|
|
33
33
|
"scripts/audit-trail.js",
|
|
34
|
+
"scripts/audit.js",
|
|
34
35
|
"scripts/auto-promote-gates.js",
|
|
35
36
|
"scripts/auto-wire-hooks.js",
|
|
36
37
|
"scripts/autoresearch-runner.js",
|
|
@@ -639,10 +640,6 @@
|
|
|
639
640
|
"test:competitive-positioning-marketing": "node --test tests/competitive-positioning-marketing.test.js tests/knowledge-graph-guardrails.test.js tests/supply-chain-guardrails.test.js",
|
|
640
641
|
"test:medium-weekly": "node --test tests/medium-weekly.test.js",
|
|
641
642
|
"test:dashboard-deeplink-e2e": "node --test tests/dashboard-deeplink-e2e.test.js",
|
|
642
|
-
"test:e2e:playwright": "playwright test",
|
|
643
|
-
"test:e2e:playwright:headed": "playwright test --headed",
|
|
644
|
-
"test:e2e:playwright:ui": "playwright test --ui",
|
|
645
|
-
"test:e2e:playwright:report": "playwright show-report",
|
|
646
643
|
"test:public-package-parity": "node --test tests/public-package-parity.test.js",
|
|
647
644
|
"prepare": "bash bin/install-hooks.sh >/dev/null 2>&1 || true",
|
|
648
645
|
"install:hooks": "bash bin/install-hooks.sh",
|
|
@@ -734,7 +731,6 @@
|
|
|
734
731
|
"devDependencies": {
|
|
735
732
|
"@changesets/changelog-github": "^0.7.0",
|
|
736
733
|
"@changesets/cli": "^2.31.0",
|
|
737
|
-
"@playwright/test": "^1.60.0",
|
|
738
734
|
"c8": "^11.0.0",
|
|
739
735
|
"undici": "^8.2.0"
|
|
740
736
|
}
|
package/public/dashboard.html
CHANGED
|
@@ -247,9 +247,9 @@
|
|
|
247
247
|
|
|
248
248
|
<!-- STATS -->
|
|
249
249
|
<div class="stats-grid" id="statsGrid">
|
|
250
|
-
<a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons
|
|
251
|
-
<a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=
|
|
252
|
-
<a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=
|
|
250
|
+
<a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view all feedback โ Lessons page"><div class="stat-label">Total Feedback</div><div class="stat-value cyan" id="statTotal">โ</div></a>
|
|
251
|
+
<a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=positive" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view positive feedback โ Lessons page"><div class="stat-label">๐ Positive</div><div class="stat-value green" id="statPositive">โ</div></a>
|
|
252
|
+
<a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=negative" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view negative feedback โ Lessons page"><div class="stat-label">๐ Negative</div><div class="stat-value red" id="statNegative">โ</div></a>
|
|
253
253
|
<a class="stat-card" data-card-action="gates" onclick="selectCard(this,'gates');return false;" href="#" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view active checks"><div class="stat-label">Active Gates</div><div class="stat-value cyan" id="statGates">โ</div></a>
|
|
254
254
|
</div>
|
|
255
255
|
|
package/public/index.html
CHANGED
|
@@ -19,7 +19,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
19
19
|
<meta property="og:image" content="https://thumbgate-production.up.railway.app/og.png">
|
|
20
20
|
<meta name="twitter:card" content="summary_large_image">
|
|
21
21
|
<meta name="twitter:image" content="https://thumbgate-production.up.railway.app/og.png">
|
|
22
|
-
<meta name="thumbgate-version" content="1.21.
|
|
22
|
+
<meta name="thumbgate-version" content="1.21.2">
|
|
23
23
|
<meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, 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">
|
|
24
24
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
25
25
|
|
|
@@ -1492,7 +1492,7 @@ __GA_BOOTSTRAP__
|
|
|
1492
1492
|
<a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
|
|
1493
1493
|
<a href="/blog">Blog</a>
|
|
1494
1494
|
</div>
|
|
1495
|
-
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.21.
|
|
1495
|
+
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.21.2</span>
|
|
1496
1496
|
</div>
|
|
1497
1497
|
</footer>
|
|
1498
1498
|
|
package/public/lessons.html
CHANGED
|
@@ -922,28 +922,6 @@ async function loadLive() {
|
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
loadLive().then(function() {
|
|
925
|
-
// Handle ?signal= query param from dashboard stat-card navigation.
|
|
926
|
-
// Vocabulary: 'up' | 'down' | 'all' (canonical). Also accepts the legacy
|
|
927
|
-
// 'positive' | 'negative' aliases the dashboard once emitted.
|
|
928
|
-
var qsSignal = new URLSearchParams(window.location.search).get('signal');
|
|
929
|
-
if (qsSignal) {
|
|
930
|
-
var signalMap = { positive: 'up', negative: 'down', up: 'up', down: 'down', all: 'all' };
|
|
931
|
-
var mapped = signalMap[qsSignal];
|
|
932
|
-
if (mapped) {
|
|
933
|
-
switchTab('timeline');
|
|
934
|
-
filterTimeline(mapped, null);
|
|
935
|
-
var filterBtns = document.querySelectorAll('#tab-timeline .filter-btn');
|
|
936
|
-
filterBtns.forEach(function(b) {
|
|
937
|
-
var label = b.textContent.trim().toLowerCase();
|
|
938
|
-
var match = (mapped === 'all' && label === 'all') ||
|
|
939
|
-
(mapped === 'up' && (label.indexOf('๐') !== -1 || label.indexOf('positive') !== -1 || label === 'up')) ||
|
|
940
|
-
(mapped === 'down' && (label.indexOf('๐') !== -1 || label.indexOf('negative') !== -1 || label === 'down'));
|
|
941
|
-
b.classList.toggle('active', match);
|
|
942
|
-
});
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
925
|
// Default: highlight Active Rules card on page load
|
|
948
926
|
highlightCard(0);
|
|
949
927
|
|
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.21.
|
|
28
|
+
"softwareVersion": "1.21.2",
|
|
29
29
|
"url": "https://thumbgate-production.up.railway.app/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.21.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 ยท Version 1.21.2</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>
|
package/scripts/audit.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* scripts/audit.js
|
|
5
|
+
*
|
|
6
|
+
* Heuristic-based AI bill auditor. Finds repeated mistakes in agent transcripts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
const PATTERNS = [
|
|
12
|
+
{
|
|
13
|
+
id: 'force-push-retry',
|
|
14
|
+
name: 'git push --force after correction',
|
|
15
|
+
regex: /git\s+push.*--force/gi,
|
|
16
|
+
tokenEstimate: 6000,
|
|
17
|
+
costPerRepeat: 0.44,
|
|
18
|
+
why: 'Full diff context reload on error.'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'import-loop',
|
|
22
|
+
name: 'Hallucinated import retry',
|
|
23
|
+
regex: /(Cannot find module|Module not found|import .* from .*error)/gi,
|
|
24
|
+
tokenEstimate: 4000,
|
|
25
|
+
costPerRepeat: 0.12,
|
|
26
|
+
why: 'Re-indexing and path searching.'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'apology-loop',
|
|
30
|
+
name: '"I apologize" retry cycle',
|
|
31
|
+
regex: /(I apologize|Let me try a different approach|I will now attempt)/gi,
|
|
32
|
+
tokenEstimate: 5000,
|
|
33
|
+
costPerRepeat: 0.15,
|
|
34
|
+
why: 'Reasoning chain reset.'
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
function runAudit(filePath) {
|
|
39
|
+
if (!fs.existsSync(filePath)) {
|
|
40
|
+
return { error: 'File not found: ' + filePath };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
44
|
+
const results = [];
|
|
45
|
+
let totalWaste = 0;
|
|
46
|
+
|
|
47
|
+
PATTERNS.forEach(p => {
|
|
48
|
+
const matches = (content.match(p.regex) || []).length;
|
|
49
|
+
if (matches > 1) { // It's only a "repeat" if it happens more than once
|
|
50
|
+
const repeats = matches - 1;
|
|
51
|
+
const waste = repeats * p.costPerRepeat;
|
|
52
|
+
totalWaste += waste;
|
|
53
|
+
results.push({
|
|
54
|
+
pattern: p.name,
|
|
55
|
+
occurrences: repeats,
|
|
56
|
+
waste: waste.toFixed(2),
|
|
57
|
+
why: p.why
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { results, totalWaste: totalWaste.toFixed(2) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { runAudit, PATTERNS };
|
package/scripts/cli-schema.js
CHANGED
|
@@ -525,6 +525,14 @@ const CLI_COMMANDS = [
|
|
|
525
525
|
{ name: 'json', type: 'boolean', description: 'Output as JSON' },
|
|
526
526
|
],
|
|
527
527
|
},
|
|
528
|
+
{
|
|
529
|
+
name: 'audit',
|
|
530
|
+
description: 'Audit an agent transcript for repeat-mistake patterns and estimated token waste',
|
|
531
|
+
group: 'ops',
|
|
532
|
+
flags: [
|
|
533
|
+
{ name: 'file', type: 'string', description: 'Path to the agent transcript to audit' },
|
|
534
|
+
],
|
|
535
|
+
},
|
|
528
536
|
{
|
|
529
537
|
name: 'init',
|
|
530
538
|
description: 'Scaffold .thumbgate/ config and wire agent hooks',
|
package/scripts/hook-runtime.js
CHANGED
|
@@ -33,34 +33,34 @@ function publishedHookCommandsAvailable(version) {
|
|
|
33
33
|
return available;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function
|
|
36
|
+
function resolveCliCommand(subcommand) {
|
|
37
37
|
const version = packageVersion();
|
|
38
38
|
if (publishedHookCommandsAvailable(version)) {
|
|
39
|
-
return publishedCliShellCommand(version);
|
|
39
|
+
return publishedCliShellCommand(version, [subcommand]);
|
|
40
40
|
}
|
|
41
41
|
if (isSourceCheckout(PKG_ROOT)) {
|
|
42
|
-
return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
|
|
42
|
+
return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))} ${subcommand}`;
|
|
43
43
|
}
|
|
44
|
-
return publishedCliShellCommand(version);
|
|
44
|
+
return publishedCliShellCommand(version, [subcommand]);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
47
|
+
function resolveCodexCliCommand(subcommand) {
|
|
48
48
|
const version = packageVersion();
|
|
49
49
|
if (publishedHookCommandsAvailable(version)) {
|
|
50
|
-
return publishedCliShellCommand('latest', [], { preferInstalled: false });
|
|
50
|
+
return publishedCliShellCommand('latest', [subcommand], { preferInstalled: false });
|
|
51
51
|
}
|
|
52
52
|
if (isSourceCheckout(PKG_ROOT)) {
|
|
53
|
-
return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
|
|
53
|
+
return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))} ${subcommand}`;
|
|
54
54
|
}
|
|
55
|
-
return publishedCliShellCommand('latest', [], { preferInstalled: false });
|
|
55
|
+
return publishedCliShellCommand('latest', [subcommand], { preferInstalled: false });
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function buildPortableHookCommand(subcommand) {
|
|
59
|
-
return
|
|
59
|
+
return resolveCliCommand(subcommand);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function buildCodexPortableHookCommand(subcommand) {
|
|
63
|
-
return
|
|
63
|
+
return resolveCodexCliCommand(subcommand);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function preToolHookCommand() {
|
|
@@ -115,8 +115,8 @@ module.exports = {
|
|
|
115
115
|
packageVersion,
|
|
116
116
|
publishedHookCommandsAvailable,
|
|
117
117
|
preToolHookCommand,
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
resolveCodexCliCommand,
|
|
119
|
+
resolveCliCommand,
|
|
120
120
|
sessionStartHookCommand,
|
|
121
121
|
statuslineCommand,
|
|
122
122
|
userPromptHookCommand,
|
package/scripts/statusline.sh
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
8
8
|
case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
|
|
9
9
|
LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
|
|
10
|
+
STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
|
|
10
11
|
|
|
11
12
|
# โโ Parse Claude Code session JSON from stdin โโโโโโโโโโโโโโโโโโโโโ
|
|
12
13
|
eval "$(cat | jq -r '
|
|
@@ -92,17 +93,19 @@ fi
|
|
|
92
93
|
LINK_STATE="offline"
|
|
93
94
|
UP_URL=""; DOWN_URL=""; DASHBOARD_URL=""; LESSONS_URL=""
|
|
94
95
|
DASHBOARD_LABEL="Dashboard"; LESSONS_LABEL="Lessons"
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
97
|
+
_LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
|
|
98
|
+
if [ -n "$_LINKS_JSON" ]; then
|
|
99
|
+
eval "$(echo "$_LINKS_JSON" | jq -r '
|
|
100
|
+
@sh "LINK_STATE=\(.state // "offline")",
|
|
101
|
+
@sh "UP_URL=\(.upUrl // "")",
|
|
102
|
+
@sh "DOWN_URL=\(.downUrl // "")",
|
|
103
|
+
@sh "DASHBOARD_URL=\(.dashboardUrl // "")",
|
|
104
|
+
@sh "LESSONS_URL=\(.lessonsUrl // "")",
|
|
105
|
+
@sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
|
|
106
|
+
@sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
|
|
107
|
+
' 2>/dev/null)"
|
|
108
|
+
fi
|
|
106
109
|
fi
|
|
107
110
|
|
|
108
111
|
# โโ ThumbGate package metadata โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -117,13 +120,15 @@ fi
|
|
|
117
120
|
|
|
118
121
|
# โโ Repo context (branch / work item / PR) โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
119
122
|
BRANCH_NAME=""; WORK_ITEM_LABEL=""; PR_LABEL=""
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
124
|
+
_CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
|
|
125
|
+
if [[ -n "$_CONTEXT_JSON" ]]; then
|
|
126
|
+
eval "$(echo "$_CONTEXT_JSON" | jq -r '
|
|
127
|
+
@sh "BRANCH_NAME=\(.branchName // "")",
|
|
128
|
+
@sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
|
|
129
|
+
@sh "PR_LABEL=\(.prLabel // "")"
|
|
130
|
+
' 2>/dev/null)"
|
|
131
|
+
fi
|
|
127
132
|
fi
|
|
128
133
|
|
|
129
134
|
# โโ Control Tower stats โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -139,14 +144,16 @@ fi
|
|
|
139
144
|
|
|
140
145
|
# โโ Latest lesson (data available for extensions; not rendered in statusbar) โโ
|
|
141
146
|
LESSON_TEXT=""; LESSON_ID=""; LESSON_LABEL=""; LESSON_LINK=""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
148
|
+
_LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
|
|
149
|
+
if [[ -n "$_LESSON_JSON" ]]; then
|
|
150
|
+
eval "$(echo "$_LESSON_JSON" | jq -r '
|
|
151
|
+
@sh "LESSON_TEXT=\(.text // "")",
|
|
152
|
+
@sh "LESSON_ID=\(.lessonId // "")",
|
|
153
|
+
@sh "LESSON_LABEL=\(.label // "")",
|
|
154
|
+
@sh "LESSON_LINK=\(.link // "")"
|
|
155
|
+
' 2>/dev/null)"
|
|
156
|
+
fi
|
|
150
157
|
fi
|
|
151
158
|
|
|
152
159
|
# โโ Colors โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -199,8 +206,10 @@ LINE="${LINE:+${LINE} ยท }ThumbGate v${TG_VERSION} ยท ${TG_TIER}"
|
|
|
199
206
|
if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
|
|
200
207
|
LINE="${D}${LINE}${RST} ยท no feedback yet"
|
|
201
208
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} ยท ${D}${PR_LABEL}${RST}"
|
|
202
|
-
|
|
203
|
-
|
|
209
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
210
|
+
LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
|
|
211
|
+
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
|
|
212
|
+
fi
|
|
204
213
|
printf '%b\n' "$LINE"
|
|
205
214
|
else
|
|
206
215
|
LINE="${LINE} ยท ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
|
|
@@ -210,8 +219,10 @@ else
|
|
|
210
219
|
[[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}โ ${RST}"
|
|
211
220
|
[[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}โ ${RST}"
|
|
212
221
|
[[ -n "$PR_LABEL" ]] && LINE="${LINE} ยท ${D}${PR_LABEL}${RST}"
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
|
|
223
|
+
LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
|
|
224
|
+
[[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
|
|
225
|
+
fi
|
|
215
226
|
|
|
216
227
|
printf '%b\n' "$LINE"
|
|
217
228
|
fi
|
|
@@ -1178,15 +1178,10 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
|
|
|
1178
1178
|
if (lowRiskHandoff) {
|
|
1179
1179
|
return 'allow';
|
|
1180
1180
|
}
|
|
1181
|
-
// Background customer-system actions checkpoint (warn), never hard-deny.
|
|
1182
|
-
// The checkpoint IS the mitigation โ blocking outright prevents legitimate work.
|
|
1183
|
-
if (backgroundAgent && customerSystemAction) {
|
|
1184
|
-
return 'warn';
|
|
1185
|
-
}
|
|
1186
1181
|
if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
|
|
1187
1182
|
return 'deny';
|
|
1188
1183
|
}
|
|
1189
|
-
if (economicAction || (backgroundAgent && riskScore >= 0.3)) {
|
|
1184
|
+
if (economicAction || (backgroundAgent && customerSystemAction) || (backgroundAgent && riskScore >= 0.3)) {
|
|
1190
1185
|
return 'warn';
|
|
1191
1186
|
}
|
|
1192
1187
|
if ((workflowControl && workflowControl.mode === 'warn') || (costControl && costControl.mode === 'warn') || riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {
|