thumbgate 0.9.9 → 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.claude-plugin/README.md +4 -4
  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 +2 -2
  7. package/adapters/amp/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
  8. package/adapters/chatgpt/openapi.yaml +2 -2
  9. package/adapters/claude/.mcp.json +3 -3
  10. package/adapters/codex/config.toml +4 -4
  11. package/adapters/gemini/function-declarations.json +1 -1
  12. package/adapters/mcp/server-stdio.js +66 -6
  13. package/adapters/opencode/opencode.json +4 -2
  14. package/bin/cli.js +188 -39
  15. package/config/e2e-critical-flows.json +4 -0
  16. package/config/gates/default.json +74 -2
  17. package/config/github-about.json +1 -1
  18. package/config/mcp-allowlists.json +33 -6
  19. package/config/skill-packs/react-testing.json +1 -1
  20. package/config/tessl-tiles.json +3 -3
  21. package/openapi/openapi.yaml +2 -2
  22. package/package.json +23 -9
  23. package/plugins/amp-skill/INSTALL.md +3 -2
  24. package/plugins/amp-skill/SKILL.md +1 -0
  25. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  26. package/plugins/claude-codex-bridge/.mcp.json +5 -3
  27. package/plugins/claude-codex-bridge/README.md +1 -1
  28. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +1 -1
  29. package/plugins/claude-skill/INSTALL.md +4 -3
  30. package/plugins/claude-skill/SKILL.md +1 -1
  31. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  32. package/plugins/codex-profile/.mcp.json +5 -3
  33. package/plugins/codex-profile/INSTALL.md +2 -2
  34. package/plugins/codex-profile/README.md +1 -1
  35. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  36. package/plugins/cursor-marketplace/README.md +5 -5
  37. package/plugins/cursor-marketplace/mcp.json +4 -2
  38. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +1 -1
  39. package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
  40. package/plugins/gemini-extension/INSTALL.md +4 -4
  41. package/plugins/opencode-profile/INSTALL.md +5 -5
  42. package/public/dashboard.html +15 -8
  43. package/public/index.html +134 -375
  44. package/public/js/buyer-intent.js +252 -0
  45. package/public/pro.html +1085 -0
  46. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  47. package/scripts/adk-consolidator.js +17 -5
  48. package/scripts/agent-readiness.js +3 -1
  49. package/scripts/agent-security-hardening.js +4 -4
  50. package/scripts/auto-promote-gates.js +8 -0
  51. package/scripts/auto-wire-hooks.js +105 -21
  52. package/scripts/billing.js +111 -7
  53. package/scripts/build-metadata.js +14 -0
  54. package/scripts/check-congruence.js +1 -1
  55. package/scripts/context-engine.js +2 -1
  56. package/scripts/daemon-manager.js +2 -2
  57. package/scripts/dashboard.js +2 -2
  58. package/scripts/data-governance.js +1 -1
  59. package/scripts/deploy-gcp.sh +1 -1
  60. package/scripts/deploy-policy.js +22 -4
  61. package/scripts/dispatch-brief.js +1 -1
  62. package/scripts/ensure-repo-bootstrap.js +1 -1
  63. package/scripts/feedback-attribution.js +22 -10
  64. package/scripts/feedback-fallback.js +3 -2
  65. package/scripts/feedback-inbox-read.js +1 -1
  66. package/scripts/feedback-loop.js +41 -3
  67. package/scripts/feedback-paths.js +8 -8
  68. package/scripts/feedback-schema.js +1 -1
  69. package/scripts/feedback-to-memory.js +2 -2
  70. package/scripts/filesystem-search.js +2 -2
  71. package/scripts/gates-engine.js +765 -34
  72. package/scripts/generate-paperbanana-diagrams.sh +3 -3
  73. package/scripts/github-about.js +1 -1
  74. package/scripts/gtm-revenue-loop.js +20 -1
  75. package/scripts/hook-runtime.js +89 -0
  76. package/scripts/hook-stop-self-score.sh +3 -3
  77. package/scripts/hook-thumbgate-cache-updater.js +98 -37
  78. package/scripts/hosted-config.js +12 -10
  79. package/scripts/hybrid-feedback-context.js +54 -13
  80. package/scripts/install-mcp.js +14 -1
  81. package/scripts/intent-router.js +1 -1
  82. package/scripts/internal-agent-bootstrap.js +1 -1
  83. package/scripts/lesson-inference.js +6 -1
  84. package/scripts/license.js +54 -16
  85. package/scripts/mcp-config.js +69 -7
  86. package/scripts/memory-migration.js +1 -1
  87. package/scripts/money-watcher.js +166 -16
  88. package/scripts/operational-integrity.js +480 -0
  89. package/scripts/optimize-context.js +1 -1
  90. package/scripts/perplexity-marketing.js +1 -1
  91. package/scripts/post-everywhere.js +7 -12
  92. package/scripts/post-to-x.js +1 -1
  93. package/scripts/pr-manager.js +14 -11
  94. package/scripts/problem-detail.js +10 -10
  95. package/scripts/profile-router.js +2 -0
  96. package/scripts/prompt-dlp.js +1 -0
  97. package/scripts/prove-adapters.js +6 -6
  98. package/scripts/prove-automation.js +1 -1
  99. package/scripts/prove-autoresearch.js +1 -1
  100. package/scripts/prove-claim-verification.js +3 -3
  101. package/scripts/prove-data-pipeline.js +5 -5
  102. package/scripts/prove-data-quality.js +1 -1
  103. package/scripts/prove-evolution.js +7 -7
  104. package/scripts/prove-harnesses.js +2 -2
  105. package/scripts/prove-lancedb.js +2 -2
  106. package/scripts/prove-local-intelligence.js +1 -1
  107. package/scripts/prove-loop-closure.js +1 -1
  108. package/scripts/prove-predictive-insights.js +2 -2
  109. package/scripts/prove-runtime.js +6 -6
  110. package/scripts/prove-seo-gsd.js +1 -1
  111. package/scripts/prove-settings.js +4 -4
  112. package/scripts/prove-subway-upgrades.js +1 -1
  113. package/scripts/prove-tessl.js +2 -2
  114. package/scripts/prove-xmemory.js +2 -2
  115. package/scripts/publish-decision.js +10 -0
  116. package/scripts/published-cli.js +34 -0
  117. package/scripts/rate-limiter.js +2 -2
  118. package/scripts/reddit-monitor-cron.sh +2 -2
  119. package/scripts/reminder-engine.js +1 -1
  120. package/scripts/schedule-manager.js +3 -3
  121. package/scripts/self-healing-check.js +1 -1
  122. package/scripts/shieldcortex-memory-firewall-runner.mjs +1 -1
  123. package/scripts/skill-quality-tracker.js +1 -1
  124. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  125. package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
  126. package/scripts/social-analytics/engagement-audit.js +202 -0
  127. package/scripts/social-analytics/generate-instagram-card.js +1 -1
  128. package/scripts/social-analytics/instagram-thumbgate-post.js +5 -1
  129. package/scripts/social-analytics/install-growth-automation.js +114 -0
  130. package/scripts/social-analytics/publish-instagram-thumbgate.js +8 -2
  131. package/scripts/social-analytics/publish-thumbgate-launch.js +1 -1
  132. package/scripts/social-analytics/publishers/reddit.js +7 -12
  133. package/scripts/social-analytics/publishers/zernio.js +19 -0
  134. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
  135. package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
  136. package/scripts/social-analytics/sync-launch-assets.js +185 -0
  137. package/scripts/social-pipeline.js +2 -2
  138. package/scripts/social-post-hourly.js +185 -0
  139. package/scripts/social-quality-gate.js +119 -3
  140. package/scripts/social-reply-monitor.js +150 -34
  141. package/scripts/statusline-cache-path.js +27 -0
  142. package/scripts/statusline-meta.js +22 -0
  143. package/scripts/statusline.sh +24 -32
  144. package/scripts/sync-version.js +24 -12
  145. package/scripts/telemetry-analytics.js +4 -4
  146. package/scripts/tessl-export.js +1 -1
  147. package/scripts/test-coverage.js +20 -13
  148. package/scripts/thumbgate-search.js +2 -2
  149. package/scripts/tool-registry.js +98 -1
  150. package/scripts/train_from_feedback.py +1 -1
  151. package/scripts/user-profile.js +4 -4
  152. package/scripts/validate-feedback.js +1 -1
  153. package/scripts/vector-store.js +1 -1
  154. package/scripts/verification-loop.js +1 -1
  155. package/scripts/verify-run.js +1 -1
  156. package/scripts/weekly-auto-post.js +1 -1
  157. package/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
  158. package/src/api/server.js +291 -41
  159. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  160. package/scripts/social-analytics/db/social-analytics.db +0 -0
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { execFileSync, spawnSync } = require('child_process');
7
+
8
+ const DEFAULT_BASE_BRANCH = 'main';
9
+ const DEFAULT_RELEASE_SENSITIVE_GLOBS = [
10
+ 'package.json',
11
+ 'package-lock.json',
12
+ 'server.json',
13
+ '.github/workflows/ci.yml',
14
+ '.github/workflows/publish-*.yml',
15
+ 'scripts/publish-decision.js',
16
+ 'scripts/pr-manager.js',
17
+ 'scripts/gates-engine.js',
18
+ 'scripts/tool-registry.js',
19
+ 'src/api/server.js',
20
+ 'adapters/mcp/server-stdio.js',
21
+ 'config/gates/**',
22
+ 'config/mcp-allowlists.json',
23
+ ];
24
+
25
+ function normalizePosix(filePath) {
26
+ return String(filePath || '')
27
+ .replace(/\\/g, '/')
28
+ .replace(/^\.\//, '')
29
+ .replace(/^\/+/, '')
30
+ .trim();
31
+ }
32
+
33
+ function normalizeGlob(glob) {
34
+ return normalizePosix(glob).replace(/\/+$/, '');
35
+ }
36
+
37
+ function sanitizeGlobList(globs) {
38
+ if (!Array.isArray(globs)) return [];
39
+ return [...new Set(globs.map((glob) => normalizeGlob(glob)).filter(Boolean))];
40
+ }
41
+
42
+ function globToRegExp(glob) {
43
+ const normalized = normalizeGlob(glob);
44
+ let pattern = '^';
45
+ for (let i = 0; i < normalized.length; i++) {
46
+ const char = normalized[i];
47
+ const next = normalized[i + 1];
48
+ if (char === '*') {
49
+ if (next === '*') {
50
+ pattern += '.*';
51
+ i += 1;
52
+ } else {
53
+ pattern += '[^/]*';
54
+ }
55
+ continue;
56
+ }
57
+ if ('\\^$+?.()|{}[]'.includes(char)) {
58
+ pattern += `\\${char}`;
59
+ continue;
60
+ }
61
+ pattern += char;
62
+ }
63
+ pattern += '$';
64
+ return new RegExp(pattern);
65
+ }
66
+
67
+ function matchesAnyGlob(filePath, globs) {
68
+ const normalized = sanitizeGlobList(globs);
69
+ if (!filePath || normalized.length === 0) return false;
70
+ return normalized.some((glob) => {
71
+ try {
72
+ return globToRegExp(glob).test(normalizePosix(filePath));
73
+ } catch {
74
+ return false;
75
+ }
76
+ });
77
+ }
78
+
79
+ function runGit(repoPath, args) {
80
+ return execFileSync('git', args, {
81
+ cwd: repoPath,
82
+ encoding: 'utf8',
83
+ stdio: ['ignore', 'pipe', 'ignore'],
84
+ }).trim();
85
+ }
86
+
87
+ function tryRunGit(repoPath, args) {
88
+ try {
89
+ return runGit(repoPath, args);
90
+ } catch {
91
+ return '';
92
+ }
93
+ }
94
+
95
+ function resolveRepoRoot(repoPath = process.cwd()) {
96
+ try {
97
+ return runGit(repoPath, ['rev-parse', '--show-toplevel']);
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function gitRefExists(repoPath, ref) {
104
+ if (!repoPath || !ref) return false;
105
+ try {
106
+ execFileSync('git', ['rev-parse', '--verify', ref], {
107
+ cwd: repoPath,
108
+ stdio: 'ignore',
109
+ });
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ function isSafeBranchName(branchName) {
117
+ const normalized = String(branchName || '').trim();
118
+ if (!normalized) return false;
119
+ if (normalized.startsWith('-')) return false;
120
+ if (!/^[A-Za-z0-9._/-]+$/.test(normalized)) return false;
121
+ if (normalized.includes('..') || normalized.includes('//') || normalized.includes('@{')) return false;
122
+ if (normalized.endsWith('.') || normalized.endsWith('/')) return false;
123
+ return true;
124
+ }
125
+
126
+ function fetchBaseBranch(repoPath, baseBranch) {
127
+ if (!repoPath || !isSafeBranchName(baseBranch)) return false;
128
+ // Fetch the remote tracking refs without passing user-controlled branch names to git.
129
+ const result = spawnSync('git', ['fetch', '--no-tags', '--depth=64', 'origin'], {
130
+ cwd: repoPath,
131
+ encoding: 'utf8',
132
+ });
133
+ return result.status === 0;
134
+ }
135
+
136
+ function resolveBaseRef(repoPath, baseBranch = DEFAULT_BASE_BRANCH, { fetchIfMissing = false } = {}) {
137
+ if (!isSafeBranchName(baseBranch)) return null;
138
+ const remoteRef = `refs/remotes/origin/${baseBranch}`;
139
+ if (gitRefExists(repoPath, remoteRef)) {
140
+ return `origin/${baseBranch}`;
141
+ }
142
+ if (fetchIfMissing) {
143
+ fetchBaseBranch(repoPath, baseBranch);
144
+ if (gitRefExists(repoPath, remoteRef)) {
145
+ return `origin/${baseBranch}`;
146
+ }
147
+ }
148
+ if (gitRefExists(repoPath, baseBranch)) {
149
+ return baseBranch;
150
+ }
151
+ return null;
152
+ }
153
+
154
+ function getCurrentBranch(repoPath) {
155
+ return tryRunGit(repoPath, ['rev-parse', '--abbrev-ref', 'HEAD']) || null;
156
+ }
157
+
158
+ function getHeadSha(repoPath) {
159
+ return tryRunGit(repoPath, ['rev-parse', 'HEAD']) || null;
160
+ }
161
+
162
+ function readPackageVersion(repoPath, ref = 'HEAD') {
163
+ try {
164
+ let raw;
165
+ if (ref === 'HEAD') {
166
+ raw = fs.readFileSync(path.join(repoPath, 'package.json'), 'utf8');
167
+ } else {
168
+ raw = runGit(repoPath, ['show', `${ref}:package.json`]);
169
+ }
170
+ return JSON.parse(raw).version || null;
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+
176
+ function parseSemver(version) {
177
+ const match = String(version || '').trim().match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
178
+ if (!match) return null;
179
+ return match.slice(1).map((part) => Number(part));
180
+ }
181
+
182
+ function compareSemver(left, right) {
183
+ const a = parseSemver(left);
184
+ const b = parseSemver(right);
185
+ if (!a || !b) return null;
186
+ for (let i = 0; i < 3; i += 1) {
187
+ if (a[i] > b[i]) return 1;
188
+ if (a[i] < b[i]) return -1;
189
+ }
190
+ return 0;
191
+ }
192
+
193
+ function listChangedFilesAgainstBase(repoPath, baseBranch = DEFAULT_BASE_BRANCH, { fetchIfMissing = false } = {}) {
194
+ const baseRef = resolveBaseRef(repoPath, baseBranch, { fetchIfMissing });
195
+ if (!baseRef) return [];
196
+ const diff = tryRunGit(repoPath, ['diff', '--name-only', `${baseRef}...HEAD`]);
197
+ return diff.split('\n').map((line) => normalizePosix(line)).filter(Boolean);
198
+ }
199
+
200
+ function findReleaseSensitiveFiles(files, globs = DEFAULT_RELEASE_SENSITIVE_GLOBS) {
201
+ return (Array.isArray(files) ? files : []).filter((filePath) => matchesAnyGlob(filePath, globs));
202
+ }
203
+
204
+ function isHeadReachableFrom(repoPath, ref, commit = 'HEAD') {
205
+ if (!repoPath || !ref) return false;
206
+ const result = spawnSync('git', ['merge-base', '--is-ancestor', commit, ref], {
207
+ cwd: repoPath,
208
+ encoding: 'utf8',
209
+ });
210
+ return result.status === 0;
211
+ }
212
+
213
+ function runGh(args) {
214
+ return spawnSync('gh', args, {
215
+ encoding: 'utf8',
216
+ stdio: ['ignore', 'pipe', 'pipe'],
217
+ });
218
+ }
219
+
220
+ function findOpenPrForBranch({ branchName, runner = runGh } = {}) {
221
+ const normalizedBranch = String(branchName || '').trim();
222
+ if (!normalizedBranch) return null;
223
+ if (!process.env.GH_TOKEN && !process.env.GITHUB_TOKEN) {
224
+ return null;
225
+ }
226
+ const result = runner(['pr', 'list', '--head', normalizedBranch, '--state', 'open', '--json', 'number,state,isDraft,url']);
227
+ if (!result || result.status !== 0) {
228
+ return null;
229
+ }
230
+ try {
231
+ const prs = JSON.parse(result.stdout || '[]');
232
+ return Array.isArray(prs) && prs.length > 0 ? prs[0] : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ function classifyCommand(command) {
239
+ const text = String(command || '').trim();
240
+ return {
241
+ text,
242
+ isPrCreate: /\bgh\s+pr\s+create\b/i.test(text),
243
+ isPrMerge: /\bgh\s+pr\s+merge\b/i.test(text),
244
+ isPublish: /\b(?:npm|yarn|pnpm)\s+publish\b/i.test(text),
245
+ isReleaseCreate: /\bgh\s+release\s+create\b/i.test(text),
246
+ isTagCreate: /\bgit\s+tag\b/i.test(text),
247
+ };
248
+ }
249
+
250
+ function buildBlocker(code, message, extra = {}) {
251
+ return { code, message, ...extra };
252
+ }
253
+
254
+ function evaluateOperationalIntegrity(options = {}) {
255
+ const repoRoot = options.repoPath ? resolveRepoRoot(options.repoPath) : resolveRepoRoot(process.cwd());
256
+ const baseBranch = String(options.baseBranch || DEFAULT_BASE_BRANCH).trim() || DEFAULT_BASE_BRANCH;
257
+ const currentBranch = String(options.currentBranch || (repoRoot ? getCurrentBranch(repoRoot) : '')).trim() || null;
258
+ const baseRef = repoRoot ? resolveBaseRef(repoRoot, baseBranch, { fetchIfMissing: options.fetchBase === true }) : null;
259
+ const changedFiles = Array.isArray(options.changedFiles)
260
+ ? options.changedFiles.map((filePath) => normalizePosix(filePath)).filter(Boolean)
261
+ : (repoRoot ? listChangedFilesAgainstBase(repoRoot, baseBranch, { fetchIfMissing: options.fetchBase === true }) : []);
262
+ const releaseSensitiveGlobs = sanitizeGlobList(options.releaseSensitiveGlobs || DEFAULT_RELEASE_SENSITIVE_GLOBS);
263
+ const releaseSensitiveFiles = findReleaseSensitiveFiles(changedFiles, releaseSensitiveGlobs);
264
+ const packageVersion = options.packageVersion !== undefined
265
+ ? options.packageVersion
266
+ : (repoRoot ? readPackageVersion(repoRoot, 'HEAD') : null);
267
+ const baseVersion = options.baseVersion !== undefined
268
+ ? options.baseVersion
269
+ : (repoRoot && baseRef ? readPackageVersion(repoRoot, baseRef) : null);
270
+ const versionComparison = packageVersion && baseVersion ? compareSemver(packageVersion, baseVersion) : null;
271
+ const headSha = options.headSha || (repoRoot ? getHeadSha(repoRoot) : null);
272
+ const headOnBase = options.headOnBase !== undefined
273
+ ? options.headOnBase
274
+ : Boolean(repoRoot && baseRef && headSha && isHeadReachableFrom(repoRoot, baseRef, headSha));
275
+ const branchGovernance = options.branchGovernance && typeof options.branchGovernance === 'object'
276
+ ? options.branchGovernance
277
+ : null;
278
+ const openPr = options.openPr !== undefined
279
+ ? options.openPr
280
+ : findOpenPrForBranch({ branchName: currentBranch, runner: options.ghRunner || runGh });
281
+ const commandInfo = classifyCommand(options.command || '');
282
+ const blockers = [];
283
+
284
+ const requiresGovernance = commandInfo.isPrCreate || commandInfo.isPrMerge || commandInfo.isPublish || commandInfo.isReleaseCreate || commandInfo.isTagCreate;
285
+ const isPublishLike = commandInfo.isPublish || commandInfo.isReleaseCreate || commandInfo.isTagCreate;
286
+
287
+ if (requiresGovernance && !branchGovernance) {
288
+ blockers.push(buildBlocker(
289
+ 'missing_branch_governance',
290
+ 'PR, merge, release, and publish actions require explicit branch governance.'
291
+ ));
292
+ }
293
+
294
+ if (branchGovernance && branchGovernance.localOnly === true && requiresGovernance) {
295
+ blockers.push(buildBlocker(
296
+ 'local_only_branch',
297
+ 'This task is marked local-only. PR, merge, release, and publish actions are blocked.'
298
+ ));
299
+ }
300
+
301
+ if (commandInfo.isPrMerge && /--admin\b/i.test(commandInfo.text)) {
302
+ blockers.push(buildBlocker(
303
+ 'admin_merge_bypass_forbidden',
304
+ 'Admin merge bypass is blocked. Use the normal protected-branch flow or merge queue.'
305
+ ));
306
+ }
307
+
308
+ if (commandInfo.isPrMerge && branchGovernance && !branchGovernance.prNumber && !branchGovernance.prUrl) {
309
+ blockers.push(buildBlocker(
310
+ 'merge_requires_pr_context',
311
+ 'Merging requires explicit PR context (prNumber or prUrl) in branch governance.'
312
+ ));
313
+ }
314
+
315
+ if (isPublishLike) {
316
+ if (!branchGovernance || !branchGovernance.releaseVersion) {
317
+ blockers.push(buildBlocker(
318
+ 'missing_release_version',
319
+ 'Release and publish actions require an explicit releaseVersion in branch governance.'
320
+ ));
321
+ } else if (packageVersion && branchGovernance.releaseVersion !== packageVersion) {
322
+ blockers.push(buildBlocker(
323
+ 'release_version_mismatch',
324
+ `Branch governance expects release version ${branchGovernance.releaseVersion}, but package.json is ${packageVersion}.`
325
+ ));
326
+ }
327
+
328
+ if (currentBranch && currentBranch !== baseBranch) {
329
+ blockers.push(buildBlocker(
330
+ 'publish_requires_base_branch',
331
+ `Release and publish actions must run from ${baseBranch}, not ${currentBranch}.`
332
+ ));
333
+ }
334
+
335
+ if (!headOnBase) {
336
+ blockers.push(buildBlocker(
337
+ 'publish_requires_mainline_head',
338
+ `Current HEAD is not reachable from ${baseBranch}. Release and publish actions require a mainline commit.`
339
+ ));
340
+ }
341
+ }
342
+
343
+ if (options.requirePrForReleaseSensitive && releaseSensitiveFiles.length > 0 && currentBranch && currentBranch !== baseBranch && !openPr) {
344
+ blockers.push(buildBlocker(
345
+ 'release_sensitive_changes_require_pr',
346
+ `Release-sensitive changes on ${currentBranch} require an open pull request before continuing.`,
347
+ { releaseSensitiveFiles }
348
+ ));
349
+ }
350
+
351
+ if (options.requireVersionNotBehindBase && releaseSensitiveFiles.length > 0 && versionComparison !== null && versionComparison < 0) {
352
+ blockers.push(buildBlocker(
353
+ 'version_behind_base',
354
+ `package.json version ${packageVersion} is behind ${baseBranch} version ${baseVersion} while release-sensitive files changed.`,
355
+ { packageVersion, baseVersion }
356
+ ));
357
+ }
358
+
359
+ return {
360
+ ok: blockers.length === 0,
361
+ repoRoot,
362
+ baseBranch,
363
+ baseRef,
364
+ currentBranch,
365
+ headSha,
366
+ headOnBase,
367
+ changedFiles,
368
+ releaseSensitiveFiles,
369
+ packageVersion,
370
+ baseVersion,
371
+ versionComparison,
372
+ branchGovernance,
373
+ openPr,
374
+ blockers,
375
+ commandInfo,
376
+ };
377
+ }
378
+
379
+ function parseCliArgs(argv = process.argv.slice(2)) {
380
+ const options = {
381
+ json: false,
382
+ ci: false,
383
+ fetchBase: false,
384
+ requirePrForReleaseSensitive: false,
385
+ requireVersionNotBehindBase: false,
386
+ repoPath: process.cwd(),
387
+ baseBranch: DEFAULT_BASE_BRANCH,
388
+ };
389
+
390
+ for (let i = 0; i < argv.length; i += 1) {
391
+ const arg = argv[i];
392
+ if (arg === '--json') {
393
+ options.json = true;
394
+ } else if (arg === '--ci') {
395
+ options.ci = true;
396
+ options.fetchBase = true;
397
+ options.requirePrForReleaseSensitive = true;
398
+ options.requireVersionNotBehindBase = true;
399
+ } else if (arg === '--fetch-base') {
400
+ options.fetchBase = true;
401
+ } else if (arg === '--require-pr-for-release-sensitive') {
402
+ options.requirePrForReleaseSensitive = true;
403
+ } else if (arg === '--require-version-not-behind-base') {
404
+ options.requireVersionNotBehindBase = true;
405
+ } else if (arg === '--repo-path' && argv[i + 1]) {
406
+ options.repoPath = argv[i + 1];
407
+ i += 1;
408
+ } else if (arg === '--base-branch' && argv[i + 1]) {
409
+ options.baseBranch = argv[i + 1];
410
+ i += 1;
411
+ }
412
+ }
413
+
414
+ return options;
415
+ }
416
+
417
+ function runCli(env = process.env, argv = process.argv.slice(2)) {
418
+ const args = parseCliArgs(argv);
419
+ const result = evaluateOperationalIntegrity({
420
+ repoPath: args.repoPath,
421
+ baseBranch: args.baseBranch || env.DEFAULT_BRANCH || DEFAULT_BASE_BRANCH,
422
+ currentBranch: env.GITHUB_REF_NAME || undefined,
423
+ requirePrForReleaseSensitive: args.requirePrForReleaseSensitive,
424
+ requireVersionNotBehindBase: args.requireVersionNotBehindBase,
425
+ fetchBase: args.fetchBase,
426
+ });
427
+
428
+ const lines = [];
429
+ lines.push(`Operational integrity: ${result.ok ? 'ok' : 'blocked'}`);
430
+ lines.push(`Base branch: ${result.baseBranch}`);
431
+ lines.push(`Current branch: ${result.currentBranch || 'unknown'}`);
432
+ if (result.packageVersion) {
433
+ lines.push(`package.json version: ${result.packageVersion}`);
434
+ }
435
+ if (result.baseVersion) {
436
+ lines.push(`${result.baseBranch} version: ${result.baseVersion}`);
437
+ }
438
+ if (result.releaseSensitiveFiles.length > 0) {
439
+ lines.push(`Release-sensitive files: ${result.releaseSensitiveFiles.join(', ')}`);
440
+ }
441
+ if (result.openPr && result.openPr.number) {
442
+ lines.push(`Open PR: #${result.openPr.number}${result.openPr.url ? ` ${result.openPr.url}` : ''}`);
443
+ }
444
+ for (const blocker of result.blockers) {
445
+ lines.push(`BLOCKER ${blocker.code}: ${blocker.message}`);
446
+ }
447
+
448
+ if (args.json) {
449
+ console.log(JSON.stringify(result, null, 2));
450
+ } else {
451
+ console.log(lines.join('\n'));
452
+ }
453
+
454
+ return result.ok ? 0 : 1;
455
+ }
456
+
457
+ if (require.main === module) {
458
+ process.exitCode = runCli();
459
+ }
460
+
461
+ module.exports = {
462
+ DEFAULT_BASE_BRANCH,
463
+ DEFAULT_RELEASE_SENSITIVE_GLOBS,
464
+ classifyCommand,
465
+ compareSemver,
466
+ evaluateOperationalIntegrity,
467
+ findOpenPrForBranch,
468
+ findReleaseSensitiveFiles,
469
+ getCurrentBranch,
470
+ isSafeBranchName,
471
+ listChangedFilesAgainstBase,
472
+ normalizeGlob,
473
+ normalizePosix,
474
+ parseSemver,
475
+ readPackageVersion,
476
+ resolveBaseRef,
477
+ resolveRepoRoot,
478
+ runCli,
479
+ sanitizeGlobList,
480
+ };
@@ -2,7 +2,7 @@
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const CLAUDE_MD_PATH = path.join(process.cwd(), 'CLAUDE.md');
5
- const THUMBGATE_DIR = path.join(process.cwd(), '.rlhf');
5
+ const THUMBGATE_DIR = path.join(process.cwd(), '.thumbgate');
6
6
  const RULES_PATH = path.join(THUMBGATE_DIR, 'prevention-rules.md');
7
7
  function optimize() {
8
8
  console.log('🚀 [Context Optimizer] Starting CLAUDE.md migration...');
@@ -98,7 +98,7 @@ I built "${PRODUCT.name}" — ${PRODUCT.tagline}
98
98
  Research and provide:
99
99
  1. **Competitor Analysis**: What similar tools exist? (LangSmith, Weights & Biases, custom ThumbGate pipelines, etc.) What do they charge? What gaps does my tool fill?
100
100
  2. **Target Buyer Personas**: Who would pay $19/mo or $149/yr for a Pro plan with curated ThumbGate configs? (AI engineers, dev tool builders, agent framework users)
101
- 3. **Distribution Channels**: Specific subreddits, Discord servers, Slack communities, newsletters, and forums where MCP/RLHF tool buyers hang out. Include URLs.
101
+ 3. **Distribution Channels**: Specific subreddits, Discord servers, Slack communities, newsletters, and forums where MCP and AI agent reliability tool buyers hang out. Include URLs.
102
102
  4. **SEO/GEO Keywords**: High-intent search terms people use when looking for this type of tool
103
103
  5. **Launch Strategy**: Specific steps to get first 10 paying customers this week
104
104
  6. **Pricing Validation**: Is $19/mo or $149/yr right for a Pro plan? What would similar tools charge?
@@ -136,17 +136,9 @@ async function postToReddit(parsed, dryRun) {
136
136
  const reddit = getPublisher('reddit');
137
137
  const postData = await reddit.publishToReddit({ subreddit, title, text: body });
138
138
 
139
- // Post the follow-up comment if we have one and got a post ID
139
+ // Reddit follow-up comments are manual-review only.
140
140
  if (comment && postData.name) {
141
- console.log('[post-everywhere] Posting follow-up comment...');
142
- const token = await reddit.getRedditToken(
143
- process.env.REDDIT_CLIENT_ID,
144
- process.env.REDDIT_CLIENT_SECRET,
145
- process.env.REDDIT_USERNAME,
146
- process.env.REDDIT_PASSWORD
147
- );
148
- const userAgent = process.env.REDDIT_USER_AGENT || `thumbgate/1.0 by ${process.env.REDDIT_USERNAME}`;
149
- await reddit.submitComment(token, userAgent, { parentId: postData.name, text: comment });
141
+ console.log('[post-everywhere] Reddit follow-up comment skipped; manual review required');
150
142
  }
151
143
 
152
144
  return postData;
@@ -216,8 +208,11 @@ async function postEverywhere(filePath, { platforms, dryRun } = {}) {
216
208
  }
217
209
  console.log('[post-everywhere] Quality gate: PASSED');
218
210
 
219
- // Determine which platforms to post to
220
- const targetPlatforms = platforms || (parsed.platform ? [parsed.platform] : Object.keys(DISPATCHERS));
211
+ // Determine which platforms to post to.
212
+ // Default excludes devto high-volume Dev.to posting is counterproductive (0 engagement on 427 posts).
213
+ // Use --platforms=devto explicitly for monthly cross-posts only.
214
+ const DEFAULT_PLATFORMS = ['reddit', 'x', 'linkedin'];
215
+ const targetPlatforms = platforms || (parsed.platform ? [parsed.platform] : DEFAULT_PLATFORMS);
221
216
 
222
217
  // Preserve original body/comment so each platform gets a fresh UTM tag
223
218
  const originalBody = parsed.body;
@@ -9,7 +9,7 @@
9
9
  * CLI usage:
10
10
  * node scripts/post-to-x.js "Your tweet text here"
11
11
  * node scripts/post-to-x.js --thread
12
- * node scripts/post-to-x.js --search "MCP memory gateway"
12
+ * node scripts/post-to-x.js --search "ThumbGate"
13
13
  * node scripts/post-to-x.js --reply <tweetId> "Reply text"
14
14
  * node scripts/post-to-x.js --dry-run "Preview this tweet"
15
15
  * node scripts/post-to-x.js --dry-run --thread
@@ -165,11 +165,9 @@ async function resolveBlockers(pr, runner = runGh) {
165
165
  }
166
166
 
167
167
  // 5. Ready to Merge
168
- if (pr.mergeStateStatus === 'CLEAN' || pr.mergeStateStatus === 'BLOCKED' /* admin bypass potential */) {
169
- if (pr.mergeable === 'MERGEABLE') {
170
- console.log('[PR Manager] SUCCESS: PR is ready for autonomous merge.');
171
- return { status: 'ready' };
172
- }
168
+ if (pr.mergeStateStatus === 'CLEAN' && pr.mergeable === 'MERGEABLE') {
169
+ console.log('[PR Manager] SUCCESS: PR is ready for protected autonomous merge.');
170
+ return { status: 'ready' };
173
171
  }
174
172
 
175
173
  return { status: 'pending', reason: 'unknown_state' };
@@ -179,14 +177,17 @@ async function resolveBlockers(pr, runner = runGh) {
179
177
  * Perform autonomous merge
180
178
  */
181
179
  function performMerge(prNumber, runner = runGh) {
182
- console.log(`[PR Manager] Initiating squash merge for PR #${prNumber}...`);
183
- const result = runner(['pr', 'merge', prNumber.toString(), '--squash', '--delete-branch', '--admin']);
180
+ const args = ['pr', 'merge', prNumber.toString(), '--squash', '--delete-branch', '--auto'];
181
+ console.log(`[PR Manager] Initiating protected squash merge for PR #${prNumber}...`);
182
+ const result = runner(args);
184
183
  if (result.status === 0) {
185
- console.log(`[PR Manager] Merged PR #${prNumber} successfully.`);
186
- return true;
184
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
185
+ const mode = /merge queue|queued|auto-merge/i.test(output) ? 'queued_or_auto' : 'merged';
186
+ console.log(`[PR Manager] Merge accepted for PR #${prNumber} (${mode}).`);
187
+ return { ok: true, mode, args };
187
188
  } else {
188
189
  console.error(`[PR Manager] Merge failed: ${formatGhError(result)}`);
189
- return false;
190
+ return { ok: false, mode: 'failed', args, error: formatGhError(result) };
190
191
  }
191
192
  }
192
193
 
@@ -202,7 +203,9 @@ async function managePrs(prNumber = '', runner = runGh) {
202
203
  for (const pr of prs) {
203
204
  const outcome = await resolveBlockers(pr, runner);
204
205
  if (outcome.status === 'ready') {
205
- outcome.merged = performMerge(pr.number, runner);
206
+ const mergeResult = performMerge(pr.number, runner);
207
+ outcome.mergeRequested = mergeResult.ok;
208
+ outcome.mergeMode = mergeResult.mode;
206
209
  }
207
210
 
208
211
  results.push({
@@ -6,16 +6,16 @@
6
6
  */
7
7
 
8
8
  const PROBLEM_TYPES = {
9
- RATE_LIMIT: 'urn:rlhf:error:rate-limit-exceeded',
10
- UNAUTHORIZED: 'urn:rlhf:error:unauthorized',
11
- FORBIDDEN: 'urn:rlhf:error:forbidden',
12
- NOT_FOUND: 'urn:rlhf:error:not-found',
13
- BAD_REQUEST: 'urn:rlhf:error:bad-request',
14
- INVALID_JSON: 'urn:rlhf:error:invalid-json',
15
- PAYMENT_REQUIRED: 'urn:rlhf:error:payment-required',
16
- INTERNAL: 'urn:rlhf:error:internal-server-error',
17
- WEBHOOK_INVALID: 'urn:rlhf:error:webhook-invalid-signature',
18
- SERVICE_UNAVAILABLE: 'urn:rlhf:error:service-unavailable',
9
+ RATE_LIMIT: 'urn:thumbgate:error:rate-limit-exceeded',
10
+ UNAUTHORIZED: 'urn:thumbgate:error:unauthorized',
11
+ FORBIDDEN: 'urn:thumbgate:error:forbidden',
12
+ NOT_FOUND: 'urn:thumbgate:error:not-found',
13
+ BAD_REQUEST: 'urn:thumbgate:error:bad-request',
14
+ INVALID_JSON: 'urn:thumbgate:error:invalid-json',
15
+ PAYMENT_REQUIRED: 'urn:thumbgate:error:payment-required',
16
+ INTERNAL: 'urn:thumbgate:error:internal-server-error',
17
+ WEBHOOK_INVALID: 'urn:thumbgate:error:webhook-invalid-signature',
18
+ SERVICE_UNAVAILABLE: 'urn:thumbgate:error:service-unavailable',
19
19
  };
20
20
 
21
21
  /**
@@ -183,6 +183,8 @@ function routePrivacy(params = {}) {
183
183
  'capture_feedback',
184
184
  'export_dpo_pairs',
185
185
  'export_databricks_bundle',
186
+ 'set_task_scope',
187
+ 'approve_protected_action',
186
188
  'track_action',
187
189
  'verify_claim',
188
190
  'register_claim_gate',
@@ -97,6 +97,7 @@ const KNOWN_GATED_TOOLS = new Set([
97
97
  'Bash', 'Edit', 'Write', 'Read', 'Glob', 'Grep',
98
98
  'capture_feedback', 'recall', 'search_lessons', 'prevention_rules',
99
99
  'feedback_stats', 'construct_context_pack', 'evaluate_context_pack',
100
+ 'set_task_scope', 'get_scope_state', 'approve_protected_action',
100
101
  ]);
101
102
 
102
103
  const SHADOW_LOG_FILE = 'shadow-actions.jsonl';