thumbgate 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/README.md +4 -4
- package/.claude-plugin/marketplace.json +4 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +115 -312
- package/adapters/README.md +2 -2
- package/adapters/amp/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
- package/adapters/chatgpt/openapi.yaml +2 -2
- package/adapters/claude/.mcp.json +3 -3
- package/adapters/codex/config.toml +4 -4
- package/adapters/gemini/function-declarations.json +1 -1
- package/adapters/mcp/server-stdio.js +66 -6
- package/adapters/opencode/opencode.json +4 -2
- package/bin/cli.js +188 -39
- package/config/e2e-critical-flows.json +4 -0
- package/config/gates/default.json +74 -2
- package/config/github-about.json +1 -1
- package/config/mcp-allowlists.json +33 -6
- package/config/skill-packs/react-testing.json +1 -1
- package/config/tessl-tiles.json +3 -3
- package/openapi/openapi.yaml +2 -2
- package/package.json +23 -9
- package/plugins/amp-skill/INSTALL.md +3 -2
- package/plugins/amp-skill/SKILL.md +1 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +5 -3
- package/plugins/claude-codex-bridge/README.md +1 -1
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +1 -1
- package/plugins/claude-skill/INSTALL.md +4 -3
- package/plugins/claude-skill/SKILL.md +1 -1
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +5 -3
- package/plugins/codex-profile/INSTALL.md +2 -2
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +5 -5
- package/plugins/cursor-marketplace/mcp.json +4 -2
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +1 -1
- package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
- package/plugins/gemini-extension/INSTALL.md +4 -4
- package/plugins/opencode-profile/INSTALL.md +5 -5
- package/public/dashboard.html +15 -8
- package/public/index.html +134 -375
- package/public/js/buyer-intent.js +252 -0
- package/public/pro.html +1085 -0
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/adk-consolidator.js +17 -5
- package/scripts/agent-readiness.js +3 -1
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/auto-promote-gates.js +8 -0
- package/scripts/auto-wire-hooks.js +105 -21
- package/scripts/billing.js +111 -7
- package/scripts/build-metadata.js +14 -0
- package/scripts/check-congruence.js +1 -1
- package/scripts/context-engine.js +2 -1
- package/scripts/daemon-manager.js +2 -2
- package/scripts/dashboard.js +2 -2
- package/scripts/data-governance.js +1 -1
- package/scripts/deploy-gcp.sh +1 -1
- package/scripts/deploy-policy.js +22 -4
- package/scripts/dispatch-brief.js +1 -1
- package/scripts/ensure-repo-bootstrap.js +1 -1
- package/scripts/feedback-attribution.js +22 -10
- package/scripts/feedback-fallback.js +3 -2
- package/scripts/feedback-inbox-read.js +1 -1
- package/scripts/feedback-loop.js +41 -3
- package/scripts/feedback-paths.js +8 -8
- package/scripts/feedback-schema.js +1 -1
- package/scripts/feedback-to-memory.js +2 -2
- package/scripts/filesystem-search.js +2 -2
- package/scripts/gates-engine.js +765 -34
- package/scripts/generate-paperbanana-diagrams.sh +3 -3
- package/scripts/github-about.js +1 -1
- package/scripts/gtm-revenue-loop.js +20 -1
- package/scripts/hook-runtime.js +89 -0
- package/scripts/hook-stop-self-score.sh +3 -3
- package/scripts/hook-thumbgate-cache-updater.js +98 -37
- package/scripts/hosted-config.js +12 -10
- package/scripts/hybrid-feedback-context.js +54 -13
- package/scripts/install-mcp.js +14 -1
- package/scripts/intent-router.js +1 -1
- package/scripts/internal-agent-bootstrap.js +1 -1
- package/scripts/lesson-inference.js +6 -1
- package/scripts/license.js +54 -16
- package/scripts/mcp-config.js +69 -7
- package/scripts/memory-migration.js +1 -1
- package/scripts/money-watcher.js +166 -16
- package/scripts/operational-integrity.js +480 -0
- package/scripts/optimize-context.js +1 -1
- package/scripts/perplexity-marketing.js +1 -1
- package/scripts/post-everywhere.js +7 -12
- package/scripts/post-to-x.js +1 -1
- package/scripts/pr-manager.js +14 -11
- package/scripts/problem-detail.js +10 -10
- package/scripts/profile-router.js +2 -0
- package/scripts/prompt-dlp.js +1 -0
- package/scripts/prove-adapters.js +6 -6
- package/scripts/prove-automation.js +1 -1
- package/scripts/prove-autoresearch.js +1 -1
- package/scripts/prove-claim-verification.js +3 -3
- package/scripts/prove-data-pipeline.js +5 -5
- package/scripts/prove-data-quality.js +1 -1
- package/scripts/prove-evolution.js +7 -7
- package/scripts/prove-harnesses.js +2 -2
- package/scripts/prove-lancedb.js +2 -2
- package/scripts/prove-local-intelligence.js +1 -1
- package/scripts/prove-loop-closure.js +1 -1
- package/scripts/prove-predictive-insights.js +2 -2
- package/scripts/prove-runtime.js +6 -6
- package/scripts/prove-seo-gsd.js +1 -1
- package/scripts/prove-settings.js +4 -4
- package/scripts/prove-subway-upgrades.js +1 -1
- package/scripts/prove-tessl.js +2 -2
- package/scripts/prove-xmemory.js +2 -2
- package/scripts/publish-decision.js +10 -0
- package/scripts/published-cli.js +34 -0
- package/scripts/rate-limiter.js +2 -2
- package/scripts/reddit-monitor-cron.sh +2 -2
- package/scripts/reminder-engine.js +1 -1
- package/scripts/schedule-manager.js +3 -3
- package/scripts/self-healing-check.js +1 -1
- package/scripts/shieldcortex-memory-firewall-runner.mjs +1 -1
- package/scripts/skill-quality-tracker.js +1 -1
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
- package/scripts/social-analytics/engagement-audit.js +202 -0
- package/scripts/social-analytics/generate-instagram-card.js +1 -1
- package/scripts/social-analytics/instagram-thumbgate-post.js +5 -1
- package/scripts/social-analytics/install-growth-automation.js +114 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +8 -2
- package/scripts/social-analytics/publish-thumbgate-launch.js +1 -1
- package/scripts/social-analytics/publishers/reddit.js +7 -12
- package/scripts/social-analytics/publishers/zernio.js +19 -0
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
- package/scripts/social-analytics/sync-launch-assets.js +185 -0
- package/scripts/social-pipeline.js +2 -2
- package/scripts/social-post-hourly.js +185 -0
- package/scripts/social-quality-gate.js +119 -3
- package/scripts/social-reply-monitor.js +150 -34
- package/scripts/statusline-cache-path.js +27 -0
- package/scripts/statusline-meta.js +22 -0
- package/scripts/statusline.sh +24 -32
- package/scripts/sync-version.js +24 -12
- package/scripts/telemetry-analytics.js +4 -4
- package/scripts/tessl-export.js +1 -1
- package/scripts/test-coverage.js +20 -13
- package/scripts/thumbgate-search.js +2 -2
- package/scripts/tool-registry.js +98 -1
- package/scripts/train_from_feedback.py +1 -1
- package/scripts/user-profile.js +4 -4
- package/scripts/validate-feedback.js +1 -1
- package/scripts/vector-store.js +1 -1
- package/scripts/verification-loop.js +1 -1
- package/scripts/verify-run.js +1 -1
- package/scripts/weekly-auto-post.js +1 -1
- package/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
- package/src/api/server.js +291 -41
- package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
- package/scripts/social-analytics/db/social-analytics.db +0 -0
|
@@ -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(), '.
|
|
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
|
|
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
|
-
//
|
|
139
|
+
// Reddit follow-up comments are manual-review only.
|
|
140
140
|
if (comment && postData.name) {
|
|
141
|
-
console.log('[post-everywhere]
|
|
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
|
-
|
|
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;
|
package/scripts/post-to-x.js
CHANGED
|
@@ -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 "
|
|
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
|
package/scripts/pr-manager.js
CHANGED
|
@@ -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'
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
183
|
-
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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:
|
|
10
|
-
UNAUTHORIZED: 'urn:
|
|
11
|
-
FORBIDDEN: 'urn:
|
|
12
|
-
NOT_FOUND: 'urn:
|
|
13
|
-
BAD_REQUEST: 'urn:
|
|
14
|
-
INVALID_JSON: 'urn:
|
|
15
|
-
PAYMENT_REQUIRED: 'urn:
|
|
16
|
-
INTERNAL: 'urn:
|
|
17
|
-
WEBHOOK_INVALID: 'urn:
|
|
18
|
-
SERVICE_UNAVAILABLE: 'urn:
|
|
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
|
/**
|
package/scripts/prompt-dlp.js
CHANGED
|
@@ -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';
|