thumbgate 0.9.10 → 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.claude-plugin/README.md +2 -2
  2. package/.claude-plugin/marketplace.json +4 -2
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +115 -312
  6. package/adapters/README.md +1 -1
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +4 -4
  9. package/adapters/mcp/server-stdio.js +61 -1
  10. package/adapters/opencode/opencode.json +4 -2
  11. package/bin/cli.js +156 -8
  12. package/bin/memory.sh +3 -3
  13. package/config/e2e-critical-flows.json +4 -0
  14. package/config/gates/default.json +74 -2
  15. package/config/github-about.json +1 -1
  16. package/config/mcp-allowlists.json +27 -0
  17. package/package.json +22 -5
  18. package/plugins/amp-skill/INSTALL.md +1 -0
  19. package/plugins/amp-skill/SKILL.md +1 -0
  20. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  21. package/plugins/claude-codex-bridge/.mcp.json +4 -2
  22. package/plugins/claude-skill/INSTALL.md +1 -0
  23. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  24. package/plugins/codex-profile/.mcp.json +4 -2
  25. package/plugins/codex-profile/INSTALL.md +1 -1
  26. package/plugins/codex-profile/README.md +1 -1
  27. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  28. package/plugins/cursor-marketplace/README.md +3 -3
  29. package/plugins/cursor-marketplace/mcp.json +3 -1
  30. package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
  31. package/plugins/gemini-extension/INSTALL.md +3 -3
  32. package/plugins/opencode-profile/INSTALL.md +1 -1
  33. package/public/dashboard.html +15 -8
  34. package/public/index.html +125 -185
  35. package/public/js/buyer-intent.js +252 -0
  36. package/public/pro.html +1085 -0
  37. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  38. package/scripts/adk-consolidator.js +14 -2
  39. package/scripts/agent-readiness.js +3 -1
  40. package/scripts/agent-security-hardening.js +4 -4
  41. package/scripts/auto-promote-gates.js +2 -0
  42. package/scripts/auto-wire-hooks.js +105 -17
  43. package/scripts/behavioral-extraction.js +2 -6
  44. package/scripts/billing.js +107 -3
  45. package/scripts/budget-guard.js +2 -2
  46. package/scripts/build-metadata.js +14 -0
  47. package/scripts/context-engine.js +1 -0
  48. package/scripts/deploy-policy.js +3 -17
  49. package/scripts/dpo-optimizer.js +3 -6
  50. package/scripts/ensure-repo-bootstrap.js +129 -0
  51. package/scripts/export-dpo-pairs.js +2 -3
  52. package/scripts/export-kto-pairs.js +3 -4
  53. package/scripts/export-training.js +8 -6
  54. package/scripts/feedback-attribution.js +23 -11
  55. package/scripts/feedback-loop.js +40 -2
  56. package/scripts/feedback-to-rules.js +2 -1
  57. package/scripts/filesystem-search.js +3 -2
  58. package/scripts/gates-engine.js +760 -29
  59. package/scripts/generate-pretool-hook.sh +0 -0
  60. package/scripts/gtm-revenue-loop.js +20 -1
  61. package/scripts/hook-auto-capture.sh +8 -3
  62. package/scripts/hook-runtime.js +89 -0
  63. package/scripts/hook-stop-self-score.sh +3 -3
  64. package/scripts/hook-thumbgate-cache-updater.js +99 -38
  65. package/scripts/hosted-config.js +4 -16
  66. package/scripts/hybrid-feedback-context.js +54 -14
  67. package/scripts/install-mcp.js +13 -0
  68. package/scripts/intent-router.js +2 -2
  69. package/scripts/license.js +52 -14
  70. package/scripts/local-model-profile.js +3 -2
  71. package/scripts/mcp-config.js +68 -6
  72. package/scripts/meta-policy.js +4 -8
  73. package/scripts/money-watcher.js +166 -16
  74. package/scripts/obsidian-export.js +1 -0
  75. package/scripts/operational-integrity.js +480 -0
  76. package/scripts/post-everywhere.js +7 -12
  77. package/scripts/pr-manager.js +14 -11
  78. package/scripts/profile-router.js +2 -0
  79. package/scripts/prompt-dlp.js +1 -0
  80. package/scripts/publish-decision.js +10 -0
  81. package/scripts/published-cli.js +34 -0
  82. package/scripts/risk-scorer.js +3 -2
  83. package/scripts/rlhf_session_start.sh +32 -0
  84. package/scripts/skill-quality-tracker.js +3 -5
  85. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  86. package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
  87. package/scripts/social-analytics/engagement-audit.js +202 -0
  88. package/scripts/social-analytics/instagram-thumbgate-post.js +45 -7
  89. package/scripts/social-analytics/install-growth-automation.js +114 -0
  90. package/scripts/social-analytics/load-env.js +46 -0
  91. package/scripts/social-analytics/poll-all.js +3 -18
  92. package/scripts/social-analytics/pollers/zernio.js +3 -0
  93. package/scripts/social-analytics/publish-instagram-thumbgate.js +22 -3
  94. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  95. package/scripts/social-analytics/publishers/reddit.js +7 -12
  96. package/scripts/social-analytics/publishers/zernio.js +210 -22
  97. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
  98. package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
  99. package/scripts/social-analytics/sync-launch-assets.js +185 -0
  100. package/scripts/social-post-hourly.js +185 -0
  101. package/scripts/social-quality-gate.js +119 -3
  102. package/scripts/social-reply-monitor.js +148 -32
  103. package/scripts/statusline-cache-path.js +27 -0
  104. package/scripts/statusline-meta.js +22 -0
  105. package/scripts/statusline.sh +24 -32
  106. package/scripts/sync-version.js +11 -3
  107. package/scripts/test-coverage.js +20 -13
  108. package/scripts/tool-registry.js +97 -0
  109. package/scripts/train_from_feedback.py +32 -9
  110. package/scripts/validate-feedback.js +3 -2
  111. package/scripts/vector-store.js +2 -3
  112. package/scripts/verify-obsidian-setup.sh +3 -3
  113. package/src/api/server.js +281 -33
