thumbgate 0.9.9 → 0.9.11
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/README.md +4 -4
- package/.claude-plugin/marketplace.json +4 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +115 -312
- package/adapters/README.md +2 -2
- package/adapters/amp/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
- package/adapters/chatgpt/openapi.yaml +2 -2
- package/adapters/claude/.mcp.json +3 -3
- package/adapters/codex/config.toml +4 -4
- package/adapters/gemini/function-declarations.json +1 -1
- package/adapters/mcp/server-stdio.js +66 -6
- package/adapters/opencode/opencode.json +4 -2
- package/bin/cli.js +188 -39
- package/config/e2e-critical-flows.json +4 -0
- package/config/gates/default.json +74 -2
- package/config/github-about.json +1 -1
- package/config/mcp-allowlists.json +33 -6
- package/config/skill-packs/react-testing.json +1 -1
- package/config/tessl-tiles.json +3 -3
- package/openapi/openapi.yaml +2 -2
- package/package.json +23 -9
- package/plugins/amp-skill/INSTALL.md +3 -2
- package/plugins/amp-skill/SKILL.md +1 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +5 -3
- package/plugins/claude-codex-bridge/README.md +1 -1
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +1 -1
- package/plugins/claude-skill/INSTALL.md +4 -3
- package/plugins/claude-skill/SKILL.md +1 -1
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +5 -3
- package/plugins/codex-profile/INSTALL.md +2 -2
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +5 -5
- package/plugins/cursor-marketplace/mcp.json +4 -2
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +1 -1
- package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
- package/plugins/gemini-extension/INSTALL.md +4 -4
- package/plugins/opencode-profile/INSTALL.md +5 -5
- package/public/dashboard.html +15 -8
- package/public/index.html +134 -375
- package/public/js/buyer-intent.js +252 -0
- package/public/pro.html +1085 -0
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/adk-consolidator.js +17 -5
- package/scripts/agent-readiness.js +3 -1
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/auto-promote-gates.js +8 -0
- package/scripts/auto-wire-hooks.js +105 -21
- package/scripts/billing.js +111 -7
- package/scripts/build-metadata.js +14 -0
- package/scripts/check-congruence.js +1 -1
- package/scripts/context-engine.js +2 -1
- package/scripts/daemon-manager.js +2 -2
- package/scripts/dashboard.js +2 -2
- package/scripts/data-governance.js +1 -1
- package/scripts/deploy-gcp.sh +1 -1
- package/scripts/deploy-policy.js +22 -4
- package/scripts/dispatch-brief.js +1 -1
- package/scripts/ensure-repo-bootstrap.js +1 -1
- package/scripts/feedback-attribution.js +22 -10
- package/scripts/feedback-fallback.js +3 -2
- package/scripts/feedback-inbox-read.js +1 -1
- package/scripts/feedback-loop.js +41 -3
- package/scripts/feedback-paths.js +8 -8
- package/scripts/feedback-schema.js +1 -1
- package/scripts/feedback-to-memory.js +2 -2
- package/scripts/filesystem-search.js +2 -2
- package/scripts/gates-engine.js +765 -34
- package/scripts/generate-paperbanana-diagrams.sh +3 -3
- package/scripts/github-about.js +1 -1
- package/scripts/gtm-revenue-loop.js +20 -1
- package/scripts/hook-runtime.js +89 -0
- package/scripts/hook-stop-self-score.sh +3 -3
- package/scripts/hook-thumbgate-cache-updater.js +98 -37
- package/scripts/hosted-config.js +12 -10
- package/scripts/hybrid-feedback-context.js +54 -13
- package/scripts/install-mcp.js +14 -1
- package/scripts/intent-router.js +1 -1
- package/scripts/internal-agent-bootstrap.js +1 -1
- package/scripts/lesson-inference.js +6 -1
- package/scripts/license.js +54 -16
- package/scripts/mcp-config.js +69 -7
- package/scripts/memory-migration.js +1 -1
- package/scripts/money-watcher.js +166 -16
- package/scripts/operational-integrity.js +480 -0
- package/scripts/optimize-context.js +1 -1
- package/scripts/perplexity-marketing.js +1 -1
- package/scripts/post-everywhere.js +7 -12
- package/scripts/post-to-x.js +1 -1
- package/scripts/pr-manager.js +14 -11
- package/scripts/problem-detail.js +10 -10
- package/scripts/profile-router.js +2 -0
- package/scripts/prompt-dlp.js +1 -0
- package/scripts/prove-adapters.js +6 -6
- package/scripts/prove-automation.js +1 -1
- package/scripts/prove-autoresearch.js +1 -1
- package/scripts/prove-claim-verification.js +3 -3
- package/scripts/prove-data-pipeline.js +5 -5
- package/scripts/prove-data-quality.js +1 -1
- package/scripts/prove-evolution.js +7 -7
- package/scripts/prove-harnesses.js +2 -2
- package/scripts/prove-lancedb.js +2 -2
- package/scripts/prove-local-intelligence.js +1 -1
- package/scripts/prove-loop-closure.js +1 -1
- package/scripts/prove-predictive-insights.js +2 -2
- package/scripts/prove-runtime.js +6 -6
- package/scripts/prove-seo-gsd.js +1 -1
- package/scripts/prove-settings.js +4 -4
- package/scripts/prove-subway-upgrades.js +1 -1
- package/scripts/prove-tessl.js +2 -2
- package/scripts/prove-xmemory.js +2 -2
- package/scripts/publish-decision.js +10 -0
- package/scripts/published-cli.js +34 -0
- package/scripts/rate-limiter.js +2 -2
- package/scripts/reddit-monitor-cron.sh +2 -2
- package/scripts/reminder-engine.js +1 -1
- package/scripts/schedule-manager.js +3 -3
- package/scripts/self-healing-check.js +1 -1
- package/scripts/shieldcortex-memory-firewall-runner.mjs +1 -1
- package/scripts/skill-quality-tracker.js +1 -1
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
- package/scripts/social-analytics/engagement-audit.js +202 -0
- package/scripts/social-analytics/generate-instagram-card.js +1 -1
- package/scripts/social-analytics/instagram-thumbgate-post.js +5 -1
- package/scripts/social-analytics/install-growth-automation.js +114 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +8 -2
- package/scripts/social-analytics/publish-thumbgate-launch.js +1 -1
- package/scripts/social-analytics/publishers/reddit.js +7 -12
- package/scripts/social-analytics/publishers/zernio.js +19 -0
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
- package/scripts/social-analytics/sync-launch-assets.js +185 -0
- package/scripts/social-pipeline.js +2 -2
- package/scripts/social-post-hourly.js +185 -0
- package/scripts/social-quality-gate.js +119 -3
- package/scripts/social-reply-monitor.js +150 -34
- package/scripts/statusline-cache-path.js +27 -0
- package/scripts/statusline-meta.js +22 -0
- package/scripts/statusline.sh +24 -32
- package/scripts/sync-version.js +24 -12
- package/scripts/telemetry-analytics.js +4 -4
- package/scripts/tessl-export.js +1 -1
- package/scripts/test-coverage.js +20 -13
- package/scripts/thumbgate-search.js +2 -2
- package/scripts/tool-registry.js +98 -1
- package/scripts/train_from_feedback.py +1 -1
- package/scripts/user-profile.js +4 -4
- package/scripts/validate-feedback.js +1 -1
- package/scripts/vector-store.js +1 -1
- package/scripts/verification-loop.js +1 -1
- package/scripts/verify-run.js +1 -1
- package/scripts/weekly-auto-post.js +1 -1
- package/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
- package/src/api/server.js +291 -41
- package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
- package/scripts/social-analytics/db/social-analytics.db +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Codex MCP profile (copy into ~/.codex/config.toml or merge section)
|
|
2
2
|
[mcp_servers.thumbgate]
|
|
3
|
-
command = "
|
|
4
|
-
args = ["
|
|
3
|
+
command = "npx"
|
|
4
|
+
args = ["--yes", "--package", "thumbgate@0.9.11", "thumbgate", "serve"]
|
|
5
5
|
|
|
6
6
|
# Hard PreToolUse hook for Codex
|
|
7
7
|
[hooks.pre_tool_use]
|
|
8
|
-
command = "
|
|
9
|
-
args = ["
|
|
8
|
+
command = "npx"
|
|
9
|
+
args = ["--yes", "--package", "thumbgate@0.9.11", "thumbgate", "gate-check"]
|
|
@@ -43,10 +43,18 @@ const {
|
|
|
43
43
|
evaluateSecretGuard,
|
|
44
44
|
satisfyCondition,
|
|
45
45
|
loadStats: loadGateStats,
|
|
46
|
+
setTaskScope,
|
|
47
|
+
setBranchGovernance,
|
|
48
|
+
getScopeState,
|
|
49
|
+
getBranchGovernanceState,
|
|
50
|
+
approveProtectedAction,
|
|
46
51
|
trackAction,
|
|
47
52
|
verifyClaimEvidence,
|
|
48
53
|
registerClaimGate,
|
|
49
54
|
} = require('../../scripts/gates-engine');
|
|
55
|
+
const {
|
|
56
|
+
evaluateOperationalIntegrity,
|
|
57
|
+
} = require('../../scripts/operational-integrity');
|
|
50
58
|
const { diagnoseFailure } = require('../../scripts/failure-diagnostics');
|
|
51
59
|
const {
|
|
52
60
|
analyzeCodeGraphImpact,
|
|
@@ -71,7 +79,7 @@ const {
|
|
|
71
79
|
retrieveRelevantLessons,
|
|
72
80
|
} = require('../../scripts/lesson-retrieval');
|
|
73
81
|
const {
|
|
74
|
-
|
|
82
|
+
searchThumbgate,
|
|
75
83
|
} = require('../../scripts/thumbgate-search');
|
|
76
84
|
const { checkLimit, UPGRADE_MESSAGE } = require('../../scripts/rate-limiter');
|
|
77
85
|
const { generateOrgDashboard } = require('../../scripts/org-dashboard');
|
|
@@ -103,7 +111,7 @@ const {
|
|
|
103
111
|
finalizeSession: finalizeFeedbackSession,
|
|
104
112
|
} = require('../../scripts/feedback-session');
|
|
105
113
|
|
|
106
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '0.9.
|
|
114
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '0.9.11' };
|
|
107
115
|
const COMMERCE_CATEGORIES = [
|
|
108
116
|
'product_recommendation',
|
|
109
117
|
'brand_compliance',
|
|
@@ -395,9 +403,9 @@ async function callToolInner(name, args) {
|
|
|
395
403
|
args.actionContext || '',
|
|
396
404
|
{ maxResults: Number(args.maxResults || 5) },
|
|
397
405
|
));
|
|
398
|
-
case '
|
|
399
|
-
enforceLimit('
|
|
400
|
-
return toTextResult(
|
|
406
|
+
case 'search_thumbgate':
|
|
407
|
+
enforceLimit('search_thumbgate');
|
|
408
|
+
return toTextResult(searchThumbgate({
|
|
401
409
|
query: args.query,
|
|
402
410
|
limit: args.limit,
|
|
403
411
|
source: args.source,
|
|
@@ -519,6 +527,49 @@ async function callToolInner(name, args) {
|
|
|
519
527
|
}
|
|
520
528
|
return toTextResult(result);
|
|
521
529
|
}
|
|
530
|
+
case 'set_task_scope':
|
|
531
|
+
return toTextResult({
|
|
532
|
+
scope: setTaskScope({
|
|
533
|
+
taskId: args.taskId,
|
|
534
|
+
summary: args.summary,
|
|
535
|
+
allowedPaths: args.allowedPaths,
|
|
536
|
+
protectedPaths: args.protectedPaths,
|
|
537
|
+
repoPath: args.repoPath,
|
|
538
|
+
localOnly: args.localOnly === true,
|
|
539
|
+
clear: args.clear === true,
|
|
540
|
+
}),
|
|
541
|
+
});
|
|
542
|
+
case 'get_scope_state':
|
|
543
|
+
return toTextResult(getScopeState());
|
|
544
|
+
case 'set_branch_governance':
|
|
545
|
+
return toTextResult({
|
|
546
|
+
branchGovernance: setBranchGovernance({
|
|
547
|
+
branchName: args.branchName,
|
|
548
|
+
baseBranch: args.baseBranch,
|
|
549
|
+
prRequired: args.prRequired,
|
|
550
|
+
prNumber: args.prNumber,
|
|
551
|
+
prUrl: args.prUrl,
|
|
552
|
+
queueRequired: args.queueRequired,
|
|
553
|
+
localOnly: args.localOnly === true,
|
|
554
|
+
releaseVersion: args.releaseVersion,
|
|
555
|
+
releaseEvidence: args.releaseEvidence,
|
|
556
|
+
releaseSensitiveGlobs: args.releaseSensitiveGlobs,
|
|
557
|
+
clear: args.clear === true,
|
|
558
|
+
}),
|
|
559
|
+
});
|
|
560
|
+
case 'get_branch_governance':
|
|
561
|
+
return toTextResult(getBranchGovernanceState());
|
|
562
|
+
case 'approve_protected_action':
|
|
563
|
+
return toTextResult({
|
|
564
|
+
approved: true,
|
|
565
|
+
approval: approveProtectedAction({
|
|
566
|
+
pathGlobs: args.pathGlobs,
|
|
567
|
+
reason: args.reason,
|
|
568
|
+
evidence: args.evidence,
|
|
569
|
+
taskId: args.taskId,
|
|
570
|
+
ttlMs: args.ttlMs,
|
|
571
|
+
}),
|
|
572
|
+
});
|
|
522
573
|
case 'track_action': {
|
|
523
574
|
const entry = trackAction(args.actionId, args.metadata || {});
|
|
524
575
|
return toTextResult({
|
|
@@ -529,6 +580,15 @@ async function callToolInner(name, args) {
|
|
|
529
580
|
}
|
|
530
581
|
case 'verify_claim':
|
|
531
582
|
return toTextResult(verifyClaimEvidence(args.claim));
|
|
583
|
+
case 'check_operational_integrity':
|
|
584
|
+
return toTextResult(evaluateOperationalIntegrity({
|
|
585
|
+
repoPath: args.repoPath,
|
|
586
|
+
baseBranch: args.baseBranch,
|
|
587
|
+
command: args.command,
|
|
588
|
+
requirePrForReleaseSensitive: args.requirePrForReleaseSensitive === true,
|
|
589
|
+
requireVersionNotBehindBase: args.requireVersionNotBehindBase === true,
|
|
590
|
+
branchGovernance: getBranchGovernanceState(),
|
|
591
|
+
}));
|
|
532
592
|
case 'register_claim_gate':
|
|
533
593
|
return toTextResult(registerClaimGate(args.claimPattern, args.requiredActions, args.message));
|
|
534
594
|
case 'gate_stats':
|
|
@@ -682,7 +742,7 @@ const LOCK_STALE_MS = Number(process.env.THUMBGATE_LOCK_STALE_MS) || 2 * 60 * 60
|
|
|
682
742
|
*
|
|
683
743
|
* Staleness reaping: if the lock-holding process is alive but the lock is
|
|
684
744
|
* older than LOCK_STALE_MS, the holder is killed (SIGTERM) and the lock is
|
|
685
|
-
* reclaimed. This prevents orphaned `
|
|
745
|
+
* reclaimed. This prevents orphaned `thumbgate serve` processes from permanently
|
|
686
746
|
* blocking new sessions.
|
|
687
747
|
*/
|
|
688
748
|
function acquireLock() {
|
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* npx thumbgate init # scaffold .thumbgate/ config + .mcp.json
|
|
7
7
|
* npx thumbgate init --wire-hooks # wire hooks only (auto-detect agent)
|
|
8
8
|
* npx thumbgate init --agent claude-code # scaffold + wire hooks for specific agent
|
|
9
|
+
* npx thumbgate gate-check # PreToolUse hook: pipe tool JSON via stdin, get verdict
|
|
9
10
|
* npx thumbgate capture # capture feedback
|
|
10
11
|
* npx thumbgate export-dpo # export DPO training pairs
|
|
11
12
|
* npx thumbgate export-databricks # export Databricks-ready analytics bundle
|
|
@@ -19,9 +20,16 @@
|
|
|
19
20
|
const fs = require('fs');
|
|
20
21
|
const path = require('path');
|
|
21
22
|
const crypto = require('crypto');
|
|
22
|
-
const { execSync } = require('child_process');
|
|
23
|
+
const { execSync, execFileSync } = require('child_process');
|
|
23
24
|
const { resolveMcpEntry } = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
|
|
24
25
|
const { trackEvent } = require(path.join(__dirname, '..', 'scripts', 'cli-telemetry'));
|
|
26
|
+
const {
|
|
27
|
+
cacheUpdateHookCommand,
|
|
28
|
+
preToolHookCommand,
|
|
29
|
+
sessionStartHookCommand,
|
|
30
|
+
statuslineCommand,
|
|
31
|
+
userPromptHookCommand,
|
|
32
|
+
} = require(path.join(__dirname, '..', 'scripts', 'hook-runtime'));
|
|
25
33
|
const {
|
|
26
34
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
27
35
|
PRO_PRICE_LABEL,
|
|
@@ -103,14 +111,33 @@ function limitNudge(action) {
|
|
|
103
111
|
|
|
104
112
|
function parseArgs(argv) {
|
|
105
113
|
const args = {};
|
|
106
|
-
argv.forEach((arg) => {
|
|
114
|
+
argv.forEach((arg, index) => {
|
|
107
115
|
if (!arg.startsWith('--')) return;
|
|
108
116
|
const [key, ...rest] = arg.slice(2).split('=');
|
|
109
|
-
|
|
117
|
+
if (rest.length) {
|
|
118
|
+
args[key] = rest.join('=');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const next = argv[index + 1];
|
|
123
|
+
if (next && !next.startsWith('--')) {
|
|
124
|
+
args[key] = next;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
args[key] = true;
|
|
110
129
|
});
|
|
111
130
|
return args;
|
|
112
131
|
}
|
|
113
132
|
|
|
133
|
+
function readStdinText() {
|
|
134
|
+
try {
|
|
135
|
+
return fs.readFileSync(0, 'utf8');
|
|
136
|
+
} catch {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
function pkgVersion() {
|
|
115
142
|
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
116
143
|
return pkg.version;
|
|
@@ -119,8 +146,8 @@ function pkgVersion() {
|
|
|
119
146
|
// --- Platform auto-detection helpers ---
|
|
120
147
|
|
|
121
148
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
122
|
-
const MCP_SERVER_NAME = '
|
|
123
|
-
const
|
|
149
|
+
const MCP_SERVER_NAME = 'thumbgate';
|
|
150
|
+
const MCP_SERVER_NAMES = ['thumbgate', 'mcp-memory-gateway', 'rlhf'];
|
|
124
151
|
|
|
125
152
|
function mcpEntriesMatch(entry, expectedEntry) {
|
|
126
153
|
return Boolean(
|
|
@@ -165,7 +192,7 @@ function mcpSectionRegex(name) {
|
|
|
165
192
|
|
|
166
193
|
function upsertCodexServerConfig(content) {
|
|
167
194
|
const canonicalBlock = mcpSectionBlock(MCP_SERVER_NAME, 'home');
|
|
168
|
-
const sections =
|
|
195
|
+
const sections = MCP_SERVER_NAMES.map((name) => ({
|
|
169
196
|
name,
|
|
170
197
|
regex: mcpSectionRegex(name),
|
|
171
198
|
}));
|
|
@@ -233,10 +260,10 @@ function mergeMcpJson(filePath, label, scope = 'project') {
|
|
|
233
260
|
changed = true;
|
|
234
261
|
}
|
|
235
262
|
|
|
236
|
-
for (const
|
|
237
|
-
if (
|
|
238
|
-
if (Object.prototype.hasOwnProperty.call(existing.mcpServers,
|
|
239
|
-
delete existing.mcpServers[
|
|
263
|
+
for (const serverName of MCP_SERVER_NAMES) {
|
|
264
|
+
if (serverName === MCP_SERVER_NAME) continue;
|
|
265
|
+
if (Object.prototype.hasOwnProperty.call(existing.mcpServers, serverName)) {
|
|
266
|
+
delete existing.mcpServers[serverName];
|
|
240
267
|
changed = true;
|
|
241
268
|
}
|
|
242
269
|
}
|
|
@@ -284,14 +311,21 @@ function setupClaude() {
|
|
|
284
311
|
}
|
|
285
312
|
|
|
286
313
|
// Upsert PostToolUse hook for ThumbGate statusline cache updates
|
|
287
|
-
const cacheHookCommand =
|
|
314
|
+
const cacheHookCommand = cacheUpdateHookCommand();
|
|
315
|
+
const originalPostToolUseCount = (settings.hooks.PostToolUse || []).length;
|
|
316
|
+
settings.hooks.PostToolUse = (settings.hooks.PostToolUse || []).filter(
|
|
317
|
+
(entry) => !(entry.hooks || []).some((h) => h.command && h.command !== cacheHookCommand && /(hook-thumbgate-cache-updater|cache-update\b)/.test(h.command))
|
|
318
|
+
);
|
|
319
|
+
if (settings.hooks.PostToolUse.length !== originalPostToolUseCount) {
|
|
320
|
+
hooksChanged = true;
|
|
321
|
+
}
|
|
288
322
|
const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
|
|
289
|
-
.some(entry => (entry.hooks || []).some(h => h.command && h.command.includes('
|
|
323
|
+
.some(entry => (entry.hooks || []).some(h => h.command === cacheHookCommand || (h.command && h.command.includes('cache-update'))));
|
|
290
324
|
|
|
291
325
|
if (!cacheAlreadyPresent) {
|
|
292
326
|
settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
|
|
293
327
|
settings.hooks.PostToolUse.push({
|
|
294
|
-
matcher: '
|
|
328
|
+
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
295
329
|
hooks: [{ type: 'command', command: cacheHookCommand }]
|
|
296
330
|
});
|
|
297
331
|
hooksChanged = true;
|
|
@@ -299,8 +333,8 @@ function setupClaude() {
|
|
|
299
333
|
}
|
|
300
334
|
|
|
301
335
|
// Upsert statusLine for ThumbGate feedback display
|
|
302
|
-
const statuslineScript =
|
|
303
|
-
if (!settings.statusLine) {
|
|
336
|
+
const statuslineScript = statuslineCommand();
|
|
337
|
+
if (!settings.statusLine || settings.statusLine.command !== statuslineScript) {
|
|
304
338
|
settings.statusLine = { type: 'command', command: statuslineScript };
|
|
305
339
|
hooksChanged = true;
|
|
306
340
|
console.log(' Claude Code: installed ThumbGate status line');
|
|
@@ -344,10 +378,10 @@ function setupGemini() {
|
|
|
344
378
|
changed = true;
|
|
345
379
|
}
|
|
346
380
|
|
|
347
|
-
for (const
|
|
348
|
-
if (
|
|
349
|
-
if (Object.prototype.hasOwnProperty.call(settings.mcpServers,
|
|
350
|
-
delete settings.mcpServers[
|
|
381
|
+
for (const serverName of MCP_SERVER_NAMES) {
|
|
382
|
+
if (serverName === MCP_SERVER_NAME) continue;
|
|
383
|
+
if (Object.prototype.hasOwnProperty.call(settings.mcpServers, serverName)) {
|
|
384
|
+
delete settings.mcpServers[serverName];
|
|
351
385
|
changed = true;
|
|
352
386
|
}
|
|
353
387
|
}
|
|
@@ -362,14 +396,14 @@ function setupGemini() {
|
|
|
362
396
|
}
|
|
363
397
|
|
|
364
398
|
function setupAmp() {
|
|
365
|
-
const skillDir = path.join(CWD, '.amp', 'skills', '
|
|
399
|
+
const skillDir = path.join(CWD, '.amp', 'skills', 'thumbgate-feedback');
|
|
366
400
|
const destPath = path.join(skillDir, 'SKILL.md');
|
|
367
401
|
if (fs.existsSync(destPath)) return false;
|
|
368
402
|
const srcPath = path.join(PKG_ROOT, 'plugins', 'amp-skill', 'SKILL.md');
|
|
369
403
|
if (!fs.existsSync(srcPath)) return false;
|
|
370
404
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
371
405
|
fs.copyFileSync(srcPath, destPath);
|
|
372
|
-
console.log(' Amp: installed .amp/skills/
|
|
406
|
+
console.log(' Amp: installed .amp/skills/thumbgate-feedback/SKILL.md');
|
|
373
407
|
return true;
|
|
374
408
|
}
|
|
375
409
|
|
|
@@ -401,11 +435,11 @@ function init() {
|
|
|
401
435
|
return;
|
|
402
436
|
}
|
|
403
437
|
|
|
404
|
-
const
|
|
405
|
-
const configPath = path.join(
|
|
438
|
+
const thumbgateDir = path.join(CWD, '.thumbgate');
|
|
439
|
+
const configPath = path.join(thumbgateDir, 'config.json');
|
|
406
440
|
|
|
407
|
-
if (!fs.existsSync(
|
|
408
|
-
fs.mkdirSync(
|
|
441
|
+
if (!fs.existsSync(thumbgateDir)) {
|
|
442
|
+
fs.mkdirSync(thumbgateDir, { recursive: true });
|
|
409
443
|
console.log('Created .thumbgate/');
|
|
410
444
|
} else {
|
|
411
445
|
console.log('.thumbgate/ already exists — updating config');
|
|
@@ -461,7 +495,7 @@ function init() {
|
|
|
461
495
|
// ChatGPT — cannot be automated
|
|
462
496
|
const chatgptSpec = path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml');
|
|
463
497
|
if (fs.existsSync(chatgptSpec)) {
|
|
464
|
-
const projectChatgptSpec = path.join(
|
|
498
|
+
const projectChatgptSpec = path.join(thumbgateDir, 'chatgpt-openapi.yaml');
|
|
465
499
|
fs.copyFileSync(chatgptSpec, projectChatgptSpec);
|
|
466
500
|
console.log(` ChatGPT: import ${path.relative(CWD, projectChatgptSpec)} in GPT Builder > Actions`);
|
|
467
501
|
}
|
|
@@ -766,8 +800,9 @@ function pro() {
|
|
|
766
800
|
}
|
|
767
801
|
|
|
768
802
|
// Validate key format (THUMBGATE_API_KEY prefix)
|
|
769
|
-
|
|
770
|
-
|
|
803
|
+
const legacyPrefix = String.fromCharCode(114, 108, 104, 102) + '_';
|
|
804
|
+
if (!key.startsWith('tg_') && !key.startsWith(legacyPrefix)) {
|
|
805
|
+
console.error('❌ Invalid license key format. Keys start with "tg_".');
|
|
771
806
|
process.exit(1);
|
|
772
807
|
}
|
|
773
808
|
|
|
@@ -786,8 +821,8 @@ function pro() {
|
|
|
786
821
|
|
|
787
822
|
if (args.upgrade) {
|
|
788
823
|
const proDir = path.join(PKG_ROOT, 'pro');
|
|
789
|
-
const
|
|
790
|
-
if (!fs.existsSync(
|
|
824
|
+
const thumbgateDir = path.join(CWD, '.thumbgate');
|
|
825
|
+
if (!fs.existsSync(thumbgateDir)) fs.mkdirSync(thumbgateDir, { recursive: true });
|
|
791
826
|
|
|
792
827
|
const files = [
|
|
793
828
|
['constraints-pro.json', '10 RLAIF constraints'],
|
|
@@ -797,7 +832,7 @@ function pro() {
|
|
|
797
832
|
];
|
|
798
833
|
|
|
799
834
|
for (const [file] of files) {
|
|
800
|
-
fs.copyFileSync(path.join(proDir, file), path.join(
|
|
835
|
+
fs.copyFileSync(path.join(proDir, file), path.join(thumbgateDir, file));
|
|
801
836
|
}
|
|
802
837
|
|
|
803
838
|
console.log('\n✅ Pro configs installed to .thumbgate/');
|
|
@@ -963,7 +998,7 @@ function obsidianExport() {
|
|
|
963
998
|
const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
964
999
|
|
|
965
1000
|
const vaultPath = args['vault-path'] || process.env.THUMBGATE_OBSIDIAN_VAULT_PATH || '';
|
|
966
|
-
const outputSubdir = args['output-dir'] || 'AI-Memories/
|
|
1001
|
+
const outputSubdir = args['output-dir'] || 'AI-Memories/thumbgate';
|
|
967
1002
|
let outputDir;
|
|
968
1003
|
if (vaultPath) {
|
|
969
1004
|
outputDir = path.join(vaultPath, outputSubdir);
|
|
@@ -996,7 +1031,7 @@ function obsidianExport() {
|
|
|
996
1031
|
function rules() {
|
|
997
1032
|
const args = parseArgs(process.argv.slice(3));
|
|
998
1033
|
const { writePreventionRules } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
999
|
-
const outPath = args.output || path.join(CWD, '.
|
|
1034
|
+
const outPath = args.output || path.join(CWD, '.thumbgate', 'prevention-rules.md');
|
|
1000
1035
|
const result = writePreventionRules(outPath, Number(args.min || 2));
|
|
1001
1036
|
console.log(`Wrote prevention rules to ${result.path}`);
|
|
1002
1037
|
}
|
|
@@ -1134,6 +1169,75 @@ function install() {
|
|
|
1134
1169
|
}
|
|
1135
1170
|
}
|
|
1136
1171
|
|
|
1172
|
+
async function gateCheck() {
|
|
1173
|
+
const payload = readStdinText();
|
|
1174
|
+
const input = payload ? JSON.parse(payload) : {};
|
|
1175
|
+
const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
1176
|
+
const output = await gatesEngine.runAsync(input);
|
|
1177
|
+
process.stdout.write(output + '\n');
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function cacheUpdate() {
|
|
1181
|
+
const payload = readStdinText();
|
|
1182
|
+
const { updateCacheFromEvent } = require(path.join(PKG_ROOT, 'scripts', 'hook-thumbgate-cache-updater'));
|
|
1183
|
+
updateCacheFromEvent(payload ? JSON.parse(payload) : {});
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
function statuslineRender() {
|
|
1187
|
+
const payload = readStdinText();
|
|
1188
|
+
const output = execFileSync('bash', [path.join(PKG_ROOT, 'scripts', 'statusline.sh')], {
|
|
1189
|
+
encoding: 'utf8',
|
|
1190
|
+
input: payload,
|
|
1191
|
+
env: process.env,
|
|
1192
|
+
});
|
|
1193
|
+
process.stdout.write(output);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function hookAutoCapture() {
|
|
1197
|
+
const prompt = process.env.CLAUDE_USER_PROMPT || process.env.THUMBGATE_USER_PROMPT || readStdinText().trim();
|
|
1198
|
+
const { evaluatePromptGuard } = require(path.join(PKG_ROOT, 'scripts', 'prompt-guard'));
|
|
1199
|
+
const { processInlineFeedback, formatCliOutput } = require(path.join(PKG_ROOT, 'scripts', 'cli-feedback'));
|
|
1200
|
+
const { recordConversationEntry, readRecentConversationWindow } = require(path.join(PKG_ROOT, 'scripts', 'feedback-history-distiller'));
|
|
1201
|
+
|
|
1202
|
+
recordConversationEntry({
|
|
1203
|
+
author: 'user',
|
|
1204
|
+
text: prompt,
|
|
1205
|
+
source: 'claude_user_prompt',
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
const guardResult = evaluatePromptGuard(prompt);
|
|
1209
|
+
if (guardResult) {
|
|
1210
|
+
process.stdout.write(`${JSON.stringify(guardResult)}\n`);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const lower = prompt.toLowerCase();
|
|
1215
|
+
const isUp = /(thumbs?\s*up|that worked|looks good|nice work|perfect|good job)/i.test(lower);
|
|
1216
|
+
const isDown = /(thumbs?\s*down|that failed|that was wrong|fix this)/i.test(lower);
|
|
1217
|
+
if (!isUp && !isDown) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const signal = isDown ? 'down' : 'up';
|
|
1222
|
+
const conversationWindow = readRecentConversationWindow({ limit: 8 });
|
|
1223
|
+
const result = processInlineFeedback({
|
|
1224
|
+
signal,
|
|
1225
|
+
context: prompt,
|
|
1226
|
+
chatHistory: signal === 'down'
|
|
1227
|
+
? conversationWindow.map((entry) => ({ role: entry.author === 'assistant' ? 'assistant' : 'user', content: entry.text || '' }))
|
|
1228
|
+
: undefined,
|
|
1229
|
+
whatWentWrong: signal === 'down' ? prompt : undefined,
|
|
1230
|
+
whatWorked: signal === 'up' ? prompt : undefined,
|
|
1231
|
+
});
|
|
1232
|
+
process.stdout.write(formatCliOutput(result) + '\n');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
function sessionStart() {
|
|
1236
|
+
const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1237
|
+
const { refreshStatuslineCache } = require(path.join(PKG_ROOT, 'scripts', 'hook-thumbgate-cache-updater'));
|
|
1238
|
+
refreshStatuslineCache(analyzeFeedback());
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1137
1241
|
function installMcp() {
|
|
1138
1242
|
const { installMcp: doInstall, parseFlags } = require(path.join(PKG_ROOT, 'scripts', 'install-mcp'));
|
|
1139
1243
|
const flags = parseFlags(process.argv.slice(3));
|
|
@@ -1186,6 +1290,11 @@ function help() {
|
|
|
1186
1290
|
console.log(' --dry-run Preview hook changes without writing');
|
|
1187
1291
|
console.log(' install-mcp Install ThumbGate MCP server into Claude Code settings (--project for local)');
|
|
1188
1292
|
console.log(' serve Start MCP server (stdio) — for claude/codex/gemini mcp add');
|
|
1293
|
+
console.log(' gate-check Internal: evaluate a PreToolUse payload from stdin');
|
|
1294
|
+
console.log(' cache-update Internal: refresh the Claude statusline cache from stdin');
|
|
1295
|
+
console.log(' statusline-render Internal: render the ThumbGate Claude status line');
|
|
1296
|
+
console.log(' hook-auto-capture Internal: process Claude UserPromptSubmit feedback');
|
|
1297
|
+
console.log(' session-start Internal: refresh local ThumbGate session cache');
|
|
1189
1298
|
console.log(' capture [flags] Capture feedback (--feedback=up|down --context="..." --tags="...")');
|
|
1190
1299
|
console.log(' stats Show feedback analytics + Revenue-at-Risk');
|
|
1191
1300
|
console.log(' cfo Show hosted billing summary when configured, else local fallback JSON');
|
|
@@ -1201,7 +1310,7 @@ function help() {
|
|
|
1201
1310
|
console.log(' export-databricks Export feedback logs + proof artifacts as a Databricks-ready analytics bundle');
|
|
1202
1311
|
console.log(' obsidian-export Export all feedback data as interlinked Obsidian markdown notes');
|
|
1203
1312
|
console.log(' --vault-path=PATH Obsidian vault path (or set THUMBGATE_OBSIDIAN_VAULT_PATH)');
|
|
1204
|
-
console.log(' --output-dir=DIR Output subdirectory (default: AI-Memories/
|
|
1313
|
+
console.log(' --output-dir=DIR Output subdirectory (default: AI-Memories/thumbgate)');
|
|
1205
1314
|
console.log(' rules Generate prevention rules from repeated failures');
|
|
1206
1315
|
console.log(' optimize [PRO] Prune CLAUDE.md and migrate manual rules to Pre-Action Gates');
|
|
1207
1316
|
console.log(' force-gate <PATTERN> Immediately create a blocking gate from a pattern');
|
|
@@ -1216,9 +1325,10 @@ function help() {
|
|
|
1216
1325
|
console.log(' funnel Show marketing & revenue conversion funnel analytics');
|
|
1217
1326
|
console.log(' pulse Show real-time GTM velocity and Mission Control summary');
|
|
1218
1327
|
console.log(' dispatch Dispatch-safe brief — metrics, gates, and read-only prompt templates');
|
|
1328
|
+
console.log(' gate-check PreToolUse hook: reads tool JSON from stdin, outputs gate verdict');
|
|
1219
1329
|
console.log(' gate-stats Show gate statistics — active gates, blocks, warns, time saved');
|
|
1220
1330
|
console.log(' analytics Unified ThumbGate analytics snapshot (npm, GitHub, landing page)');
|
|
1221
|
-
console.log(' start-api Start the
|
|
1331
|
+
console.log(' start-api Start the ThumbGate HTTPS API server');
|
|
1222
1332
|
console.log(' help Show this help message');
|
|
1223
1333
|
console.log('');
|
|
1224
1334
|
console.log('Examples:');
|
|
@@ -1255,6 +1365,24 @@ switch (COMMAND) {
|
|
|
1255
1365
|
case 'mcp':
|
|
1256
1366
|
serve();
|
|
1257
1367
|
break;
|
|
1368
|
+
case 'gate-check':
|
|
1369
|
+
gateCheck().catch((err) => {
|
|
1370
|
+
console.error(err && err.message ? err.message : err);
|
|
1371
|
+
process.exit(1);
|
|
1372
|
+
});
|
|
1373
|
+
break;
|
|
1374
|
+
case 'cache-update':
|
|
1375
|
+
cacheUpdate();
|
|
1376
|
+
break;
|
|
1377
|
+
case 'statusline-render':
|
|
1378
|
+
statuslineRender();
|
|
1379
|
+
break;
|
|
1380
|
+
case 'hook-auto-capture':
|
|
1381
|
+
hookAutoCapture();
|
|
1382
|
+
break;
|
|
1383
|
+
case 'session-start':
|
|
1384
|
+
sessionStart();
|
|
1385
|
+
break;
|
|
1258
1386
|
case 'capture':
|
|
1259
1387
|
case 'feedback':
|
|
1260
1388
|
capture();
|
|
@@ -1420,6 +1548,27 @@ switch (COMMAND) {
|
|
|
1420
1548
|
case 'dispatch-brief':
|
|
1421
1549
|
dispatchBrief();
|
|
1422
1550
|
break;
|
|
1551
|
+
case 'gate-check': {
|
|
1552
|
+
// PreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict
|
|
1553
|
+
// Used by: generate-pretool-hook.sh → npx thumbgate gate-check
|
|
1554
|
+
const { run: gateRun, runAsync: gateRunAsync } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
|
|
1555
|
+
let stdinData = '';
|
|
1556
|
+
process.stdin.setEncoding('utf8');
|
|
1557
|
+
process.stdin.on('data', (chunk) => { stdinData += chunk; });
|
|
1558
|
+
process.stdin.on('end', async () => {
|
|
1559
|
+
try {
|
|
1560
|
+
const input = JSON.parse(stdinData);
|
|
1561
|
+
const output = await gateRunAsync(input);
|
|
1562
|
+
process.stdout.write(output + '\n');
|
|
1563
|
+
process.exit(0);
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
process.stderr.write(`gate-check error: ${err.message}\n`);
|
|
1566
|
+
process.stdout.write(JSON.stringify({}) + '\n');
|
|
1567
|
+
process.exit(0);
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
break;
|
|
1571
|
+
}
|
|
1423
1572
|
case 'gate-stats':
|
|
1424
1573
|
gateStats();
|
|
1425
1574
|
break;
|
|
@@ -1444,8 +1593,8 @@ switch (COMMAND) {
|
|
|
1444
1593
|
break;
|
|
1445
1594
|
case 'checkin': {
|
|
1446
1595
|
// User check-in command — asks how it's going after install
|
|
1447
|
-
const
|
|
1448
|
-
const configPath = path.join(
|
|
1596
|
+
const thumbgateDir = path.join(CWD, '.thumbgate');
|
|
1597
|
+
const configPath = path.join(thumbgateDir, 'config.json');
|
|
1449
1598
|
let installAge = 'unknown';
|
|
1450
1599
|
if (fs.existsSync(configPath)) {
|
|
1451
1600
|
try {
|
|
@@ -1465,8 +1614,8 @@ switch (COMMAND) {
|
|
|
1465
1614
|
console.log('Or email: iganapolsky@gmail.com\n');
|
|
1466
1615
|
|
|
1467
1616
|
// Log the check-in event
|
|
1468
|
-
const checkinLog = path.join(
|
|
1469
|
-
if (fs.existsSync(
|
|
1617
|
+
const checkinLog = path.join(thumbgateDir, 'checkin-log.jsonl');
|
|
1618
|
+
if (fs.existsSync(thumbgateDir)) {
|
|
1470
1619
|
const event = { event: 'checkin_shown', at: new Date().toISOString(), installAge };
|
|
1471
1620
|
fs.appendFileSync(checkinLog, JSON.stringify(event) + '\n');
|
|
1472
1621
|
}
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"id": "rotated_billing_key_survives_dashboard",
|
|
22
22
|
"title": "E2E: rotated billing key disables the old key and keeps dashboard access alive"
|
|
23
23
|
},
|
|
24
|
+
{
|
|
25
|
+
"id": "governance_scope_and_approval_http",
|
|
26
|
+
"title": "E2E: governance task scope and protected approvals persist over the HTTP surface"
|
|
27
|
+
},
|
|
24
28
|
{
|
|
25
29
|
"id": "history_aware_feedback_distillation",
|
|
26
30
|
"title": "E2E: vague thumbs-down distills a lesson and preserves linked follow-up context"
|