thumbgate 1.27.16 → 1.27.18

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.
Files changed (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -215
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +98 -228
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -123
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. package/scripts/visitor-journey.js +0 -172
package/bin/cli.js CHANGED
@@ -23,17 +23,14 @@
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
27
26
  */
28
27
 
29
28
  'use strict';
30
29
 
31
30
  const fs = require('fs');
32
- const os = require('os');
33
31
  const path = require('path');
34
32
  const crypto = require('crypto');
35
- const http = require('http');
36
- const { execSync, execFileSync, execFile, spawn, spawnSync } = require('child_process');
33
+ const { execSync, execFileSync } = require('child_process');
37
34
  const {
38
35
  codexAutoUpdateCliEntry,
39
36
  codexAutoUpdateMcpEntry,
@@ -43,6 +40,13 @@ const {
43
40
  resolveMcpEntry,
44
41
  } = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
45
42
  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'));
46
50
  const {
47
51
  PRO_MONTHLY_PAYMENT_LINK,
48
52
  PRO_PRICE_LABEL,
@@ -52,48 +56,8 @@ const COMMAND = process.argv[2];
52
56
  const CWD = process.cwd();
53
57
  const PKG_ROOT = path.join(__dirname, '..');
54
58
 
55
- const PRO_URL = 'https://thumbgate.ai';
59
+ const PRO_URL = 'https://thumbgate-production.up.railway.app';
56
60
  const PRO_CHECKOUT_URL = PRO_MONTHLY_PAYMENT_LINK;
57
- // Mirrors scripts/rate-limiter.js TRIAL_DAYS — must stay in sync. Gitar
58
- // caught a 14-vs-7 mismatch on PR #2337 (banner said "7-day trial" but
59
- // the date label was computed 14 days out).
60
- const TRIAL_DAYS = 7;
61
-
62
- function checkoutUrlFor(source, content) {
63
- try {
64
- const base = content === 'capture_feedback'
65
- ? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
66
- : PRO_CHECKOUT_URL;
67
- const url = new URL(base);
68
- url.searchParams.set('utm_source', source || 'cli');
69
- url.searchParams.set('utm_medium', 'cli');
70
- url.searchParams.set('utm_campaign', 'pro_conversion');
71
- if (content) url.searchParams.set('utm_content', content);
72
- return url.toString();
73
- } catch (_) {
74
- return content === 'capture_feedback'
75
- ? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
76
- : PRO_CHECKOUT_URL;
77
- }
78
- }
79
-
80
- function pricingUrlFor(source, content) {
81
- try {
82
- const url = new URL('/pricing', PRO_URL);
83
- url.searchParams.set('utm_source', source || 'cli');
84
- url.searchParams.set('utm_medium', 'cli');
85
- url.searchParams.set('utm_campaign', 'pro_education');
86
- if (content) url.searchParams.set('utm_content', content);
87
- return url.toString();
88
- } catch (_) {
89
- return `${PRO_URL}/pricing`;
90
- }
91
- }
92
-
93
- function trialDeadlineLabel(now = new Date()) {
94
- const deadline = new Date(now.getTime() + (TRIAL_DAYS * 24 * 60 * 60 * 1000));
95
- return deadline.toISOString().slice(0, 10);
96
- }
97
61
 
98
62
  function upgradeNudge() {
99
63
  if (process.env.THUMBGATE_NO_NUDGE === '1') return;
@@ -101,13 +65,11 @@ function upgradeNudge() {
101
65
  const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
102
66
  if (isProTier()) return;
103
67
  } catch (_) { return; }
104
- const pricingUrl = pricingUrlFor('cli_upgrade_nudge', COMMAND || 'general');
105
68
  process.stderr.write(
106
69
  '\n Team rollout: start with the Workflow Hardening Sprint\n' +
107
- ' https://thumbgate.ai/#workflow-sprint-intake\n' +
70
+ ' https://thumbgate-production.up.railway.app/#workflow-sprint-intake\n' +
108
71
  `\n Solo side lane: Pro — ${PRO_PRICE_LABEL}\n` +
109
- ' Keeps lessons, rules, and the dashboard synced across machines and agent runtimes.\n' +
110
- ` ${pricingUrl}\n\n`
72
+ ` ${PRO_CHECKOUT_URL}\n\n`
111
73
  );
112
74
  }
113
75
 
@@ -164,7 +126,7 @@ function telemetryPing(installId) {
164
126
  timestamp: new Date().toISOString(),
165
127
  };
166
128
  appendLocalTelemetry(payloadObject);
167
- const apiUrl = process.env.THUMBGATE_API_URL || 'https://thumbgate.ai';
129
+ const apiUrl = process.env.THUMBGATE_API_URL || 'https://thumbgate-production.up.railway.app';
168
130
  const payload = JSON.stringify(payloadObject);
169
131
  try {
170
132
  const url = new URL('/v1/telemetry/ping', apiUrl);
@@ -183,47 +145,25 @@ function proNudge(context) {
183
145
  const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
184
146
  if (isProTier()) return;
185
147
  } catch (_) { /* if rate-limiter is unavailable, fall through and nudge */ }
186
- const checkoutUrl = checkoutUrlFor('cli_nudge', context || COMMAND || 'general');
187
- const pricingUrl = pricingUrlFor('cli_nudge', context || COMMAND || 'general');
188
148
  const messages = [
189
- `\n 💡 Pro (${PRO_PRICE_LABEL}): keep lessons, rules, and dashboard state synced across machines and agent runtimes.\n See pricing: ${pricingUrl}\n`,
190
- `\n 💡 You just taught ThumbGate something locally. Pro keeps that lesson alive on every laptop, CI box, and agent runtime.\n See pricing: ${pricingUrl}\n`,
191
- `\n 💡 ThumbGate Pro syncs lessons/rules across Claude, Codex, Cursor, containers, and CI. ${PRO_PRICE_LABEL}.\n Start Pro: ${checkoutUrl}\n`,
149
+ `\n 💡 Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
150
+ `\n 💡 Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
151
+ `\n 💡 ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\n ${PRO_CHECKOUT_URL}\n`,
192
152
  ];
193
153
  // Rotate message daily — no Math.random (security policy)
194
154
  const msg = messages[Math.floor(Date.now() / 86400000) % messages.length];
195
155
  process.stderr.write(msg);
196
156
  }
197
157
 
198
- function limitNudge(action, limitResult = {}) {
158
+ function limitNudge(action) {
199
159
  if (process.env.THUMBGATE_NO_NUDGE === '1') return;
200
- const checkoutUrl = checkoutUrlFor('cli_limit', action);
201
- const usageLine = Number.isFinite(limitResult.used) && Number.isFinite(limitResult.limit)
202
- ? ` Usage: ${limitResult.used}/${limitResult.limit} (${limitResult.limitType || 'limit'}).\n`
203
- : '';
204
- const reason = limitResult.message
205
- ? String(limitResult.message).split('\n')[0]
206
- : 'Free tier limit reached.';
207
160
  process.stderr.write(
208
- `\n 🔒 ${reason}\n` +
209
- usageLine +
210
- ` Upgrade to Pro (${PRO_PRICE_LABEL}) to continue ${action.replace(/_/g, ' ')}:\n` +
211
- ` ${checkoutUrl}\n\n`
161
+ `\n ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro\n` +
162
+ ` ${action} daily limit reached. Upgrade to Pro for unlimited usage — ${PRO_PRICE_LABEL}:\n` +
163
+ ` ${PRO_CHECKOUT_URL}\n\n`
212
164
  );
213
165
  }
214
166
 
215
- function printInitConversionPrompt(email) {
216
- if (process.env.THUMBGATE_NO_NUDGE === '1') return;
217
- const checkoutUrl = checkoutUrlFor('cli_init', email ? 'init_email' : 'init_no_email');
218
- console.log('');
219
- console.log(' ┌──────────────────────────────────────────────────────────┐');
220
- console.log(` │ 7-day Pro trial active through ${trialDeadlineLabel()}. │`);
221
- console.log(' │ Pro keeps lessons/rules/dashboard synced everywhere. │');
222
- console.log(' │ Add onboarding: npx thumbgate init --email you@company.com │');
223
- console.log(` │ Upgrade: ${checkoutUrl}`);
224
- console.log(' └──────────────────────────────────────────────────────────┘');
225
- }
226
-
227
167
  function parseArgs(argv) {
228
168
  const args = {};
229
169
  argv.forEach((arg, index) => {
@@ -245,73 +185,6 @@ function parseArgs(argv) {
245
185
  return args;
246
186
  }
247
187
 
248
- function probeDash(port) {
249
- return new Promise((resolve) => {
250
- const req = http.get({ hostname: '127.0.0.1', port, path: '/health', timeout: 750 }, (res) => {
251
- res.resume();
252
- resolve(res.statusCode >= 200 && res.statusCode < 500);
253
- });
254
- req.on('error', () => resolve(false));
255
- req.on('timeout', () => { req.destroy(); resolve(false); });
256
- });
257
- }
258
-
259
- async function waitDash(port, timeoutMs = 8000) {
260
- const deadline = Date.now() + timeoutMs;
261
- while (Date.now() < deadline) {
262
- if (await probeDash(port)) return true;
263
- await new Promise((r) => setTimeout(r, 250));
264
- }
265
- return false;
266
- }
267
-
268
- async function ensureDash(port) {
269
- if (await probeDash(port)) {
270
- return { started: false, pid: null };
271
- }
272
-
273
- const serverPath = path.join(PKG_ROOT, 'src', 'api', 'server.js');
274
- const child = spawn(process.execPath, [serverPath], {
275
- cwd: PKG_ROOT,
276
- detached: true,
277
- env: { ...process.env, PORT: String(port), THUMBGATE_ALLOW_INSECURE: process.env.THUMBGATE_ALLOW_INSECURE || 'true' },
278
- stdio: 'ignore',
279
- });
280
- child.unref();
281
-
282
- if (!(await waitDash(port))) {
283
- throw new Error('Dashboard API timeout');
284
- }
285
-
286
- return { started: true, pid: child.pid };
287
- }
288
-
289
- function openBrowser(url) {
290
- if (process.env.THUMBGATE_DASHBOARD_NO_OPEN === '1') {
291
- console.log(`Ready: ${url}`);
292
- return Promise.resolve();
293
- }
294
- const [command, args] = ({ darwin: ['open', [url]], win32: ['cmd', ['/c', 'start', '', url]] }[process.platform] || ['xdg-open', [url]]);
295
- return new Promise((resolve, reject) => {
296
- execFile(command, args, (err) => (err ? reject(err) : resolve()));
297
- });
298
- }
299
-
300
- function parseTtlMs(value, fallbackMs = 5 * 60 * 1000) {
301
- if (value === undefined || value === null || value === true || value === '') return fallbackMs;
302
- const raw = String(value).trim().toLowerCase();
303
- const match = raw.match(/^(\d+(?:\.\d+)?)(ms|s|m|h)?$/);
304
- if (!match) return fallbackMs;
305
- const amount = Number(match[1]);
306
- if (!Number.isFinite(amount) || amount <= 0) return fallbackMs;
307
- const unit = match[2] || 'ms';
308
- const factor = unit === 'h' ? 60 * 60 * 1000
309
- : unit === 'm' ? 60 * 1000
310
- : unit === 's' ? 1000
311
- : 1;
312
- return Math.round(amount * factor);
313
- }
314
-
315
188
  function readStdinText() {
316
189
  try {
317
190
  return fs.readFileSync(0, 'utf8');
@@ -522,24 +395,64 @@ function whichExists(cmd) {
522
395
 
523
396
  function setupClaude() {
524
397
  const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code', 'project');
525
- const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
526
- const hookResult = wireHooks({ agent: 'claude-code' });
527
398
 
528
- if (hookResult.error) {
529
- console.log(` Claude Code hooks: ${hookResult.error}`);
530
- return mcpChanged;
399
+ // Upsert Stop hook into .claude/settings.json for autonomous self-scoring
400
+ const settingsPath = path.join(CWD, '.claude', 'settings.json');
401
+ const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
402
+
403
+ let settings = { hooks: {} };
404
+ if (fs.existsSync(settingsPath)) {
405
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
531
406
  }
407
+ settings.hooks = settings.hooks || {};
532
408
 
533
- if (!hookResult.changed) {
534
- console.log(` Claude Code hooks: already configured at ${hookResult.settingsPath}`);
535
- } else {
536
- for (const h of hookResult.added) {
537
- console.log(` Claude Code: wired ${h.lifecycle} hook`);
538
- }
539
- console.log(` Claude Code settings: ${hookResult.settingsPath}`);
409
+ const stopAlreadyPresent = (settings.hooks.Stop || [])
410
+ .some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
411
+
412
+ let hooksChanged = false;
413
+ if (!stopAlreadyPresent) {
414
+ settings.hooks.Stop = settings.hooks.Stop || [];
415
+ settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
416
+ hooksChanged = true;
417
+ console.log(' Claude Code: installed Stop hook');
418
+ }
419
+
420
+ // Upsert PostToolUse hook for ThumbGate statusline cache updates
421
+ const cacheHookCommand = cacheUpdateHookCommand();
422
+ const originalPostToolUseCount = (settings.hooks.PostToolUse || []).length;
423
+ settings.hooks.PostToolUse = (settings.hooks.PostToolUse || []).filter(
424
+ (entry) => !(entry.hooks || []).some((h) => h.command && h.command !== cacheHookCommand && /(hook-thumbgate-cache-updater|cache-update\b)/.test(h.command))
425
+ );
426
+ if (settings.hooks.PostToolUse.length !== originalPostToolUseCount) {
427
+ hooksChanged = true;
428
+ }
429
+ const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
430
+ .some(entry => (entry.hooks || []).some(h => h.command === cacheHookCommand || (h.command && h.command.includes('cache-update'))));
431
+
432
+ if (!cacheAlreadyPresent) {
433
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
434
+ settings.hooks.PostToolUse.push({
435
+ matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
436
+ hooks: [{ type: 'command', command: cacheHookCommand }]
437
+ });
438
+ hooksChanged = true;
439
+ console.log(' Claude Code: installed ThumbGate cache updater hook');
540
440
  }
541
441
 
542
- return mcpChanged || hookResult.changed;
442
+ // Upsert statusLine for ThumbGate feedback display
443
+ const statuslineScript = statuslineCommand();
444
+ if (!settings.statusLine || settings.statusLine.command !== statuslineScript) {
445
+ settings.statusLine = { type: 'command', command: statuslineScript };
446
+ hooksChanged = true;
447
+ console.log(' Claude Code: installed ThumbGate status line');
448
+ }
449
+
450
+ if (hooksChanged) {
451
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
452
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
453
+ }
454
+
455
+ return mcpChanged || hooksChanged;
543
456
  }
544
457
 
545
458
  function setupCodex() {
@@ -570,20 +483,6 @@ function setupCodex() {
570
483
  }
571
484
 
572
485
  function setupGemini() {
573
- // Try to import custom commands as a Gemini plugin if the CLI is installed
574
- const { execSync } = require('child_process');
575
- let pluginImported = false;
576
- for (const binName of ['agy', 'gemini']) {
577
- try {
578
- execSync(`${binName} plugin import "${PKG_ROOT}" --force`, { stdio: 'ignore' });
579
- console.log(` Gemini: imported thumbgate plugin via ${binName}`);
580
- pluginImported = true;
581
- break;
582
- } catch (err) {
583
- // ignore errors if command doesn't exist or fails
584
- }
585
- }
586
-
587
486
  const settingsPath = path.join(HOME, '.gemini', 'settings.json');
588
487
  if (fs.existsSync(settingsPath)) {
589
488
  const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
@@ -604,14 +503,13 @@ function setupGemini() {
604
503
  }
605
504
  }
606
505
 
607
- if (!changed) return pluginImported;
506
+ if (!changed) return false;
608
507
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
609
508
  console.log(' Gemini: updated ~/.gemini/settings.json');
610
509
  return true;
611
510
  }
612
511
  // Fallback: project-level .gemini/settings.json
613
- const mcpChanged = mergeMcpJson(path.join(CWD, '.gemini', 'settings.json'), 'Gemini', 'project');
614
- return pluginImported || mcpChanged;
512
+ return mergeMcpJson(path.join(CWD, '.gemini', 'settings.json'), 'Gemini', 'project');
615
513
  }
616
514
 
617
515
  function setupAmp() {
@@ -699,104 +597,6 @@ function detectAgent(projectDir) {
699
597
  return null;
700
598
  }
701
599
 
702
- async function setupVertex(options = {}) {
703
- const { execSync } = require('child_process');
704
- const dryRun = options.dryRun === true || options['dry-run'] === true;
705
- console.log(`\nthumbgate setup-vertex v${pkgVersion()}`);
706
- console.log(' Zero-friction Google Cloud & Vertex AI onboarding...');
707
- if (dryRun) {
708
- console.log(' Dry run: will detect gcloud account/project, but will not enable services or write .env.');
709
- }
710
- console.log('');
711
-
712
- // 1. Detect gcloud CLI
713
- let activeAccount = '';
714
- let activeProject = '';
715
- try {
716
- activeAccount = execSync('gcloud config get-value account', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
717
- activeProject = execSync('gcloud config get-value project', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
718
- } catch (_) {
719
- console.log(' ⚠️ Google Cloud SDK (gcloud CLI) not detected or not logged in.');
720
- console.log(' To automate setup, install the Google Cloud CLI and run: gcloud auth login');
721
- console.log(' Otherwise, manually set the following variables in your .env file:');
722
- console.log(' THUMBGATE_PROVIDER_MODE=vertex');
723
- console.log(' VERTEX_PROJECT_ID=<your-gcp-project-id>');
724
- console.log('');
725
- return;
726
- }
727
-
728
- if (!activeAccount) {
729
- console.log(' ⚠️ No active Google Cloud account set. Run: gcloud auth login');
730
- return;
731
- }
732
-
733
- console.log(` Active Account : \x1b[36m${activeAccount}\x1b[0m`);
734
- if (!activeProject) {
735
- console.log(' ⚠️ No active Google Cloud project set. Run: gcloud config set project <PROJECT_ID>');
736
- return;
737
- }
738
- console.log(` Active Project : \x1b[36m${activeProject}\x1b[0m`);
739
-
740
- // Validate project ID matches GCP format before use in shell
741
- if (!/^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(activeProject)) {
742
- console.log(' ⚠️ Invalid GCP project ID format. Aborting.');
743
- return;
744
- }
745
-
746
- if (dryRun) {
747
- console.log(` DRY-RUN would enable Vertex AI API for project: ${activeProject}`);
748
- console.log(` DRY-RUN would write THUMBGATE_PROVIDER_MODE=vertex and VERTEX_PROJECT_ID=${activeProject} to .env.`);
749
- console.log('');
750
- console.log(' Dry run complete. Re-run without --dry-run to apply these changes.');
751
- return;
752
- }
753
-
754
- // 2. Auto-enable Vertex AI API
755
- console.log(' ⚙️ Enabling Vertex AI API in your project (this can take a few seconds)...');
756
- try {
757
- execSync(`gcloud services enable aiplatform.googleapis.com --project=${activeProject}`, { stdio: 'inherit' });
758
- console.log(' ✅ Vertex AI API successfully enabled.');
759
- } catch (err) {
760
- console.log(' ⚠️ Could not programmatically enable Vertex AI API. Please make sure your billing is open.');
761
- }
762
-
763
- // 3. Write env config to .env
764
- const envPath = path.join(process.cwd(), '.env');
765
- let envContent = '';
766
- if (fs.existsSync(envPath)) {
767
- envContent = fs.readFileSync(envPath, 'utf8');
768
- }
769
-
770
- // Helper to update or append env values
771
- function updateEnvKey(content, key, value) {
772
- const regex = new RegExp(`^${key}=.*$`, 'm');
773
- if (regex.test(content)) {
774
- return content.replace(regex, `${key}=${value}`);
775
- }
776
- return content.trim() + `\n${key}=${value}\n`;
777
- }
778
-
779
- let updatedContent = updateEnvKey(envContent, 'THUMBGATE_PROVIDER_MODE', 'vertex');
780
- updatedContent = updateEnvKey(updatedContent, 'VERTEX_PROJECT_ID', activeProject);
781
-
782
- fs.writeFileSync(envPath, updatedContent, 'utf8');
783
- console.log(' ✅ Wrote configuration to local .env file.');
784
-
785
- // 4. Print gorgeous success activation box
786
- console.log('');
787
- console.log(' ╭──────────────────────────────────────────────────────────╮');
788
- console.log(' │ Vertex AI Setup Complete │');
789
- console.log(' │ │');
790
- console.log(' │ ThumbGate wrote local Vertex routing config. │');
791
- console.log(' │ This does not create or verify a Dialogflow CX agent. │');
792
- console.log(' │ Verify DFCX with the console or Dialogflow CX REST API. │');
793
- console.log(' │ │');
794
- console.log(' │ Try a test run: │');
795
- console.log(' │ npx thumbgate feedback-self-test │');
796
- console.log(' ╰──────────────────────────────────────────────────────────╯');
797
- console.log('');
798
- }
799
-
800
600
  function quickStart() {
801
601
  const qsArgs = parseArgs(process.argv.slice(3));
802
602
  const projectDir = process.cwd();
@@ -857,28 +657,8 @@ function quickStart() {
857
657
  console.log('');
858
658
  }
859
659
 
860
- // Activation walkthrough (guided first rule + live demonstrated block).
861
- // Implementation lives in scripts/activation-quickstart.js so it can be unit
862
- // tested without executing the CLI's top-level command switch. `init` is
863
- // deliberately untouched — this is an additive, separate command.
864
- function quickstart() {
865
- return require(path.join(PKG_ROOT, 'scripts', 'activation-quickstart')).quickstart();
866
- }
867
-
868
660
  function init(cliArgs = parseArgs(process.argv.slice(3))) {
869
661
  const args = { ...cliArgs };
870
- if (args.help || args.h) {
871
- console.log('Usage: npx thumbgate init [--agent <name>] [--wire-hooks] [--email you@company.com]');
872
- console.log('');
873
- console.log('Scaffold ThumbGate in the current project and wire detected agent integrations.');
874
- console.log('');
875
- console.log('Options:');
876
- console.log(' --agent <name> Wire a specific agent: claude-code, codex, gemini, amp, cursor, cline');
877
- console.log(' --wire-hooks Wire hooks only; do not scaffold project files');
878
- console.log(' --email <email> Subscribe installer to the setup guide and trial reminders');
879
- console.log(' --dry-run Show hook changes without writing them');
880
- return;
881
- }
882
662
 
883
663
  // --wire-hooks only mode: skip scaffolding, just wire hooks
884
664
  if (args['wire-hooks']) {
@@ -938,43 +718,12 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
938
718
  // Always create .mcp.json (project-level MCP config used by Claude, Codex, Cursor)
939
719
  mergeMcpJson(path.join(CWD, '.mcp.json'), 'MCP');
940
720
 
941
- // Copy custom slash commands (.claude/commands/*.md) to the project's config directories
942
- const pkgCommandsDir = path.join(PKG_ROOT, '.claude', 'commands');
943
- if (fs.existsSync(pkgCommandsDir)) {
944
- const targets = [
945
- path.join(CWD, '.claude', 'commands'),
946
- path.join(CWD, '.gemini', 'commands'),
947
- path.join(CWD, '.antigravitycli', 'commands')
948
- ];
949
- for (const projectCommandsDir of targets) {
950
- if (!fs.existsSync(projectCommandsDir)) {
951
- fs.mkdirSync(projectCommandsDir, { recursive: true });
952
- }
953
- try {
954
- const files = fs.readdirSync(pkgCommandsDir);
955
- for (const file of files) {
956
- if (file.endsWith('.md')) {
957
- fs.copyFileSync(path.join(pkgCommandsDir, file), path.join(projectCommandsDir, file));
958
- }
959
- }
960
- } catch (err) {
961
- console.log(` Failed to copy custom commands to ${path.relative(CWD, projectCommandsDir)}: ${err.message}`);
962
- }
963
- }
964
- console.log('Scaffolded custom slash commands directories (.claude, .gemini, .antigravitycli)');
965
- }
966
-
967
721
  // Auto-detect and configure platform-specific locations
968
722
  console.log('');
969
723
  console.log('Detecting platforms...');
970
724
  let configured = 0;
971
725
 
972
726
  const platforms = [
973
- { name: 'Claude Code', detect: [
974
- () => whichExists('claude'),
975
- () => fs.existsSync(path.join(HOME, '.claude')),
976
- () => fs.existsSync(path.join(CWD, '.claude')),
977
- ], setup: setupClaude },
978
727
  { name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
979
728
  { name: 'Gemini', detect: [() => whichExists('gemini'), () => fs.existsSync(path.join(HOME, '.gemini'))], setup: setupGemini },
980
729
  { name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
@@ -997,11 +746,8 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
997
746
  }
998
747
 
999
748
  // ChatGPT — cannot be automated
1000
- const chatgptSpec = [
1001
- path.join(PKG_ROOT, 'openapi', 'openapi.yaml'),
1002
- path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml'),
1003
- ].find((candidate) => fs.existsSync(candidate));
1004
- if (chatgptSpec) {
749
+ const chatgptSpec = path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml');
750
+ if (fs.existsSync(chatgptSpec)) {
1005
751
  const projectChatgptSpec = path.join(thumbgateDir, 'chatgpt-openapi.yaml');
1006
752
  fs.copyFileSync(chatgptSpec, projectChatgptSpec);
1007
753
  console.log(` ChatGPT: import ${path.relative(CWD, projectChatgptSpec)} in GPT Builder > Actions`);
@@ -1041,43 +787,16 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
1041
787
  }
1042
788
 
1043
789
  console.log('');
1044
- console.log(`✅ thumbgate v${pkgVersion()} initialized.`);
1045
- const onboardingEmail = typeof args.email === 'string'
1046
- ? args.email.trim()
1047
- : (typeof args['onboarding-email'] === 'string' ? args['onboarding-email'].trim() : '');
1048
- if (onboardingEmail) {
1049
- try {
1050
- execFileSync(process.execPath, [__filename, 'subscribe', onboardingEmail], {
1051
- cwd: CWD,
1052
- env: process.env,
1053
- stdio: 'inherit',
1054
- });
1055
- trackEvent('cli_init_email_subscribed', { command: 'init', source: 'init_email' });
1056
- } catch (_) {
1057
- console.log(` Retry onboarding email: npx thumbgate subscribe ${onboardingEmail}`);
1058
- }
1059
- }
790
+ console.log(`thumbgate v${pkgVersion()} initialized.`);
791
+ console.log('Run: npx thumbgate help');
1060
792
  trackEvent('cli_init', { command: 'init' });
1061
-
1062
- // ---------------------------------------------------------------------------
1063
- // Activation guide: the ONE thing the user should do next.
1064
- // 98.5% of init users never promote their first prevention rule.
1065
- // This is the funnel break — not conversion, not nudges — activation.
1066
- // ---------------------------------------------------------------------------
1067
- console.log('');
1068
- console.log(' ╭──────────────────────────────────────────────────────────╮');
1069
- console.log(' │ NEXT: Prove feedback capture works │');
1070
- console.log(' │ │');
1071
- console.log(' │ npx thumbgate feedback-self-test │');
1072
- console.log(' │ │');
1073
- console.log(' │ Then dogfood it in chat: │');
1074
- console.log(' │ thumbs down: agent skipped verification │');
1075
- console.log(' ╰──────────────────────────────────────────────────────────╯');
793
+ proNudge();
1076
794
  console.log('');
1077
- printInitConversionPrompt(onboardingEmail);
1078
- if (!onboardingEmail) {
1079
- console.log(' Get trial reminders: npx thumbgate init --email you@company.com');
1080
- }
795
+ console.log(' ┌──────────────────────────────────────────────────────────┐');
796
+ console.log(' │ Teams: shared enforcement, CI gates, audit trails │');
797
+ console.log(' │ One correction protects every agent on your team.');
798
+ console.log(' │ https://thumbgate-production.up.railway.app/pro │');
799
+ console.log(' └──────────────────────────────────────────────────────────┘');
1081
800
 
1082
801
  try {
1083
802
  const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
@@ -1107,7 +826,7 @@ function capture() {
1107
826
  const { getUsage } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1108
827
  const capLimit = checkLimit('capture_feedback');
1109
828
  if (!capLimit.allowed) {
1110
- limitNudge('capture_feedback', capLimit);
829
+ limitNudge('capture_feedback');
1111
830
  process.exit(1);
1112
831
  }
1113
832
  trackEvent('cli_capture', { command: 'capture' });
@@ -1122,88 +841,23 @@ function capture() {
1122
841
  return;
1123
842
  }
1124
843
 
1125
- // Parse pure positional arguments
1126
- const rawArgv = process.argv.slice(3);
1127
- const positionalArgs = [];
1128
- const BOOLEAN_FLAGS = new Set(['--json', '--verbose', '--quiet', '--dry-run', '--stats', '--summary', '--no-nudge', '--help']);
1129
- for (let i = 0; i < rawArgv.length; i++) {
1130
- const arg = rawArgv[i];
1131
- if (arg.startsWith('--')) {
1132
- if (!arg.includes('=') && !BOOLEAN_FLAGS.has(arg)) {
1133
- i++;
1134
- }
1135
- } else {
1136
- positionalArgs.push(arg);
1137
- }
1138
- }
1139
-
1140
- let signal = (args.feedback || '').toLowerCase();
1141
- let consumedSignalArgs = 0;
1142
- if (!signal && positionalArgs[0]) {
1143
- const { detectFeedbackSignal } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
1144
- const oneWord = positionalArgs[0];
1145
- const twoWords = positionalArgs.slice(0, 2).join(' ');
1146
- const detected = detectFeedbackSignal(twoWords) || detectFeedbackSignal(oneWord);
1147
- if (detected) {
1148
- signal = detected.signal;
1149
- consumedSignalArgs = detectFeedbackSignal(twoWords) ? Math.min(2, positionalArgs.length) : 1;
1150
- } else {
1151
- const firstPos = positionalArgs[0].toLowerCase();
1152
- if (['up', 'down', 'thumbsup', 'thumbsdown', 'thumbs_up', 'thumbs_down', 'positive', 'negative'].some(v => firstPos.includes(v))) {
1153
- signal = firstPos;
1154
- consumedSignalArgs = 1;
1155
- }
1156
- }
1157
- }
1158
-
844
+ const signal = (args.feedback || '').toLowerCase();
1159
845
  const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some(v => signal.includes(v)) ? 'up'
1160
846
  : ['down', 'thumbsdown', 'thumbs_down', 'negative'].some(v => signal.includes(v)) ? 'down'
1161
847
  : signal;
1162
848
 
1163
849
  if (normalized !== 'up' && normalized !== 'down') {
1164
- console.error('Missing or unrecognized --feedback=up|down (or positional argument)');
1165
- process.exit(1);
1166
- }
1167
-
1168
- const gateAction = (args.action || '').toLowerCase();
1169
- if (gateAction && !['block', 'approve', 'warn'].includes(gateAction)) {
1170
- console.error('Unrecognized --action. Use: block (default), approve, or warn');
850
+ console.error('Missing or unrecognized --feedback=up|down');
1171
851
  process.exit(1);
1172
852
  }
1173
853
 
1174
- let context = args.context || '';
1175
- if (!context && consumedSignalArgs > 0) {
1176
- context = positionalArgs.slice(consumedSignalArgs).join(' ');
1177
- } else if (!context && positionalArgs[1]) {
1178
- context = positionalArgs[1];
1179
- }
1180
-
1181
- let whatWentWrong = args['what-went-wrong'];
1182
- if (!whatWentWrong && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 1) {
1183
- whatWentWrong = positionalArgs.slice(consumedSignalArgs + 1).join(' ');
1184
- } else if (!whatWentWrong && positionalArgs[2]) {
1185
- whatWentWrong = positionalArgs[2];
1186
- } else if (!whatWentWrong && normalized === 'down' && context) {
1187
- whatWentWrong = context;
1188
- }
1189
-
1190
- let whatToChange = args['what-to-change'];
1191
- if (!whatToChange && consumedSignalArgs > 0 && positionalArgs.length > consumedSignalArgs + 2) {
1192
- whatToChange = positionalArgs.slice(consumedSignalArgs + 2).join(' ');
1193
- } else if (!whatToChange && positionalArgs[3]) {
1194
- whatToChange = positionalArgs[3];
1195
- } else if (!whatToChange && normalized === 'down' && context) {
1196
- whatToChange = `avoid: ${context}`;
1197
- }
1198
-
1199
854
  const result = captureFeedback({
1200
855
  signal: normalized,
1201
- context: context,
1202
- whatWentWrong: whatWentWrong,
1203
- whatToChange: whatToChange,
856
+ context: args.context || '',
857
+ whatWentWrong: args['what-went-wrong'],
858
+ whatToChange: args['what-to-change'],
1204
859
  whatWorked: args['what-worked'],
1205
860
  tags: args.tags,
1206
- gateAction: gateAction || undefined,
1207
861
  });
1208
862
 
1209
863
  if (result.accepted) {
@@ -1231,21 +885,10 @@ function capture() {
1231
885
  const pct = Math.round((capLimit.used / capLimit.limit) * 100);
1232
886
  console.log(` Usage : ${capLimit.used}/${capLimit.limit} captures today (${pct}%)`);
1233
887
  if (capLimit.remaining <= 1) {
1234
- console.log(` ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate.ai/pro`);
888
+ console.log(` ⚠️ Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro`);
1235
889
  }
1236
890
  }
1237
891
  console.log('');
1238
- try {
1239
- const { buildCaptureReceipt } = require(path.join(PKG_ROOT, 'scripts', 'commercial-offer'));
1240
- console.log(buildCaptureReceipt({
1241
- signal: normalized,
1242
- feedbackId: ev.id,
1243
- memoryId: mem.id,
1244
- actionType: ev.actionType,
1245
- }));
1246
- } catch (_) {
1247
- // Receipt is a conversion aid, not part of feedback persistence.
1248
- }
1249
892
  proNudge();
1250
893
  } else {
1251
894
  if (args.json) {
@@ -1263,175 +906,13 @@ function capture() {
1263
906
  }
1264
907
  }
1265
908
 
1266
- function readJsonlEntries(filePath) {
1267
- try {
1268
- return fs.readFileSync(filePath, 'utf8')
1269
- .split(/\r?\n/)
1270
- .map((line) => line.trim())
1271
- .filter(Boolean)
1272
- .map((line) => JSON.parse(line));
1273
- } catch (_) {
1274
- return [];
1275
- }
1276
- }
1277
-
1278
- function shellSingleQuote(value) {
1279
- return `'${String(value).replace(/'/g, `'\\''`)}'`;
1280
- }
1281
-
1282
- function feedbackSelfTest() {
1283
- const args = parseArgs(process.argv.slice(3));
1284
- const signalArg = String(args.feedback || args.signal || 'down').toLowerCase();
1285
- const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some((v) => signalArg.includes(v)) ? 'up'
1286
- : ['down', 'thumbsdown', 'thumbs_down', 'negative'].some((v) => signalArg.includes(v)) ? 'down'
1287
- : signalArg;
1288
-
1289
- if (normalized !== 'up' && normalized !== 'down') {
1290
- console.error('feedback-self-test needs --feedback=up|down when overriding the default.');
1291
- process.exit(1);
1292
- }
1293
-
1294
- const previousFeedbackDir = process.env.THUMBGATE_FEEDBACK_DIR;
1295
- const previousNoNudge = process.env.THUMBGATE_NO_NUDGE;
1296
- const feedbackDir = args['feedback-dir']
1297
- ? path.resolve(CWD, args['feedback-dir'])
1298
- : args.persist
1299
- ? null
1300
- : fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-feedback-self-test-'));
1301
- const isolated = Boolean(feedbackDir);
1302
-
1303
- if (feedbackDir) process.env.THUMBGATE_FEEDBACK_DIR = feedbackDir;
1304
- process.env.THUMBGATE_NO_NUDGE = '1';
1305
-
1306
- try {
1307
- const { captureFeedback, getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
1308
- const context = args.context || `feedback self-test: typed thumbs ${normalized} reaches ThumbGate capture`;
1309
- const result = captureFeedback({
1310
- signal: normalized,
1311
- context,
1312
- whatWentWrong: normalized === 'down'
1313
- ? (args['what-went-wrong'] || 'Need proof that feedback capture is wired in this runtime')
1314
- : undefined,
1315
- whatToChange: normalized === 'down'
1316
- ? (args['what-to-change'] || 'Run a one-command self-test before claiming thumbs feedback is captured')
1317
- : undefined,
1318
- whatWorked: normalized === 'up'
1319
- ? (args['what-worked'] || 'Feedback capture persisted and was verified by a self-test')
1320
- : undefined,
1321
- tags: args.tags || 'self-test,dogfood,feedback-capture',
1322
- });
1323
-
1324
- const paths = getFeedbackPaths();
1325
- const feedbackRows = readJsonlEntries(paths.FEEDBACK_LOG_PATH);
1326
- const memoryRows = readJsonlEntries(paths.MEMORY_LOG_PATH);
1327
- const feedbackId = result.feedbackEvent && result.feedbackEvent.id;
1328
- const memoryId = result.memoryRecord && result.memoryRecord.id;
1329
- const feedbackStored = Boolean(feedbackId && feedbackRows.some((row) => row.id === feedbackId));
1330
- const memoryStored = Boolean(memoryId && memoryRows.some((row) => row.id === memoryId));
1331
-
1332
- let hookProbe = null;
1333
- if (args.hook) {
1334
- const hookPrompt = args['hook-prompt'] || (
1335
- normalized === 'down'
1336
- ? `thumbs down ${context}`
1337
- : `thumbs up ${context}`
1338
- );
1339
- const hookResult = spawnSync(process.execPath, [__filename, 'hook-auto-capture'], {
1340
- encoding: 'utf8',
1341
- timeout: 10000,
1342
- env: {
1343
- ...process.env,
1344
- THUMBGATE_FEEDBACK_DIR: paths.FEEDBACK_DIR,
1345
- THUMBGATE_NO_NUDGE: '1',
1346
- CLAUDE_USER_PROMPT: hookPrompt,
1347
- },
1348
- });
1349
- const hookFeedbackRows = readJsonlEntries(paths.FEEDBACK_LOG_PATH);
1350
- const hookMemoryRows = readJsonlEntries(paths.MEMORY_LOG_PATH);
1351
- const hookLatestFeedback = hookFeedbackRows[hookFeedbackRows.length - 1] || null;
1352
- const hookLatestMemory = hookMemoryRows[hookMemoryRows.length - 1] || null;
1353
- hookProbe = {
1354
- ok: hookResult.status === 0
1355
- && Boolean(hookLatestFeedback)
1356
- && hookLatestFeedback.submittedContext === hookPrompt
1357
- && hookLatestFeedback.actionType !== 'no-action'
1358
- && Boolean(hookLatestMemory),
1359
- prompt: hookPrompt,
1360
- exitCode: hookResult.status,
1361
- feedbackId: hookLatestFeedback ? hookLatestFeedback.id : null,
1362
- memoryId: hookLatestMemory ? hookLatestMemory.id : null,
1363
- actionType: hookLatestFeedback ? hookLatestFeedback.actionType : null,
1364
- stdoutPreview: String(hookResult.stdout || '').slice(0, 500),
1365
- stderrPreview: String(hookResult.stderr || '').slice(0, 500),
1366
- };
1367
- }
1368
-
1369
- const ok = Boolean(result.accepted && feedbackStored && memoryStored && (!hookProbe || hookProbe.ok));
1370
-
1371
- const payload = {
1372
- ok,
1373
- command: 'feedback-self-test',
1374
- signal: normalized,
1375
- accepted: Boolean(result.accepted),
1376
- feedbackId: feedbackId || null,
1377
- memoryId: memoryId || null,
1378
- feedbackStored,
1379
- memoryStored,
1380
- isolated,
1381
- feedbackDir: paths.FEEDBACK_DIR,
1382
- hookProbe,
1383
- nextDogfoodCommand: `npx thumbgate capture --feedback=${normalized} --context=${shellSingleQuote(context)}`,
1384
- };
1385
-
1386
- if (args.json) {
1387
- console.log(JSON.stringify(payload, null, 2));
1388
- } else if (ok) {
1389
- console.log('\nThumbGate feedback self-test: PASS');
1390
- console.log('─'.repeat(50));
1391
- console.log(` Captured : ${normalized} (${feedbackId})`);
1392
- console.log(` Stored lesson: ${memoryId}`);
1393
- console.log(` Storage : ${paths.FEEDBACK_DIR}`);
1394
- console.log(` Mode : ${isolated ? 'isolated test store' : 'active ThumbGate store'}`);
1395
- if (hookProbe) {
1396
- console.log(` Hook capture : PASS (${hookProbe.feedbackId}, ${hookProbe.memoryId})`);
1397
- }
1398
- console.log('\nDogfood in chat with:');
1399
- console.log(` thumbs ${normalized}: ${context}`);
1400
- } else {
1401
- console.log('\nThumbGate feedback self-test: FAIL');
1402
- console.log('─'.repeat(50));
1403
- console.log(` Accepted : ${payload.accepted}`);
1404
- console.log(` Feedback stored: ${feedbackStored}`);
1405
- console.log(` Memory stored : ${memoryStored}`);
1406
- if (hookProbe) {
1407
- console.log(` Hook capture : ${hookProbe.ok ? 'true' : 'false'} (${hookProbe.actionType || 'no-action'})`);
1408
- }
1409
- console.log(` Storage : ${paths.FEEDBACK_DIR}`);
1410
- }
1411
-
1412
- if (!ok) process.exit(2);
1413
- } finally {
1414
- if (previousFeedbackDir === undefined) delete process.env.THUMBGATE_FEEDBACK_DIR;
1415
- else process.env.THUMBGATE_FEEDBACK_DIR = previousFeedbackDir;
1416
- if (previousNoNudge === undefined) delete process.env.THUMBGATE_NO_NUDGE;
1417
- else process.env.THUMBGATE_NO_NUDGE = previousNoNudge;
1418
- }
1419
- }
1420
-
1421
909
  function stats() {
1422
910
  trackEvent('cli_stats', { command: 'stats' });
1423
911
  const args = parseArgs(process.argv.slice(3));
1424
912
  const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
1425
913
  const data = analyzeFeedback();
1426
914
 
1427
- // Gate enforcement stats — runtime intercepts + configured gates
1428
- let gateData = { blocked: 0, warned: 0, passed: 0, byGate: {} };
1429
- try { gateData = require(path.join(PKG_ROOT, 'scripts', 'gates-engine')).loadStats(); } catch {}
1430
- let gateConfigData = { totalGates: 0, autoPromotedGates: 0, estimatedHoursSaved: '0.0', topBlocked: null, firstTimeFixRate: null };
1431
- try { gateConfigData = require(path.join(PKG_ROOT, 'scripts', 'gate-stats')).calculateStats(); } catch {}
1432
-
1433
915
  const avgCostOfMistake = 2.50;
1434
- const totalInterceptions = gateData.blocked + gateData.warned;
1435
916
  const payload = {
1436
917
  total: data.total,
1437
918
  positives: data.totalPositive,
@@ -1441,13 +922,6 @@ function stats() {
1441
922
  revenueAtRisk: Number((data.totalNegative * avgCostOfMistake).toFixed(2)),
1442
923
  topTags: data.topTags || [],
1443
924
  recentActivity: data.recentActivity || [],
1444
- gatesBlocked: gateData.blocked,
1445
- gatesWarned: gateData.warned,
1446
- totalGates: gateConfigData.totalGates,
1447
- autoPromotedGates: gateConfigData.autoPromotedGates,
1448
- estimatedHoursSaved: gateConfigData.estimatedHoursSaved,
1449
- topBlockedGate: gateConfigData.topBlocked ? gateConfigData.topBlocked.id : null,
1450
- firstTimeFixRate: gateConfigData.firstTimeFixRate,
1451
925
  };
1452
926
 
1453
927
  if (args.json) {
@@ -1461,36 +935,17 @@ function stats() {
1461
935
  console.log(` Approval Rate : ${payload.approvalRate}%`);
1462
936
  console.log(` Recent Trend : ${payload.recentTrend}%`);
1463
937
 
1464
- // Gate enforcement — the high-ROI section
1465
- if (totalInterceptions > 0 || payload.totalGates > 0) {
1466
- console.log('\n🛡️ PRE-ACTION GATE ENFORCEMENT');
1467
- console.log(` Actions blocked : ${payload.gatesBlocked}`);
1468
- console.log(` Actions warned : ${payload.gatesWarned}`);
1469
- console.log(` Active gates : ${payload.totalGates} (${payload.autoPromotedGates} auto-promoted)`);
1470
- if (payload.topBlockedGate) console.log(` Top blocker : ${payload.topBlockedGate}`);
1471
- console.log(` Est. time saved : ~${payload.estimatedHoursSaved} hours`);
1472
- const { formatFirstTimeFixRate } = require(path.join(PKG_ROOT, 'scripts', 'gate-stats'));
1473
- console.log(` First-time fix : ${formatFirstTimeFixRate(payload.firstTimeFixRate)}`);
1474
- }
1475
-
1476
938
  if (payload.negatives > 0) {
1477
939
  console.log('\n⚠️ REVENUE-AT-RISK ANALYSIS');
1478
940
  console.log(` Repeated Failures detected: ${payload.negatives}`);
1479
941
  console.log(` Estimated Operational Loss: $${payload.revenueAtRisk}`);
1480
942
  console.log(' Action Required: Run "npx thumbgate rules" to generate guardrails.');
1481
943
  console.log(' Strategic Recommendation: if this is a shared workflow problem, start the Workflow Hardening Sprint.');
1482
- console.log(' Team intake: https://thumbgate.ai/#workflow-sprint-intake');
944
+ console.log(' Team intake: https://thumbgate-production.up.railway.app/#workflow-sprint-intake');
1483
945
  console.log(' Solo side lane: npx thumbgate pro');
1484
946
  } else {
1485
947
  console.log('\n✅ System is currently high-reliability. No immediate revenue loss detected.');
1486
948
  }
1487
- try {
1488
- const { buildStatsReceipt } = require(path.join(PKG_ROOT, 'scripts', 'commercial-offer'));
1489
- const receipt = buildStatsReceipt(payload);
1490
- if (receipt) console.log(receipt);
1491
- } catch (_) {
1492
- // Keep stats resilient if the receipt helper is unavailable in old installs.
1493
- }
1494
949
  proNudge();
1495
950
  }
1496
951
 
@@ -1596,14 +1051,13 @@ function pro() {
1596
1051
  } = require(path.join(PKG_ROOT, 'scripts', 'pro-local-dashboard'));
1597
1052
 
1598
1053
  function printProInfo() {
1599
- const hostedUrl = 'https://thumbgate.ai';
1054
+ const hostedUrl = 'https://thumbgate-production.up.railway.app';
1600
1055
  const truthUrl = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md';
1601
1056
  console.log('\nThumbGate Pro — Local Dashboard');
1602
1057
  console.log('─'.repeat(50));
1603
1058
  console.log('Self-serve side lane today: Pro ($19/mo or $149/yr).');
1604
1059
  console.log('Every licensed Pro user gets a personal local dashboard on localhost.');
1605
1060
  console.log('\nWhat is available:');
1606
- console.log(' - Hosted sync: keep lessons, rules, and dashboard state aligned across laptops, CI, containers, and agent runtimes');
1607
1061
  console.log(' - Local Pro dashboard: your own browser dashboard for search, gates, and DPO export');
1608
1062
  console.log(' - Team rollout path: shared hosted lessons, org visibility, and workflow proof');
1609
1063
  console.log(' - Commercial truth doc: source of truth for traction, pricing, and proof claims');
@@ -1741,13 +1195,6 @@ function lessons() {
1741
1195
  const tags = String(args.tags || '').split(',').map((t) => t.trim()).filter(Boolean);
1742
1196
  const query = args.query || process.argv.slice(3).find((a) => !a.startsWith('--')) || '';
1743
1197
  const limit = Number(args.limit || 10);
1744
- const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1745
- const searchLimit = checkLimit('search_lessons');
1746
- if (!searchLimit.allowed) {
1747
- trackEvent('cli_upgrade_prompt', { command: 'lessons', action: 'search_lessons', limitType: searchLimit.limitType || null });
1748
- limitNudge('search_lessons', searchLimit);
1749
- process.exit(1);
1750
- }
1751
1198
 
1752
1199
  // --remote: fetch from hosted Railway instance
1753
1200
  if (args.remote) {
@@ -1914,7 +1361,6 @@ function modelCandidatesCmd() {
1914
1361
  const maxCandidates = args.max ? Number(args.max) : undefined;
1915
1362
  const { reportPath, report } = writeModelCandidatesReport(undefined, {
1916
1363
  workload: args.workload,
1917
- workloadFile: args['workload-file'] || args.workloadFile,
1918
1364
  provider: args.provider,
1919
1365
  family: args.family,
1920
1366
  gateway: args.gateway,
@@ -2015,10 +1461,13 @@ function risk() {
2015
1461
  }
2016
1462
 
2017
1463
  function exportDpo() {
2018
- const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
2019
- const exportLimit = checkLimit('export_dpo');
2020
- if (!exportLimit.allowed) {
2021
- limitNudge('export_dpo', exportLimit);
1464
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1465
+ if (!isProTier(null)) {
1466
+ process.stderr.write(
1467
+ `\n 🔒 DPO Export requires Pro (${PRO_PRICE_LABEL}).\n` +
1468
+ ` Your feedback would generate valuable training pairs.\n` +
1469
+ ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
1470
+ );
2022
1471
  process.exit(1);
2023
1472
  }
2024
1473
  const extraArgs = process.argv.slice(3).join(' ');
@@ -2035,10 +1484,13 @@ function exportDpo() {
2035
1484
  }
2036
1485
 
2037
1486
  function exportDatabricks() {
2038
- const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
2039
- const exportLimit = checkLimit('export_databricks');
2040
- if (!exportLimit.allowed) {
2041
- limitNudge('export_databricks', exportLimit);
1487
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1488
+ if (!isProTier(null)) {
1489
+ process.stderr.write(
1490
+ `\n 🔒 Databricks Export requires Pro (${PRO_PRICE_LABEL}).\n` +
1491
+ ` Export feedback logs + proof artifacts for analytics.\n` +
1492
+ ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
1493
+ );
2042
1494
  process.exit(1);
2043
1495
  }
2044
1496
  const extraArgs = process.argv.slice(3).join(' ');
@@ -2233,26 +1685,6 @@ function pulse() {
2233
1685
  });
2234
1686
  }
2235
1687
 
2236
- function checkUpdateCmd() {
2237
- const { checkUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
2238
- const args = parseArgs(process.argv.slice(3));
2239
- checkUpdate({ verbose: !args.json, force: args.force }).then((res) => {
2240
- if (args.json) {
2241
- console.log(JSON.stringify(res, null, 2));
2242
- }
2243
- process.exit(0);
2244
- }).catch((err) => {
2245
- console.error(err && err.message ? err.message : err);
2246
- process.exit(1);
2247
- });
2248
- }
2249
-
2250
- function selfUpdateCmd() {
2251
- const { selfUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
2252
- const success = selfUpdate();
2253
- process.exit(success ? 0 : 1);
2254
- }
2255
-
2256
1688
  function dispatchBrief() {
2257
1689
  const args = parseArgs(process.argv.slice(3));
2258
1690
  const {
@@ -2286,25 +1718,6 @@ function gateStats() {
2286
1718
  console.log('\n' + formatStats(stats) + '\n');
2287
1719
  }
2288
1720
 
2289
- function contextPacks() {
2290
- const args = parseArgs(process.argv.slice(3));
2291
- const { generateAutoContextPacks } = require(path.join(PKG_ROOT, 'scripts', 'auto-context-packs'));
2292
- const result = generateAutoContextPacks();
2293
- if (args.json) {
2294
- console.log(JSON.stringify(result, null, 2));
2295
- return;
2296
- }
2297
- console.log(`\nGenerated ${result.packCount} context pack(s):`);
2298
- for (const p of result.packs) {
2299
- console.log(` [${p.type}] ${p.name}`);
2300
- console.log(` -> ${p.filePath}`);
2301
- }
2302
- if (result.packCount === 0) {
2303
- console.log(' (No failure patterns found yet — capture some feedback first.)');
2304
- }
2305
- console.log('');
2306
- }
2307
-
2308
1721
  function harnessAudit() {
2309
1722
  const args = parseArgs(process.argv.slice(3));
2310
1723
  const {
@@ -2585,7 +1998,7 @@ function backgroundGovernance() {
2585
1998
  console.log(' - Run --check before dispatching an unattended PR job.');
2586
1999
  console.log(' - Route protected branches and large blast-radius jobs to human review.');
2587
2000
  console.log(' - Convert CI failures into thumbs-down lessons so repeats become Pre-Action Checks.');
2588
- console.log('\nGuide: https://thumbgate.ai/guides/background-agent-governance\n');
2001
+ console.log('\nGuide: https://thumbgate-production.up.railway.app/guides/background-agent-governance\n');
2589
2002
  }
2590
2003
 
2591
2004
  function optimize() {
@@ -2593,52 +2006,6 @@ function optimize() {
2593
2006
  doOptimize();
2594
2007
  }
2595
2008
 
2596
- function syncGcp() {
2597
- const { syncToGcp } = require(path.join(PKG_ROOT, 'adapters', 'gcp', 'sync.js'));
2598
- syncToGcp();
2599
- }
2600
-
2601
- function cleanup() {
2602
- console.log('Cleaning up ThumbGate processes...');
2603
- try {
2604
- const { execSync } = require('child_process');
2605
- // Kill all 'thumbgate serve' and 'thumbgate dashboard' processes except this one
2606
- const pids = execSync("ps aux | grep -E 'thumbgate (serve|dashboard|mcp)' | grep -v 'grep' | grep -v 'cleanup' | awk '{print $2}'", { encoding: 'utf8' })
2607
- .split('\n')
2608
- .filter(Boolean)
2609
- .map(Number)
2610
- .filter(pid => pid !== process.pid);
2611
-
2612
- if (pids.length > 0) {
2613
- console.log(`Killing ${pids.length} process(es): ${pids.join(', ')}`);
2614
- pids.forEach(pid => {
2615
- try { process.kill(pid, 'SIGTERM'); } catch (_) {}
2616
- });
2617
- // Give them a moment to die
2618
- execSync('sleep 1');
2619
- } else {
2620
- console.log('No other ThumbGate processes found.');
2621
- }
2622
-
2623
- // Check port 3456 specifically
2624
- try {
2625
- const portPids = execSync("lsof -ti :3456", { encoding: 'utf8' })
2626
- .split('\n')
2627
- .map(s => s.trim())
2628
- .filter(Boolean)
2629
- .map(Number);
2630
- portPids.forEach(pid => {
2631
- console.log(`Killing process ${pid} holding port 3456`);
2632
- try { process.kill(pid, 'SIGKILL'); } catch (_) {}
2633
- });
2634
- } catch (_) { /* port already free */ }
2635
-
2636
- console.log('✅ Cleanup complete. Run "npx thumbgate pro" to restart the dashboard.');
2637
- } catch (err) {
2638
- console.error(`Cleanup failed: ${err.message}`);
2639
- }
2640
- }
2641
-
2642
2009
  function serve() {
2643
2010
  try {
2644
2011
  const { repairCodexHooks } = require(path.join(PKG_ROOT, 'scripts', 'codex-self-heal'));
@@ -2675,34 +2042,11 @@ function install() {
2675
2042
  }
2676
2043
 
2677
2044
  async function gateCheck() {
2678
- // Explicit emergency escape hatch ONLY. The 2026-06-03 hotfix made this
2679
- // bypass-by-default, which silently disabled ThumbGate's enforcement entirely
2680
- // (the firewall approved everything). Restored 2026-06-04: enforcement runs by
2681
- // default in warn-by-default posture (see gates-engine applyEnforcementPosture);
2682
- // set THUMBGATE_HOTFIX_BYPASS=1 to disable all checks if a gate ever misfires.
2683
- if (process.env.THUMBGATE_HOTFIX_BYPASS === '1') {
2684
- process.stdout.write(JSON.stringify({
2685
- decision: 'approve',
2686
- reason: 'hotfix-bypass-opt-in',
2687
- hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: '' }
2688
- }) + '\n');
2689
- return;
2690
- }
2691
- try {
2692
- const payload = readStdinText();
2693
- const input = payload ? JSON.parse(payload) : {};
2694
- const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
2695
- const output = await gatesEngine.runAsync(input);
2696
- process.stdout.write(output + '\n');
2697
- } catch (err) {
2698
- process.stderr.write(`gate-check error: ${err.message}\n`);
2699
- process.stdout.write(JSON.stringify({
2700
- hookSpecificOutput: {
2701
- hookEventName: 'PreToolUse',
2702
- additionalContext: `[ThumbGate Error] ${err.message}`,
2703
- }
2704
- }) + '\n');
2705
- }
2045
+ const payload = readStdinText();
2046
+ const input = payload ? JSON.parse(payload) : {};
2047
+ const gatesEngine = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
2048
+ const output = await gatesEngine.runAsync(input);
2049
+ process.stdout.write(output + '\n');
2706
2050
  }
2707
2051
 
2708
2052
  function cacheUpdate() {
@@ -2729,14 +2073,9 @@ function statuslineRender() {
2729
2073
 
2730
2074
  function hookAutoCapture() {
2731
2075
  syncActiveProjectContext();
2732
- const prompt = process.env.CLAUDE_USER_PROMPT
2733
- || process.env.THUMBGATE_USER_PROMPT
2734
- || process.env.CODEX_USER_PROMPT
2735
- || process.env.USER_PROMPT
2736
- || readStdinText().trim();
2076
+ const prompt = process.env.CLAUDE_USER_PROMPT || process.env.THUMBGATE_USER_PROMPT || readStdinText().trim();
2737
2077
  const { evaluatePromptGuard } = require(path.join(PKG_ROOT, 'scripts', 'prompt-guard'));
2738
2078
  const { processInlineFeedback, formatCliOutput } = require(path.join(PKG_ROOT, 'scripts', 'cli-feedback'));
2739
- const { detectFeedbackSignal, isGenericFeedbackText } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
2740
2079
  const { loadOptionalModule } = require(path.join(PKG_ROOT, 'scripts', 'private-core-boundary'));
2741
2080
  const { recordConversationEntry, readRecentConversationWindow } = loadOptionalModule(
2742
2081
  path.join(PKG_ROOT, 'scripts', 'feedback-history-distiller'),
@@ -2758,13 +2097,14 @@ function hookAutoCapture() {
2758
2097
  return;
2759
2098
  }
2760
2099
 
2761
- const detected = detectFeedbackSignal(prompt);
2762
- if (!detected) {
2100
+ const lower = prompt.toLowerCase();
2101
+ const isUp = /(thumbs?\s*up|that worked|looks good|nice work|perfect|good job)/i.test(lower);
2102
+ const isDown = /(thumbs?\s*down|that failed|that was wrong|fix this)/i.test(lower);
2103
+ if (!isUp && !isDown) {
2763
2104
  return;
2764
2105
  }
2765
2106
 
2766
- const signal = detected.signal;
2767
- const genericPrompt = isGenericFeedbackText(prompt, signal === 'down' ? 'negative' : 'positive');
2107
+ const signal = isDown ? 'down' : 'up';
2768
2108
  const conversationWindow = readRecentConversationWindow({ limit: 8 });
2769
2109
  const result = processInlineFeedback({
2770
2110
  signal,
@@ -2772,8 +2112,8 @@ function hookAutoCapture() {
2772
2112
  chatHistory: signal === 'down'
2773
2113
  ? conversationWindow.map((entry) => ({ role: entry.author === 'assistant' ? 'assistant' : 'user', content: entry.text || '' }))
2774
2114
  : undefined,
2775
- whatWentWrong: signal === 'down' && !genericPrompt ? prompt : undefined,
2776
- whatWorked: signal === 'up' && !genericPrompt ? prompt : undefined,
2115
+ whatWentWrong: signal === 'down' ? prompt : undefined,
2116
+ whatWorked: signal === 'up' ? prompt : undefined,
2777
2117
  });
2778
2118
  process.stdout.write(formatCliOutput(result) + '\n');
2779
2119
  }
@@ -2845,27 +2185,6 @@ function installMcp() {
2845
2185
 
2846
2186
  function dashboard() {
2847
2187
  const args = parseArgs(process.argv.slice(3));
2848
- if (args.open || args.web) {
2849
- const { resolveProjectDir } = require(path.join(PKG_ROOT, 'scripts', 'feedback-paths'));
2850
- const projectDir = resolveProjectDir({ cwd: process.cwd(), env: process.env });
2851
- const port = process.env.PORT || 3456;
2852
- const url = `http://127.0.0.1:${port}/dashboard?project=${encodeURIComponent(projectDir)}`;
2853
-
2854
- ensureDash(Number(port))
2855
- .then((server) => {
2856
- if (server.started) {
2857
- console.log(`API ${port} pid ${server.pid}`);
2858
- }
2859
- return openBrowser(url);
2860
- })
2861
- .then(() => process.exit(0))
2862
- .catch((err) => {
2863
- console.error(err && err.message ? err.message : err);
2864
- process.exit(1);
2865
- });
2866
- return;
2867
- }
2868
-
2869
2188
  const { printDashboard } = require(path.join(PKG_ROOT, 'scripts', 'dashboard'));
2870
2189
  const { getOperationalDashboard } = require(path.join(PKG_ROOT, 'scripts', 'operational-dashboard'));
2871
2190
 
@@ -2985,64 +2304,6 @@ function startApi() {
2985
2304
  }
2986
2305
  }
2987
2306
 
2988
- function breakGlass() {
2989
- const args = parseArgs(process.argv.slice(3));
2990
- const positionalReason = process.argv.slice(3).find((arg) => !arg.startsWith('--'));
2991
- const reason = String(args.reason || positionalReason || '').trim();
2992
- if (!reason) {
2993
- console.error('Usage: npx thumbgate break-glass --reason "why this recovery is needed" [--ttl=5m] [--json]');
2994
- process.exit(1);
2995
- }
2996
-
2997
- const ttlMs = parseTtlMs(args.ttl, 5 * 60 * 1000);
2998
- const { breakGlassEmergency } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
2999
- const result = breakGlassEmergency({ reason, ttlMs });
3000
- if (args.json) {
3001
- console.log(JSON.stringify(result, null, 2));
3002
- return;
3003
- }
3004
-
3005
- console.log('ThumbGate break-glass active.');
3006
- console.log(` Reason : ${result.reason}`);
3007
- console.log(` Expires : ${result.expiresAt}`);
3008
- console.log(' Unlocked : hook settings edits, pr_create_allowed, pr_threads_checked');
3009
- console.log(' Still gated: local-only scope, force-push, protected branch push, unsafe chmod, broad rm -rf');
3010
- }
3011
-
3012
- function aiInventory() {
3013
- const args = parseArgs(process.argv.slice(3));
3014
- const {
3015
- scanAiComponents,
3016
- buildCycloneDxMlBom,
3017
- formatInventoryText,
3018
- writeOutput,
3019
- } = require(path.join(PKG_ROOT, 'scripts', 'ai-component-inventory'));
3020
- const rootDir = path.resolve(String(args.root || args.cwd || CWD));
3021
- const format = String(args.format || (args.json ? 'json' : 'summary')).toLowerCase();
3022
- const inventory = scanAiComponents({
3023
- rootDir,
3024
- maxFiles: args['max-files'] ? Number(args['max-files']) : undefined,
3025
- includeSnippets: args.snippets !== false,
3026
- });
3027
-
3028
- let payload;
3029
- if (format === 'cyclonedx' || format === 'ml-bom' || format === 'mlbom') {
3030
- payload = JSON.stringify(buildCycloneDxMlBom(inventory, { version: pkgVersion() }), null, 2);
3031
- } else if (format === 'json') {
3032
- payload = JSON.stringify(inventory, null, 2);
3033
- } else {
3034
- payload = formatInventoryText(inventory);
3035
- }
3036
-
3037
- if (args.output) {
3038
- writeOutput(path.resolve(String(args.output)), `${payload}\n`);
3039
- console.log(`Wrote AI inventory evidence to ${path.resolve(String(args.output))}`);
3040
- return;
3041
- }
3042
-
3043
- console.log(payload);
3044
- }
3045
-
3046
2307
  function help() {
3047
2308
  const v = pkgVersion();
3048
2309
  const helpArgs = process.argv.slice(3);
@@ -3058,18 +2319,13 @@ function help() {
3058
2319
  console.log('');
3059
2320
  console.log('Common commands:');
3060
2321
  console.log(' init Detect agent and wire ThumbGate hooks');
3061
- console.log(' feedback-self-test Prove thumbs capture works locally');
3062
2322
  console.log(' capture --feedback=up|down --context="<text>" Capture a thumbs signal as a stored lesson');
3063
2323
  console.log(' stats Approval rate, recent trend, blocked-pattern count');
3064
2324
  console.log(' lessons [query] Search promoted lessons');
3065
2325
  console.log(' explore Interactive TUI for lessons, gates, stats');
3066
2326
  console.log(' dashboard Open the local ThumbGate dashboard');
3067
- console.log(' ai-inventory Scan AI/ML components and export ML-BOM evidence');
3068
2327
  console.log(' doctor Audit runtime isolation + bootstrap context');
3069
- console.log(' break-glass --reason="..." Short TTL recovery if gates over-fire');
3070
- console.log(' brain [--write] Build the agent-readable context brain (lessons + rules + gates)');
3071
2328
  console.log(' pro ThumbGate Pro (dashboard, exports, sync)');
3072
- console.log(' subscribe <email> Get the 5-min setup guide + weekly tips by email');
3073
2329
  console.log('');
3074
2330
  console.log('More:');
3075
2331
  console.log(' thumbgate help all Full subcommand surface (~70 commands)');
@@ -3115,11 +2371,7 @@ function help() {
3115
2371
 
3116
2372
  // Legacy and specialist commands kept visible until they graduate into the schema.
3117
2373
  console.log('Also available:');
3118
- console.log(' install-mcp Install MCP server + PreToolUse hooks into Claude Code settings');
3119
- console.log(' default: machine-wide (~/.claude/settings.json — shared dashboard)');
3120
- console.log(' --project: per-repo (<cwd>/.claude/settings.json — isolated dashboard)');
3121
- console.log(' --no-hooks: MCP only, skip hook wiring');
3122
- console.log(' break-glass Short TTL recovery if gates over-fire');
2374
+ console.log(' install-mcp Install MCP server into Claude Code settings (--project for local)');
3123
2375
  console.log(' cfo Hosted billing summary (local fallback JSON)');
3124
2376
  console.log(' billing:setup Generate operator key + print Railway setup instructions');
3125
2377
  console.log(' repair-github-marketplace Repair legacy GitHub Marketplace amount mappings');
@@ -3140,7 +2392,6 @@ function help() {
3140
2392
  console.log(' proxy-pointer-rag-guardrails Map visual document RAG signals to Document RAG Safety gates');
3141
2393
  console.log(' rag-precision-guardrails Map retrieval tuning regressions to Document RAG Safety gates');
3142
2394
  console.log(' ai-engineering-stack-guardrails Map gateway, MCP, AGENTS.md, LLM wiki, reviewer, and sandbox gaps to stack gates');
3143
- console.log(' ai-inventory Scan AI/ML components and export JSON or CycloneDX ML-BOM evidence');
3144
2395
  console.log(' upstream-contributions Find dependency issues worth fixing without promotional PRs');
3145
2396
  console.log(' long-running-agent-context-guardrails Map structured-memory gaps to long-running agent gates');
3146
2397
  console.log(' reasoning-efficiency-guardrails Map reasoning compression signals to efficiency gates');
@@ -3165,7 +2416,6 @@ function help() {
3165
2416
 
3166
2417
  console.log('Examples:');
3167
2418
  console.log(' npx thumbgate init');
3168
- console.log(' npx thumbgate feedback-self-test');
3169
2419
  console.log(' npx thumbgate status --json');
3170
2420
  console.log(' npx thumbgate explore lessons --json');
3171
2421
  console.log(' npx thumbgate explore gates --json');
@@ -3175,7 +2425,6 @@ function help() {
3175
2425
  console.log(' npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --image-pointers=paper-1/figures/fig2.png --documents=paper-1 --visual-claims --json');
3176
2426
  console.log(' npx thumbgate rag-precision-guardrails --baseline-recall=0.86 --new-recall=0.72 --threshold-change --agentic --structural-near-misses --json');
3177
2427
  console.log(' npx thumbgate ai-engineering-stack-guardrails --mcp-tool-count=182 --direct-provider-keys --llm-wiki-pages=24 --context-freshness-days=30 --background-agents --json');
3178
- console.log(' npx thumbgate ai-inventory --format=cyclonedx --output=.thumbgate/ai-mlbom.json');
3179
2428
  console.log(' npx thumbgate long-running-agent-context-guardrails --request-count=80 --output-mb=3 --raw-chat-only --json');
3180
2429
  console.log(' npx thumbgate reasoning-efficiency-guardrails --baseline-tokens=1200 --compressed-tokens=980 --baseline-accuracy=0.84 --compressed-accuracy=0.85 --verifier --json');
3181
2430
  console.log(' npx thumbgate deepseek-v4-runtime-guardrails --context-tokens=900000 --hybrid-attention --speculative-decoding --accept-length=1.4 --precision-mode=fp8 --json');
@@ -3197,270 +2446,11 @@ if (COMMAND === 'daemon' || COMMAND === 'serve-daemon') {
3197
2446
  process.exit(0);
3198
2447
  }
3199
2448
 
3200
- // ---------------------------------------------------------------------------
3201
- // Global --help interceptor: `thumbgate <cmd> --help` shows per-command help
3202
- // instead of running the command. Commands with their own --help guard (init)
3203
- // handle it internally; everything else falls through here.
3204
- // ---------------------------------------------------------------------------
3205
- const _cliSubArgs = process.argv.slice(3);
3206
- const _wantsHelp = _cliSubArgs.includes('--help') || _cliSubArgs.includes('-h');
3207
-
3208
- const SUBCOMMAND_HELP = {
3209
- capture: 'Usage: npx thumbgate capture --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
3210
- feedback: 'Usage: npx thumbgate feedback --feedback=up|down --context="..." [--what-worked="..."] [--what-went-wrong="..."] [--what-to-change="..."] [--tags=a,b]',
3211
- 'feedback-self-test': 'Usage: npx thumbgate feedback-self-test [--json] [--persist] [--hook] [--feedback=up|down]\n\nCapture a synthetic thumbs signal and verify feedback-log + memory-log writes. Defaults to an isolated test store; use --hook to also prove UserPromptSubmit-style raw prompt auto-capture.',
3212
- dogfood: 'Usage: npx thumbgate dogfood [--json] [--persist] [--feedback=up|down]\n\nAlias for feedback-self-test.',
3213
- stats: 'Usage: npx thumbgate stats\n\nShow gate enforcement statistics: blocked/warned counts, active gates, time saved.',
3214
- trial: 'Usage: npx thumbgate trial\n\nShow Pro trial status, remaining days, and upgrade path.',
3215
- pro: 'Usage: npx thumbgate pro [--activate <key>]\n\nLaunch the local Pro dashboard or activate a Pro license key.',
3216
- subscribe: 'Usage: npx thumbgate subscribe <email>\n\nSubscribe to the 5-minute setup guide + trial reminders.',
3217
- lessons: 'Usage: npx thumbgate lessons [--query="..."] [--limit=N]\n\nSearch the lesson database (Pro feature).',
3218
- search: 'Usage: npx thumbgate search <query>\n\nSearch ThumbGate knowledge base (Pro feature).',
3219
- 'gate-check': 'Usage: npx thumbgate gate-check\n\nPreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict.',
3220
- 'hermes-gate': 'Usage: npx thumbgate hermes-gate\n\nNous Research Hermes Agent pre_tool_call shell hook: reads Hermes tool-call JSON from stdin, runs the ThumbGate gate pipeline (strict by default), and outputs {"decision":"block","reason":...} to veto or {} to allow. Gates terminal/patch/skill_manage etc. See adapters/hermes/config.yaml.',
3221
- '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.',
3222
- serve: 'Usage: npx thumbgate serve\n\nStart the MCP stdio server. This is for agent runtimes, not the local HTTP dashboard.',
3223
- mcp: 'Usage: npx thumbgate mcp\n\nAlias for `thumbgate serve`.',
3224
- dashboard: 'Usage: npx thumbgate dashboard [--window=today|7d|30d] [--open]\n\nPrint the operational dashboard summary or open the browser HTTP dashboard (use --open). Defaults to PORT=3456.',
3225
- '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.',
3226
- 'export-dpo': 'Usage: npx thumbgate export-dpo [--format=jsonl|csv]\n\nExport feedback as DPO training pairs (Pro feature).',
3227
- status: 'Usage: npx thumbgate status\n\nShow ThumbGate system health and active configuration.',
3228
- watch: 'Usage: npx thumbgate watch\n\nWatch for feedback changes and auto-regenerate prevention rules.',
3229
- compact: 'Usage: npx thumbgate compact\n\nCompact the lesson database and reclaim disk space.',
3230
- 'context-packs': 'Usage: npx thumbgate context-packs\n\nGenerate context packs from top failure patterns.',
3231
- suggest: 'Usage: npx thumbgate suggest <gate-id>\n\nSuggest fixes for a specific gate based on lesson history.',
3232
- cost: 'Usage: npx thumbgate cost [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nShow cumulative $ and tokens saved by PreToolUse gate blocks. Reads ~/.thumbgate/gate-stats.json.',
3233
- savings: 'Usage: npx thumbgate savings [--json] [--stats <path>] [--mix \'{"claude-sonnet-4-5":0.8,...}\']\n\nAlias for `thumbgate cost`.',
3234
- 'setup-vertex': 'Usage: npx thumbgate setup-vertex [--dry-run]\n\nAuto-enable Vertex AI API on GCP and write local Vertex routing config to .env. With --dry-run, only detect the active account/project and print the planned changes. This does not create or verify a Dialogflow CX agent; use the Dialogflow CX REST API or console for live-agent evidence.',
3235
- 'ai-inventory': 'Usage: npx thumbgate ai-inventory [--root <dir>] [--format=summary|json|cyclonedx] [--output <path>] [--max-files=N]\n\nScan source/manifests/model artifacts for AI, ML, agent-framework, vector DB, Vertex, Gemini, and Dialogflow CX components. Use --format=cyclonedx to produce exportable ML-BOM evidence for enterprise reviews.',
3236
- brain: 'Usage: npx thumbgate brain [--write] [--json] [--limit=N]\n\nBuild the agent-readable "context brain" — a single artifact consolidating this\nrepo\'s lessons, prevention rules, active gates, and project context for a coding\nagent to read BEFORE acting. --write saves it to .thumbgate/BRAIN.md (versioned,\ndeterministic). --json emits the structured model. --limit caps lessons (default 15).',
3237
- 'team-sync': 'Usage: npx thumbgate team-sync\n\nSynchronize prevention rules and context brain with your team\'s git repository (git pull --rebase & git push), then auto-rebuild the local brain.',
3238
- };
3239
-
3240
- if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
3241
- console.log(SUBCOMMAND_HELP[COMMAND]);
3242
- process.exit(0);
3243
- }
3244
-
3245
- // -----------------------------------------------------------------------------
3246
- // brain — consolidate ThumbGate's institutional memory (lessons + prevention
3247
- // rules + active gates + project context) into a single, versioned,
3248
- // agent-readable "context brain". The persistent context a coding agent should
3249
- // read BEFORE acting, so it stops repeating mistakes. Composes the existing
3250
- // explore-subcommands primitives; output is deterministic (no volatile
3251
- // timestamp) so `.thumbgate/BRAIN.md` diffs only when the underlying memory
3252
- // changes.
3253
- // -----------------------------------------------------------------------------
3254
- function buildBrainModel(opts = {}) {
3255
- const { exploreLessons, exploreRules, exploreGates } = require(path.join(PKG_ROOT, 'scripts', 'explore-subcommands'));
3256
- let feedbackDir;
3257
- try {
3258
- const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
3259
- feedbackDir = getFeedbackPaths().FEEDBACK_DIR;
3260
- } catch (_) { feedbackDir = path.join(CWD, '.thumbgate'); }
3261
- const limit = Math.max(1, Number(opts.limit) || 15);
3262
- const safe = (fn, fallback) => { try { return fn(); } catch (_) { return fallback; } };
3263
- const lessons = safe(() => exploreLessons({ feedbackDir, pkgRoot: PKG_ROOT, limit, json: true }), { lessons: [], total: 0 });
3264
- const rules = safe(() => exploreRules({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { rules: [], total: 0 });
3265
- const gates = safe(() => exploreGates({ feedbackDir, pkgRoot: PKG_ROOT, json: true }), { gates: [], total: 0 });
3266
- const instructionFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md', '.cursorrules', '.thumbgate/config.json']
3267
- .filter((f) => { try { return fs.existsSync(path.join(CWD, f)); } catch (_) { return false; } });
3268
- return { lessons, rules, gates, project: { root: CWD, instructionFiles } };
3269
- }
3270
-
3271
- function renderBrainMarkdown(model) {
3272
- const out = [];
3273
- out.push('# ThumbGate Context Brain');
3274
- out.push('');
3275
- out.push('<!-- The persistent context a coding agent should read BEFORE acting in this repo.');
3276
- out.push(' Generated by `npx thumbgate brain`. Rebuild after capturing feedback. -->');
3277
- out.push('');
3278
- out.push('> Institutional memory for AI coding agents working here: what has been');
3279
- out.push('> learned, what to avoid, and what is enforced. Read this first.');
3280
- out.push('');
3281
-
3282
- out.push('## What this codebase taught its agents (lessons)');
3283
- out.push('');
3284
- const lessons = (model.lessons && model.lessons.lessons) || [];
3285
- if (!lessons.length) {
3286
- out.push('_No lessons captured yet. Run `npx thumbgate capture --feedback=down --context="what failed"`._');
3287
- } else {
3288
- for (const l of lessons) {
3289
- const sig = String(l.signal || '').toLowerCase();
3290
- const mark = (sig.includes('positive') || sig === 'up') ? '✅' : ((sig.includes('negative') || sig === 'down') ? '⛔' : '•');
3291
- const enforced = l.confidence === 'active' ? ' _(enforced)_' : '';
3292
- const ctx = String(l.context || '').replace(/\s+/g, ' ').trim().slice(0, 220);
3293
- if (ctx) out.push(`- ${mark} ${ctx}${enforced}`);
3294
- }
3295
- }
3296
- out.push('');
3297
-
3298
- out.push('## Guardrails — do NOT repeat these (prevention rules)');
3299
- out.push('');
3300
- const rules = (model.rules && model.rules.rules) || [];
3301
- if (!rules.length) {
3302
- out.push('_No prevention rules yet._');
3303
- } else {
3304
- for (const r of rules) {
3305
- out.push(`- **${String(r.title || '').trim()}**`);
3306
- const body = String(r.body || '').split('\n')[0].trim();
3307
- if (body && body !== r.title) out.push(` - ${body.slice(0, 200)}`);
3308
- }
3309
- }
3310
- out.push('');
3311
-
3312
- out.push('## Active enforcement (gates)');
3313
- out.push('');
3314
- const gates = (model.gates && model.gates.gates) || [];
3315
- if (!gates.length) {
3316
- out.push('_No active gates._');
3317
- } else {
3318
- for (const g of gates) {
3319
- const occ = g.occurrences ? ` (${g.occurrences}×)` : '';
3320
- out.push(`- \`${g.pattern || g.id}\` → **${g.action}**${occ}`);
3321
- }
3322
- }
3323
- out.push('');
3324
-
3325
- out.push('## Project context');
3326
- out.push('');
3327
- out.push(`- Repo root: \`${model.project.root}\``);
3328
- const files = model.project.instructionFiles;
3329
- out.push(`- Agent instruction files: ${files.length ? files.map((f) => '`' + f + '`').join(', ') : '_none detected_'}`);
3330
- out.push('');
3331
- out.push('---');
3332
- const lt = (model.lessons && model.lessons.total) || 0;
3333
- const rt = (model.rules && model.rules.total) || 0;
3334
- const gt = (model.gates && model.gates.total) || 0;
3335
- out.push(`_${lt} lessons · ${rt} rules · ${gt} gates. Rebuild with \`npx thumbgate brain --write\`._`);
3336
- out.push('');
3337
- return out.join('\n');
3338
- }
3339
-
3340
- function cmdBrain(args = {}) {
3341
- const model = buildBrainModel({ limit: args.limit });
3342
- if (args.json) { console.log(JSON.stringify(model, null, 2)); return 0; }
3343
- const md = renderBrainMarkdown(model);
3344
- if (args.write) {
3345
- const dir = path.join(CWD, '.thumbgate');
3346
- const target = path.join(dir, 'BRAIN.md');
3347
- try {
3348
- fs.mkdirSync(dir, { recursive: true });
3349
- fs.writeFileSync(target, md);
3350
- } catch (err) {
3351
- // Surface a clean, actionable error instead of an uncaught stack trace
3352
- // (e.g. permission denied, read-only filesystem).
3353
- console.error(`Could not write ${target}: ${err && err.message ? err.message : err}`);
3354
- return 1;
3355
- }
3356
- const lt = (model.lessons && model.lessons.total) || 0;
3357
- const rt = (model.rules && model.rules.total) || 0;
3358
- const gt = (model.gates && model.gates.total) || 0;
3359
- console.log(`\u{1f9e0} Wrote context brain to .thumbgate/BRAIN.md (${lt} lessons · ${rt} rules · ${gt} gates).`);
3360
- console.log(' Point your agent at it: add "Read .thumbgate/BRAIN.md first" to CLAUDE.md / AGENTS.md.');
3361
- return 0;
3362
- }
3363
- process.stdout.write(md);
3364
- return 0;
3365
- }
3366
-
3367
- async function teamSync() {
3368
- const { execSync } = require('child_process');
3369
-
3370
- // Verify we are in a Git repo
3371
- try {
3372
- execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', cwd: CWD });
3373
- } catch (err) {
3374
- console.error('❌ Error: The current directory is not a Git repository.');
3375
- process.exit(1);
3376
- }
3377
-
3378
- console.log('🔄 Checking shared prevention rules status...');
3379
-
3380
- const rulesRelative = '.thumbgate/prevention-rules.md';
3381
- const brainRelative = '.thumbgate/BRAIN.md';
3382
- const rulesPath = path.join(CWD, rulesRelative);
3383
-
3384
- if (!fs.existsSync(rulesPath)) {
3385
- console.log('⚠️ No local prevention rules file found to sync.');
3386
- }
3387
-
3388
- let statusOutput = '';
3389
- try {
3390
- statusOutput = execSync('git status --porcelain', { encoding: 'utf8', cwd: CWD }) || '';
3391
- } catch (_) {}
3392
- const hasLocalChanges = statusOutput.includes(rulesRelative) || statusOutput.includes(brainRelative) || statusOutput.includes('.thumbgate/');
3393
-
3394
- if (hasLocalChanges) {
3395
- console.log('📝 Local changes detected in prevention rules. Committing locally...');
3396
- try {
3397
- const filesToAdd = [rulesRelative, brainRelative].filter(f => fs.existsSync(path.join(CWD, f)));
3398
- if (filesToAdd.length > 0) {
3399
- const filesStr = filesToAdd.map(f => `"${f}"`).join(' ');
3400
- execSync(`git add -f ${filesStr}`, { cwd: CWD });
3401
- execSync('git commit -m "chore(thumbgate): update shared prevention rules [skip ci]"', { cwd: CWD });
3402
- console.log('✅ Local rules committed successfully.');
3403
- } else {
3404
- console.log('✨ No local rules files exist to commit.');
3405
- }
3406
- } catch (e) {
3407
- console.log('✨ No changes to commit (already staged/clean).');
3408
- }
3409
- } else {
3410
- console.log('✨ No local rules changes to commit.');
3411
- }
3412
-
3413
- // Pull from remote
3414
- console.log('📥 Pulling rules from teammate remote (git pull --rebase)...');
3415
- try {
3416
- execSync('git pull --rebase', { stdio: 'inherit', cwd: CWD });
3417
- } catch (pullErr) {
3418
- console.error('❌ Git pull failed. Please resolve conflicts manually.');
3419
- process.exit(1);
3420
- }
3421
-
3422
- // Push to remote
3423
- console.log('📤 Pushing rules to teammate remote (git push)...');
3424
- try {
3425
- execSync('git push', { stdio: 'inherit', cwd: CWD });
3426
- } catch (pushErr) {
3427
- console.error('❌ Git push failed. You may need to run git push manually.');
3428
- process.exit(1);
3429
- }
3430
-
3431
- // Rebuild the context brain (.thumbgate/BRAIN.md) from the newly merged rules
3432
- console.log('🧠 Rebuilding local context brain from merged rules...');
3433
- try {
3434
- const brainArgs = { write: true };
3435
- cmdBrain(brainArgs);
3436
- } catch (brainErr) {
3437
- console.warn(`⚠️ Failed to rebuild context brain: ${brainErr.message}`);
3438
- }
3439
-
3440
- console.log('\n🚀 Team rules synchronization complete! Your agents now share team-wide learning.');
3441
- }
3442
-
3443
2449
  switch (COMMAND) {
3444
- case 'team-sync':
3445
- case 'git-sync':
3446
- teamSync().catch((err) => {
3447
- console.error(err && err.message ? err.message : err);
3448
- process.exit(1);
3449
- });
3450
- break;
3451
- case '--version':
3452
- case '-v':
3453
- case 'version':
3454
- console.log(pkgVersion());
3455
- break;
3456
2450
  case 'init':
3457
2451
  init();
3458
2452
  upgradeNudge();
3459
2453
  break;
3460
- case 'quickstart':
3461
- case 'first-rule':
3462
- quickstart();
3463
- break;
3464
2454
  case 'quick-start':
3465
2455
  quickStart();
3466
2456
  break;
@@ -3474,12 +2464,6 @@ switch (COMMAND) {
3474
2464
  case 'mcp':
3475
2465
  serve();
3476
2466
  break;
3477
- case 'cleanup':
3478
- cleanup();
3479
- break;
3480
- case 'sync-gcp':
3481
- syncGcp();
3482
- break;
3483
2467
  case 'gate-check':
3484
2468
  gateCheck().catch((err) => {
3485
2469
  console.error(err && err.message ? err.message : err);
@@ -3503,16 +2487,6 @@ switch (COMMAND) {
3503
2487
  capture();
3504
2488
  upgradeNudge();
3505
2489
  break;
3506
- case 'feedback-self-test':
3507
- case 'dogfood':
3508
- feedbackSelfTest();
3509
- break;
3510
- case 'setup-vertex':
3511
- setupVertex(parseArgs(process.argv.slice(3))).catch((err) => {
3512
- console.error(err && err.message ? err.message : err);
3513
- process.exit(1);
3514
- });
3515
- break;
3516
2490
  case 'stats':
3517
2491
  stats();
3518
2492
  upgradeNudge();
@@ -3521,27 +2495,6 @@ switch (COMMAND) {
3521
2495
  case 'revenue':
3522
2496
  cfo();
3523
2497
  break;
3524
- case 'cost':
3525
- case 'savings':
3526
- case 'costs': {
3527
- const { main: costMain } = require(path.join(PKG_ROOT, 'scripts', 'cost-cli'));
3528
- process.exit(costMain(process.argv.slice(3)));
3529
- // process.exit doesn't return, but keep an explicit break so the switch
3530
- // cannot accidentally fall through to case 'billing:setup' if a future
3531
- // refactor wraps costMain in try/finally that intercepts the exit, or
3532
- // a test runner stubs process.exit (flagged by gitar-bot on PR #2281).
3533
- break;
3534
- }
3535
- case 'brain': {
3536
- const sub = process.argv.slice(3).find((arg) => !arg.startsWith('--'));
3537
- if (sub && ['init', 'context', 'remember', 'check', 'cleanup', 'status'].includes(sub)) {
3538
- brain();
3539
- } else {
3540
- const brainArgs = parseArgs(process.argv.slice(3));
3541
- process.exitCode = cmdBrain(brainArgs);
3542
- }
3543
- break;
3544
- }
3545
2498
  case 'billing:setup':
3546
2499
  require(path.join(PKG_ROOT, 'scripts', 'billing-setup'));
3547
2500
  break;
@@ -3558,11 +2511,6 @@ switch (COMMAND) {
3558
2511
  case 'search-lessons':
3559
2512
  lessons();
3560
2513
  break;
3561
- case 'notes': {
3562
- const { cli: notesCli } = require(path.join(PKG_ROOT, 'scripts', 'implementation-notes'));
3563
- notesCli(process.argv.slice(3));
3564
- break;
3565
- }
3566
2514
  case 'lesson-health':
3567
2515
  case 'stale': {
3568
2516
  const { initDB } = require(path.join(PKG_ROOT, 'scripts', 'lesson-db'));
@@ -3704,9 +2652,6 @@ switch (COMMAND) {
3704
2652
  case 'rules':
3705
2653
  rules();
3706
2654
  break;
3707
- case 'context-packs':
3708
- contextPacks();
3709
- break;
3710
2655
  case 'harness-audit':
3711
2656
  case 'harness':
3712
2657
  harnessAudit();
@@ -3736,12 +2681,6 @@ switch (COMMAND) {
3736
2681
  case 'llm-wiki-guardrails':
3737
2682
  aiEngineeringStackGuardrails();
3738
2683
  break;
3739
- case 'ai-inventory':
3740
- case 'ai-component-inventory':
3741
- case 'ml-bom':
3742
- case 'mlbom':
3743
- aiInventory();
3744
- break;
3745
2684
  case 'deepseek-v4-runtime-guardrails':
3746
2685
  case 'deepseek-runtime-guardrails':
3747
2686
  case 'sparse-attention-runtime-guardrails':
@@ -3806,82 +2745,6 @@ switch (COMMAND) {
3806
2745
  case 'self-heal':
3807
2746
  selfHeal();
3808
2747
  break;
3809
- case 'workflow':
3810
- case 'swarm': {
3811
- const args = parseArgs(process.argv.slice(3));
3812
- let objective = args.objective;
3813
- if (!objective) {
3814
- const firstPositional = process.argv.slice(3).find((a, idx, arr) => {
3815
- if (a.startsWith('--')) return false;
3816
- const prev = arr[idx - 1];
3817
- if (prev && prev.startsWith('--') && !prev.includes('=')) return false;
3818
- return true;
3819
- });
3820
- if (firstPositional) objective = firstPositional;
3821
- }
3822
- if (!objective) {
3823
- console.error('Error: objective is required. Run with --objective="your objective" or provide it as a positional argument.');
3824
- process.exit(1);
3825
- }
3826
- const { executeWorkflow } = require(path.join(PKG_ROOT, 'scripts', 'parallel-workflow-orchestrator'));
3827
- const concurrency = args.concurrency ? Number(args.concurrency) : undefined;
3828
- const timeoutMs = args.timeoutMs ? Number(args.timeoutMs) : undefined;
3829
- executeWorkflow(objective, { concurrency, timeoutMs, cwd: CWD })
3830
- .then((res) => {
3831
- if (args.json) {
3832
- console.log(JSON.stringify(res, null, 2));
3833
- } else {
3834
- console.log(`\n✅ Parallel workflow execution complete.`);
3835
- console.log(` Workflow ID: ${res.workflowId}`);
3836
- console.log(` Objective : ${res.objective}`);
3837
- console.log(` Duration : ${(res.durationMs / 1000).toFixed(2)}s`);
3838
- console.log(` Report Path: ${res.reportPath}`);
3839
- console.log(`\nReport Summary:\n`);
3840
- console.log(fs.readFileSync(res.reportPath, 'utf8'));
3841
- }
3842
- process.exit(0);
3843
- })
3844
- .catch((err) => {
3845
- console.error('Workflow execution failed:', err.message);
3846
- process.exit(1);
3847
- });
3848
- break;
3849
- }
3850
- case 'trial': {
3851
- // Show trial status — connects the 4K monthly npm installers to checkout
3852
- const { isProTier, isInTrialPeriod, trialDaysRemaining, getInstallAgeDays } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
3853
- const ageDays = getInstallAgeDays();
3854
- const inTrial = isInTrialPeriod();
3855
- const remaining = trialDaysRemaining();
3856
- const isPro = isProTier();
3857
- console.log('');
3858
- console.log(' ThumbGate Pro Trial');
3859
- console.log(' ──────────────────');
3860
- if (isPro && !inTrial) {
3861
- console.log(' Status: ✅ Pro (active license or API key)');
3862
- } else if (inTrial) {
3863
- const expiryDate = new Date(Date.now() + remaining * 86400000).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
3864
- console.log(` Status: 🎁 Active (${remaining} day${remaining === 1 ? '' : 's'} remaining)`);
3865
- console.log(` Expires: ${expiryDate}`);
3866
- console.log('');
3867
- console.log(' All Pro features unlocked:');
3868
- console.log(' • Unlimited prevention rules (free tier: 5)');
3869
- console.log(' • Recall, lesson search, DPO export');
3870
- console.log(' • Cross-machine sync via API key');
3871
- } else {
3872
- console.log(' Status: ⏰ Expired');
3873
- if (ageDays !== null) {
3874
- console.log(` Installed: ${Math.floor(ageDays)} days ago`);
3875
- }
3876
- console.log('');
3877
- console.log(' Free tier: 5 active rules, no recall/search/export');
3878
- }
3879
- console.log('');
3880
- console.log(` Keep Pro: ${PRO_CHECKOUT_URL}`);
3881
- console.log(` Or set: THUMBGATE_API_KEY=<your-key>`);
3882
- console.log('');
3883
- break;
3884
- }
3885
2748
  case 'pro':
3886
2749
  pro();
3887
2750
  break;
@@ -3899,24 +2762,12 @@ switch (COMMAND) {
3899
2762
  case 'status':
3900
2763
  status();
3901
2764
  break;
3902
- case 'break-glass':
3903
- case 'breakglass':
3904
- breakGlass();
3905
- break;
3906
2765
  case 'funnel':
3907
2766
  funnel();
3908
2767
  break;
3909
2768
  case 'pulse':
3910
2769
  pulse();
3911
2770
  break;
3912
- case 'check-update':
3913
- case 'upgrade-check':
3914
- checkUpdateCmd();
3915
- break;
3916
- case 'self-update':
3917
- case 'upgrade-cli':
3918
- selfUpdateCmd();
3919
- break;
3920
2771
  case 'dispatch':
3921
2772
  case 'dispatch-brief':
3922
2773
  dispatchBrief();
@@ -3942,53 +2793,6 @@ switch (COMMAND) {
3942
2793
  });
3943
2794
  break;
3944
2795
  }
3945
- case 'hermes-gate': {
3946
- // Nous Research Hermes Agent `pre_tool_call` shell hook.
3947
- // Hermes pipes each pending tool call as JSON to stdin and reads a decision from stdout;
3948
- // {"decision":"block","reason":...} vetoes the call. We reuse the SAME gate pipeline as
3949
- // `gate-check` (runAsync → secret guard, security scan, force-push / skill_manage / learned
3950
- // prevention rules) and translate the verdict into Hermes's format.
3951
- //
3952
- // Hermes `pre_tool_call` is binary (block or allow) with no warn channel, and the whole point
3953
- // of wiring it is to gate, so we run STRICT enforcement by default — otherwise ThumbGate's
3954
- // warn-by-default posture would pass every deny through and the hook would block nothing.
3955
- // Opt out with THUMBGATE_HERMES_WARN_ONLY=1; THUMBGATE_HOTFIX_BYPASS=1 still disables checks.
3956
- // Wire it in ~/.hermes/config.yaml — see adapters/hermes/config.yaml.
3957
- if (process.env.THUMBGATE_HERMES_WARN_ONLY !== '1' && process.env.THUMBGATE_HOTFIX_BYPASS !== '1') {
3958
- process.env.THUMBGATE_STRICT_ENFORCEMENT = '1';
3959
- }
3960
- const { runAsync: hermesGateRun } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
3961
- let hermesStdin = '';
3962
- process.stdin.setEncoding('utf8');
3963
- process.stdin.on('data', (chunk) => { hermesStdin += chunk; });
3964
- process.stdin.on('end', async () => {
3965
- try {
3966
- const payload = JSON.parse(hermesStdin);
3967
- // Hermes sends snake_case tool_name/tool_input — gates-engine reads these directly.
3968
- const verdict = await hermesGateRun({ tool_name: payload.tool_name, tool_input: payload.tool_input });
3969
- let parsed = {};
3970
- try { parsed = JSON.parse(verdict); } catch (_e) { parsed = {}; }
3971
- const hso = parsed.hookSpecificOutput || {};
3972
- if (hso.permissionDecision === 'deny') {
3973
- process.stdout.write(JSON.stringify({
3974
- decision: 'block',
3975
- reason: hso.permissionDecisionReason || 'Blocked by ThumbGate prevention rule.',
3976
- }) + '\n');
3977
- } else {
3978
- // warn / no match → allow. The gate engine already logged the decision.
3979
- process.stdout.write(JSON.stringify({}) + '\n');
3980
- }
3981
- process.exit(0);
3982
- } catch (err) {
3983
- // Hermes hooks fail OPEN on error/timeout — emit an explicit allow so a gate fault
3984
- // never wedges the agent (reliability ≈ enforcement; keep this fast).
3985
- process.stderr.write(`hermes-gate error: ${err.message}\n`);
3986
- process.stdout.write(JSON.stringify({}) + '\n');
3987
- process.exit(0);
3988
- }
3989
- });
3990
- break;
3991
- }
3992
2796
  case 'gate-stats':
3993
2797
  gateStats();
3994
2798
  break;
@@ -4043,29 +2847,6 @@ switch (COMMAND) {
4043
2847
  }
4044
2848
  break;
4045
2849
  }
4046
- case 'audit': {
4047
- const auditFile = process.argv[3];
4048
- if (!auditFile) {
4049
- console.error('Usage: npx thumbgate audit <path-to-transcript.txt>');
4050
- process.exit(1);
4051
- }
4052
- const { runAudit } = require(path.join(PKG_ROOT, 'scripts', 'audit'));
4053
- const { results, totalWaste, error } = runAudit(auditFile);
4054
- if (error) {
4055
- console.error(error);
4056
- process.exit(1);
4057
- }
4058
- console.log('\n🔍 AI Bill Audit Results\n');
4059
- if (results.length === 0) {
4060
- console.log('✅ No repeat-offender patterns found. Your sessions are efficient!');
4061
- } else {
4062
- console.table(results);
4063
- console.log('\n💰 Total estimated monthly waste: $' + totalWaste);
4064
- console.log('\nBlock these mistakes permanently with ThumbGate Pro:');
4065
- console.log(PRO_CHECKOUT_URL);
4066
- }
4067
- break;
4068
- }
4069
2850
  case 'dashboard':
4070
2851
  dashboard();
4071
2852
  break;
@@ -4089,86 +2870,6 @@ switch (COMMAND) {
4089
2870
  case 'compact':
4090
2871
  compact();
4091
2872
  break;
4092
- case 'subscribe': {
4093
- // Capture an installer's email so we can send the 5-minute setup
4094
- // guide + weekly tips. Drops to /v1/marketing/install-email on the
4095
- // hosted endpoint; the server fires sendNewsletterWelcomeEmail via
4096
- // Resend and persists the address for follow-up.
4097
- //
4098
- // Usage:
4099
- // npx thumbgate subscribe you@company.com
4100
- // npx thumbgate subscribe --email=you@company.com
4101
- //
4102
- // Never prompts interactively — postinstall must stay non-interactive
4103
- // for CI safety. This is the deliberate opt-in path the banner points
4104
- // at; if the operator runs it, they want the email.
4105
- const args = process.argv.slice(3);
4106
- let email = '';
4107
- for (const arg of args) {
4108
- if (arg.startsWith('--email=')) email = arg.slice('--email='.length).trim();
4109
- else if (!arg.startsWith('--')) email = arg.trim();
4110
- }
4111
- if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
4112
- console.error('Usage: npx thumbgate subscribe <email>');
4113
- console.error(' npx thumbgate subscribe --email=you@company.com');
4114
- console.error('');
4115
- console.error('We send a 5-minute setup guide + weekly tips. One-click unsubscribe.');
4116
- process.exit(1);
4117
- }
4118
- const endpoint = process.env.THUMBGATE_INSTALL_EMAIL_ENDPOINT
4119
- || 'https://thumbgate.ai/v1/marketing/install-email';
4120
- const payload = JSON.stringify({
4121
- email,
4122
- source: 'cli_subscribe',
4123
- installId: (() => {
4124
- try {
4125
- const configPath = path.join(CWD, '.thumbgate', 'config.json');
4126
- if (!fs.existsSync(configPath)) return null;
4127
- return (JSON.parse(fs.readFileSync(configPath, 'utf8')).installId) || null;
4128
- } catch { return null; }
4129
- })(),
4130
- cliVersion: pkgVersion(),
4131
- });
4132
- const url = new URL(endpoint);
4133
- const transport = url.protocol === 'https:' ? require('node:https') : require('node:http');
4134
- const req = transport.request({
4135
- method: 'POST',
4136
- hostname: url.hostname,
4137
- port: url.port || (url.protocol === 'https:' ? 443 : 80),
4138
- path: url.pathname + url.search,
4139
- headers: {
4140
- 'Content-Type': 'application/json',
4141
- 'Content-Length': Buffer.byteLength(payload),
4142
- 'User-Agent': `thumbgate-cli/${pkgVersion()}`,
4143
- },
4144
- timeout: 8000,
4145
- }, (res) => {
4146
- let body = '';
4147
- res.on('data', (chunk) => { body += chunk; });
4148
- res.on('end', () => {
4149
- if (res.statusCode >= 200 && res.statusCode < 300) {
4150
- console.log(`✓ Subscribed ${email}.`);
4151
- console.log(' Check your inbox in ~30 seconds for the setup guide.');
4152
- console.log(' Unsubscribe link is in every email.');
4153
- process.exit(0);
4154
- } else {
4155
- console.error(`✗ Subscribe failed: HTTP ${res.statusCode}${body ? ` — ${body.slice(0, 200)}` : ''}`);
4156
- process.exit(2);
4157
- }
4158
- });
4159
- });
4160
- req.on('error', (err) => {
4161
- console.error(`✗ Subscribe failed: ${err.message}`);
4162
- console.error(' Network issue? Try again, or email igor.ganapolsky@gmail.com directly.');
4163
- process.exit(3);
4164
- });
4165
- req.on('timeout', () => {
4166
- req.destroy(new Error('timeout after 8000ms'));
4167
- });
4168
- req.write(payload);
4169
- req.end();
4170
- break;
4171
- }
4172
2873
  case 'checkin': {
4173
2874
  // User check-in command — asks how it's going after install
4174
2875
  const thumbgateDir = path.join(CWD, '.thumbgate');