@@ -3,6 +3,9 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execFileSync } = require('child_process');
6
+ const { publishedCliArgs, runPublishedCliHelp } = require('./published-cli');
7
+ const DEFAULT_PKG_ROOT = path.join(__dirname, '..');
8
+ const cliAvailabilityCache = new Map();
6
9
 
7
10
  function isSourceCheckout(pkgRoot) {
8
11
  return fs.existsSync(path.join(pkgRoot, '.git'));
@@ -17,17 +20,40 @@ function parseWorktreePaths(raw) {
17
20
  }
18
21
 
19
22
  function resolveStableSourceRoot(pkgRoot) {
20
- if (!isSourceCheckout(pkgRoot)) {
23
+ const effectivePkgRoot =
24
+ typeof pkgRoot === 'string' && pkgRoot.trim() ? pkgRoot : DEFAULT_PKG_ROOT;
25
+
26
+ if (!isSourceCheckout(effectivePkgRoot)) {
21
27
  return null;
22
28
  }
23
29
 
30
+ let preferredBasenames = [];
31
+ try {
32
+ const pkg = JSON.parse(fs.readFileSync(path.join(effectivePkgRoot, 'package.json'), 'utf8'));
33
+ const packageName = String(pkg && pkg.name || '').trim().toLowerCase();
34
+ if (packageName) {
35
+ preferredBasenames.push(packageName);
36
+ preferredBasenames.push(packageName.replace(/[^a-z0-9]+/g, ''));
37
+ }
38
+ } catch (_) {
39
+ preferredBasenames = [];
40
+ }
41
+
24
42
  try {
25
- const output = execFileSync('git', ['-C', pkgRoot, 'worktree', 'list', '--porcelain'], {
43
+ const output = execFileSync('git', ['-C', effectivePkgRoot, 'worktree', 'list', '--porcelain'], {
26
44
  encoding: 'utf8',
27
45
  stdio: ['ignore', 'pipe', 'ignore'],
28
46
  });
29
47
  const worktreePaths = parseWorktreePaths(output);
30
48
 
49
+ for (const worktreePath of worktreePaths) {
50
+ const baseName = path.basename(worktreePath).toLowerCase();
51
+ const normalizedBaseName = baseName.replace(/[^a-z0-9]+/g, '');
52
+ if (preferredBasenames.includes(baseName) || preferredBasenames.includes(normalizedBaseName)) {
53
+ return worktreePath;
54
+ }
55
+ }
56
+
31
57
  for (const worktreePath of worktreePaths) {
32
58
  const gitPath = path.join(worktreePath, '.git');
33
59
  if (!fs.existsSync(gitPath)) {
@@ -38,10 +64,10 @@ function resolveStableSourceRoot(pkgRoot) {
38
64
  }
39
65
  }
40
66
  } catch (_) {
41
- return pkgRoot;
67
+ return effectivePkgRoot;
42
68
  }
43
69
 
44
- return pkgRoot;
70
+ return effectivePkgRoot;
45
71
  }
46
72
 
47
73
  function resolveGitCommonDir(dirPath) {
@@ -76,7 +102,7 @@ function resolveLocalServerPath(pkgRoot, scope = 'project') {
76
102
  function portableMcpEntry(pkgVersion) {
77
103
  return {
78
104
  command: 'npx',
79
- args: ['-y', `thumbgate@${pkgVersion}`, 'serve'],
105
+ args: publishedCliArgs(pkgVersion, ['serve']),
80
106
  };
81
107
  }
82
108
 
@@ -125,17 +151,53 @@ function isVersionPublished(pkgVersion) {
125
151
  return published;
126
152
  }
127
153
 
154
+ function publishedCliOverride() {
155
+ const override = String(process.env.THUMBGATE_PUBLISHED_CLI_STATE || '').trim().toLowerCase();
156
+ if (override === 'available') {
157
+ return true;
158
+ }
159
+ if (override === 'unavailable') {
160
+ return false;
161
+ }
162
+ return null;
163
+ }
164
+
165
+ function publishedCliAvailable(pkgVersion) {
166
+ if (!isVersionPublished(pkgVersion)) {
167
+ return false;
168
+ }
169
+ const override = publishedCliOverride();
170
+ if (override !== null) {
171
+ return override;
172
+ }
173
+ if (cliAvailabilityCache.has(pkgVersion)) {
174
+ return cliAvailabilityCache.get(pkgVersion);
175
+ }
176
+
177
+ let available = false;
178
+ try {
179
+ runPublishedCliHelp(pkgVersion, { timeout: 8000 });
180
+ available = true;
181
+ } catch (_) {
182
+ available = false;
183
+ }
184
+
185
+ cliAvailabilityCache.set(pkgVersion, available);
186
+ return available;
187
+ }
188
+
128
189
  function resolveMcpEntry({ pkgRoot, pkgVersion, scope = 'project', targetDir = pkgRoot }) {
129
190
  if (!isSourceCheckout(pkgRoot)) {
130
191
  return portableMcpEntry(pkgVersion);
131
192
  }
132
- if (scope === 'project' && !isSameCheckoutFamily(pkgRoot, targetDir) && isVersionPublished(pkgVersion)) {
193
+ if (scope === 'project' && !isSameCheckoutFamily(pkgRoot, targetDir) && publishedCliAvailable(pkgVersion)) {
133
194
  return portableMcpEntry(pkgVersion);
134
195
  }
135
196
  return localMcpEntry(pkgRoot, scope);
136
197
  }
137
198
 
138
199
  module.exports = {
200
+ publishedCliAvailable,
139
201
  isVersionPublished,
140
202
  isSourceCheckout,
141
203
  isSameCheckoutFamily,
@@ -17,10 +17,10 @@
17
17
 
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
- const os = require('os');
21
20
  const { parseTimestamp } = require('./feedback-schema');
22
21
  const { timeDecayWeight } = require('./thompson-sampling');
23
22
  const { inferDomain } = require('./feedback-loop');
23
+ const { resolveFeedbackDir } = require('./feedback-paths');
24
24
 
25
25
  const MIN_OCCURRENCES = 2;
26
26
  const RECENT_DAYS = 7;
@@ -30,13 +30,11 @@ const RECENT_MS = RECENT_DAYS * 24 * 3600 * 1000;
30
30
  * Extract meta-policy rules from memory-log.jsonl feedback trends.
31
31
  *
32
32
  * @param {object} opts
33
- * @param {string} [opts.feedbackDir] - Override feedback directory (default: THUMBGATE_FEEDBACK_DIR or ~/.claude/memory/feedback)
33
+ * @param {string} [opts.feedbackDir] - Override feedback directory (default: active ThumbGate feedback dir)
34
34
  * @returns {Array<{category: string, confidence: number, trend: string, occurrence_count: number, last_seen: string}>}
35
35
  */
36
36
  function extractMetaPolicyRules(opts = {}) {
37
- const feedbackDir = opts.feedbackDir
38
- || process.env.THUMBGATE_FEEDBACK_DIR
39
- || path.join(os.homedir(), '.claude', 'memory', 'feedback');
37
+ const feedbackDir = opts.feedbackDir || resolveFeedbackDir();
40
38
 
41
39
  const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
42
40
 
@@ -160,9 +158,7 @@ function extractMetaPolicyRules(opts = {}) {
160
158
  * @returns {{ rules: Array, outputPath: string }}
161
159
  */
162
160
  function run(opts = {}) {
163
- const feedbackDir = opts.feedbackDir
164
- || process.env.THUMBGATE_FEEDBACK_DIR
165
- || path.join(os.homedir(), '.claude', 'memory', 'feedback');
161
+ const feedbackDir = opts.feedbackDir || resolveFeedbackDir();
166
162
 
167
163
  const rules = extractMetaPolicyRules({ ...opts, feedbackDir });
168
164
 
@@ -6,10 +6,19 @@
6
6
 
7
7
  'use strict';
8
8
 
9
+ const fs = require('node:fs');
10
+ const path = require('node:path');
9
11
  const { getOperationalBillingSummary } = require('./operational-summary');
10
12
 
11
- function getCommercialRevenueSnapshot(summary) {
12
- const revenue = summary.revenue || {};
13
+ const DEFAULT_STATE_PATH = path.resolve(__dirname, '..', '.thumbgate', 'commercial-watch-state.json');
14
+ const DEFAULT_ALERT_LOG_PATH = path.resolve(__dirname, '..', '.thumbgate', 'commercial-alerts.jsonl');
15
+
16
+ function ensureParentDir(filePath) {
17
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
18
+ }
19
+
20
+ function getCommercialRevenueSnapshot(summary = {}) {
21
+ const revenue = summary && typeof summary === 'object' ? summary.revenue || {} : {};
13
22
  return {
14
23
  paidOrders: revenue.paidOrders || 0,
15
24
  bookedRevenueCents: revenue.bookedRevenueCents || 0,
@@ -18,10 +27,86 @@ function getCommercialRevenueSnapshot(summary) {
18
27
  };
19
28
  }
20
29
 
21
- async function watchMoney(intervalMs = 10000) {
30
+ function readSnapshotState(statePath = DEFAULT_STATE_PATH) {
31
+ try {
32
+ if (!fs.existsSync(statePath)) {
33
+ return null;
34
+ }
35
+ return JSON.parse(fs.readFileSync(statePath, 'utf8'));
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ function writeSnapshotState(snapshot, statePath = DEFAULT_STATE_PATH) {
42
+ ensureParentDir(statePath);
43
+ fs.writeFileSync(statePath, `${JSON.stringify(snapshot, null, 2)}\n`, 'utf8');
44
+ return statePath;
45
+ }
46
+
47
+ function buildCommercialAlert(previousSnapshot = {}, currentSnapshot = {}, meta = {}) {
48
+ const newPaidOrders = (currentSnapshot.paidOrders || 0) - (previousSnapshot.paidOrders || 0);
49
+ const newBookedRevenueCents = (currentSnapshot.bookedRevenueCents || 0) - (previousSnapshot.bookedRevenueCents || 0);
50
+
51
+ if (newPaidOrders <= 0 && newBookedRevenueCents <= 0) {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ detectedAt: new Date().toISOString(),
57
+ source: meta.source || null,
58
+ fallbackReason: meta.fallbackReason || null,
59
+ newPaidOrders,
60
+ newBookedRevenueCents,
61
+ latestPaidAt: currentSnapshot.latestPaidAt || null,
62
+ latestPaidOrder: currentSnapshot.latestPaidOrder || null,
63
+ paidOrders: currentSnapshot.paidOrders || 0,
64
+ bookedRevenueCents: currentSnapshot.bookedRevenueCents || 0,
65
+ };
66
+ }
67
+
68
+ function recordCommercialAlert(alert, alertLogPath = DEFAULT_ALERT_LOG_PATH) {
69
+ ensureParentDir(alertLogPath);
70
+ fs.appendFileSync(alertLogPath, `${JSON.stringify(alert)}\n`, 'utf8');
71
+ return alertLogPath;
72
+ }
73
+
74
+ async function checkForCommercialChange(options = {}) {
75
+ const statePath = options.statePath || DEFAULT_STATE_PATH;
76
+ const alertLogPath = options.alertLogPath || DEFAULT_ALERT_LOG_PATH;
77
+ const previousSnapshot = options.previousSnapshot || readSnapshotState(statePath) || getCommercialRevenueSnapshot();
78
+ const summaryResolver = options.getSummary || getOperationalBillingSummary;
79
+ const { source, summary, fallbackReason } = await summaryResolver();
80
+ const currentSnapshot = getCommercialRevenueSnapshot(summary);
81
+ const alert = buildCommercialAlert(previousSnapshot, currentSnapshot, {
82
+ source,
83
+ fallbackReason,
84
+ });
85
+
86
+ writeSnapshotState(currentSnapshot, statePath);
87
+ if (alert) {
88
+ recordCommercialAlert(alert, alertLogPath);
89
+ }
90
+
91
+ return {
92
+ changed: Boolean(alert),
93
+ alert,
94
+ previousSnapshot,
95
+ currentSnapshot,
96
+ source,
97
+ fallbackReason: fallbackReason || null,
98
+ statePath,
99
+ alertLogPath,
100
+ };
101
+ }
102
+
103
+ async function watchMoney(intervalMs = 10000, options = {}) {
22
104
  console.log('šŸ‘€ Money Watcher activated. Polling billing summary for commercial changes...');
23
105
  const initialState = await getOperationalBillingSummary();
24
- let initialSnapshot = getCommercialRevenueSnapshot(initialState.summary);
106
+ let initialSnapshot = options.initialSnapshot
107
+ || readSnapshotState(options.statePath || DEFAULT_STATE_PATH)
108
+ || getCommercialRevenueSnapshot(initialState.summary);
109
+ writeSnapshotState(initialSnapshot, options.statePath || DEFAULT_STATE_PATH);
25
110
  let polling = false;
26
111
 
27
112
  return setInterval(async () => {
@@ -30,21 +115,18 @@ async function watchMoney(intervalMs = 10000) {
30
115
  try {
31
116
  const { source, summary, fallbackReason } = await getOperationalBillingSummary();
32
117
  const currentSnapshot = getCommercialRevenueSnapshot(summary);
118
+ const alert = buildCommercialAlert(initialSnapshot, currentSnapshot, {
119
+ source,
120
+ fallbackReason,
121
+ });
122
+ writeSnapshotState(currentSnapshot, options.statePath || DEFAULT_STATE_PATH);
33
123
 
34
- const newPaidOrders = currentSnapshot.paidOrders - initialSnapshot.paidOrders;
35
- const newBookedRevenue = currentSnapshot.bookedRevenueCents - initialSnapshot.bookedRevenueCents;
36
-
37
- if (newPaidOrders > 0 || newBookedRevenue > 0) {
124
+ if (alert) {
125
+ recordCommercialAlert(alert, options.alertLogPath || DEFAULT_ALERT_LOG_PATH);
38
126
  console.log('\n🚨🚨🚨 COMMERCIAL ALERT: NET-NEW PAID ACTIVITY DETECTED! 🚨🚨🚨');
39
127
  console.log('Operational billing summary:');
40
128
  console.log(JSON.stringify({
41
- source,
42
- fallbackReason,
43
- newPaidOrders,
44
- newBookedRevenueCents: newBookedRevenue,
45
- latestPaidAt: currentSnapshot.latestPaidAt,
46
- latestPaidOrder: currentSnapshot.latestPaidOrder,
47
- bookedRevenueCents: currentSnapshot.bookedRevenueCents,
129
+ ...alert,
48
130
  activeKeys: summary.keys.active,
49
131
  totalUsage: summary.keys.totalUsage,
50
132
  }, null, 2));
@@ -58,14 +140,82 @@ async function watchMoney(intervalMs = 10000) {
58
140
  }, intervalMs);
59
141
  }
60
142
 
143
+ function parseArgs(argv = []) {
144
+ const options = {
145
+ once: false,
146
+ intervalMs: 10000,
147
+ statePath: DEFAULT_STATE_PATH,
148
+ alertLogPath: DEFAULT_ALERT_LOG_PATH,
149
+ };
150
+
151
+ for (let index = 0; index < argv.length; index += 1) {
152
+ const arg = String(argv[index] || '').trim();
153
+
154
+ if (arg === '--once') {
155
+ options.once = true;
156
+ continue;
157
+ }
158
+
159
+ if (arg === '--interval-ms' && argv[index + 1]) {
160
+ options.intervalMs = Number.parseInt(argv[index + 1], 10) || options.intervalMs;
161
+ index += 1;
162
+ continue;
163
+ }
164
+
165
+ if (arg.startsWith('--interval-ms=')) {
166
+ options.intervalMs = Number.parseInt(arg.split('=').slice(1).join('='), 10) || options.intervalMs;
167
+ continue;
168
+ }
169
+
170
+ if (arg === '--state-path' && argv[index + 1]) {
171
+ options.statePath = path.resolve(String(argv[index + 1]));
172
+ index += 1;
173
+ continue;
174
+ }
175
+
176
+ if (arg.startsWith('--state-path=')) {
177
+ options.statePath = path.resolve(arg.split('=').slice(1).join('='));
178
+ continue;
179
+ }
180
+
181
+ if (arg === '--alert-log-path' && argv[index + 1]) {
182
+ options.alertLogPath = path.resolve(String(argv[index + 1]));
183
+ index += 1;
184
+ continue;
185
+ }
186
+
187
+ if (arg.startsWith('--alert-log-path=')) {
188
+ options.alertLogPath = path.resolve(arg.split('=').slice(1).join('='));
189
+ }
190
+ }
191
+
192
+ return options;
193
+ }
194
+
61
195
  if (require.main === module) {
62
- watchMoney().catch((err) => {
196
+ const options = parseArgs(process.argv.slice(2));
197
+ const runner = options.once
198
+ ? checkForCommercialChange(options).then((result) => {
199
+ console.log(JSON.stringify(result, null, 2));
200
+ return result;
201
+ })
202
+ : watchMoney(options.intervalMs, options);
203
+
204
+ runner.catch((err) => {
63
205
  console.error(err && err.message ? err.message : err);
64
206
  process.exit(1);
65
207
  });
66
208
  }
67
209
 
68
210
  module.exports = {
211
+ DEFAULT_ALERT_LOG_PATH,
212
+ DEFAULT_STATE_PATH,
213
+ buildCommercialAlert,
214
+ checkForCommercialChange,
69
215
  getCommercialRevenueSnapshot,
216
+ parseArgs,
217
+ readSnapshotState,
218
+ recordCommercialAlert,
70
219
  watchMoney,
220
+ writeSnapshotState,
71
221
  };
@@ -346,6 +346,7 @@ function exportGates(configPath, outputDir) {
346
346
  // Read auto-promoted gates if present (check common locations)
347
347
  const autoGatePaths = [
348
348
  path.join(path.dirname(configPath), '..', '.thumbgate', 'auto-promoted-gates.json'),
349
+ path.join(path.dirname(configPath), '..', '.rlhf', 'auto-promoted-gates.json'),
349
350
  path.join(path.dirname(configPath), '..', '.claude', 'memory', 'feedback', 'auto-promoted-gates.json'),
350
351
  ];
351
352
  for (const agPath of autoGatePaths) {