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.
- package/.claude-plugin/README.md +2 -2
- 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 +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +4 -4
- package/adapters/mcp/server-stdio.js +61 -1
- package/adapters/opencode/opencode.json +4 -2
- package/bin/cli.js +156 -8
- package/bin/memory.sh +3 -3
- 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 +27 -0
- package/package.json +22 -5
- package/plugins/amp-skill/INSTALL.md +1 -0
- 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 +4 -2
- package/plugins/claude-skill/INSTALL.md +1 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +4 -2
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +3 -3
- package/plugins/cursor-marketplace/mcp.json +3 -1
- package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
- package/plugins/gemini-extension/INSTALL.md +3 -3
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/dashboard.html +15 -8
- package/public/index.html +125 -185
- 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 +14 -2
- package/scripts/agent-readiness.js +3 -1
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/auto-promote-gates.js +2 -0
- package/scripts/auto-wire-hooks.js +105 -17
- package/scripts/behavioral-extraction.js +2 -6
- package/scripts/billing.js +107 -3
- package/scripts/budget-guard.js +2 -2
- package/scripts/build-metadata.js +14 -0
- package/scripts/context-engine.js +1 -0
- package/scripts/deploy-policy.js +3 -17
- package/scripts/dpo-optimizer.js +3 -6
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/export-dpo-pairs.js +2 -3
- package/scripts/export-kto-pairs.js +3 -4
- package/scripts/export-training.js +8 -6
- package/scripts/feedback-attribution.js +23 -11
- package/scripts/feedback-loop.js +40 -2
- package/scripts/feedback-to-rules.js +2 -1
- package/scripts/filesystem-search.js +3 -2
- package/scripts/gates-engine.js +760 -29
- package/scripts/generate-pretool-hook.sh +0 -0
- package/scripts/gtm-revenue-loop.js +20 -1
- package/scripts/hook-auto-capture.sh +8 -3
- package/scripts/hook-runtime.js +89 -0
- package/scripts/hook-stop-self-score.sh +3 -3
- package/scripts/hook-thumbgate-cache-updater.js +99 -38
- package/scripts/hosted-config.js +4 -16
- package/scripts/hybrid-feedback-context.js +54 -14
- package/scripts/install-mcp.js +13 -0
- package/scripts/intent-router.js +2 -2
- package/scripts/license.js +52 -14
- package/scripts/local-model-profile.js +3 -2
- package/scripts/mcp-config.js +68 -6
- package/scripts/meta-policy.js +4 -8
- package/scripts/money-watcher.js +166 -16
- package/scripts/obsidian-export.js +1 -0
- package/scripts/operational-integrity.js +480 -0
- package/scripts/post-everywhere.js +7 -12
- package/scripts/pr-manager.js +14 -11
- package/scripts/profile-router.js +2 -0
- package/scripts/prompt-dlp.js +1 -0
- package/scripts/publish-decision.js +10 -0
- package/scripts/published-cli.js +34 -0
- package/scripts/risk-scorer.js +3 -2
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/skill-quality-tracker.js +3 -5
- 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/instagram-thumbgate-post.js +45 -7
- package/scripts/social-analytics/install-growth-automation.js +114 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/poll-all.js +3 -18
- package/scripts/social-analytics/pollers/zernio.js +3 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +22 -3
- package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
- package/scripts/social-analytics/publishers/reddit.js +7 -12
- package/scripts/social-analytics/publishers/zernio.js +210 -22
- 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-post-hourly.js +185 -0
- package/scripts/social-quality-gate.js +119 -3
- package/scripts/social-reply-monitor.js +148 -32
- 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 +11 -3
- package/scripts/test-coverage.js +20 -13
- package/scripts/tool-registry.js +97 -0
- package/scripts/train_from_feedback.py +32 -9
- package/scripts/validate-feedback.js +3 -2
- package/scripts/vector-store.js +2 -3
- package/scripts/verify-obsidian-setup.sh +3 -3
- package/src/api/server.js +281 -33
|
Binary file
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const { GoogleGenAI } = require('@google/genai');
|
|
16
15
|
|
|
17
16
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
18
17
|
const { getFeedbackPaths, readJSONL } = require('./feedback-loop');
|
|
@@ -91,7 +90,20 @@ async function consolidateMemory() {
|
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
let ai = null;
|
|
94
|
+
if (!useFakeConsolidation) {
|
|
95
|
+
let GoogleGenAI;
|
|
96
|
+
try {
|
|
97
|
+
({ GoogleGenAI } = require('@google/genai'));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
100
|
+
console.warn('[ADK Consolidator] @google/genai is not installed. Skipping active consolidation.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
ai = new GoogleGenAI({ apiKey });
|
|
106
|
+
}
|
|
95
107
|
const paths = getFeedbackPaths();
|
|
96
108
|
const state = loadState();
|
|
97
109
|
|
|
@@ -20,6 +20,8 @@ const WRITE_CAPABLE_TOOLS = new Set([
|
|
|
20
20
|
'evaluate_context_pack',
|
|
21
21
|
'generate_skill',
|
|
22
22
|
'satisfy_gate',
|
|
23
|
+
'set_task_scope',
|
|
24
|
+
'approve_protected_action',
|
|
23
25
|
'track_action',
|
|
24
26
|
'register_claim_gate',
|
|
25
27
|
]);
|
|
@@ -29,7 +31,7 @@ const BOOTSTRAP_FILES = [
|
|
|
29
31
|
{ id: 'claude', path: 'CLAUDE.md', required: true },
|
|
30
32
|
{ id: 'gemini', path: 'GEMINI.md', required: true },
|
|
31
33
|
{ id: 'mcp', path: '.mcp.json', required: true },
|
|
32
|
-
{ id: '
|
|
34
|
+
{ id: 'thumbgateConfig', path: '.thumbgate/config.json', required: false },
|
|
33
35
|
];
|
|
34
36
|
|
|
35
37
|
const MCP_PROFILE_TIERS = {
|
|
@@ -84,10 +84,10 @@ function getCredentialAudit({ periodHours = 24 } = {}) {
|
|
|
84
84
|
|
|
85
85
|
// MCP profile tool allowlists (loaded from config or defaults)
|
|
86
86
|
const PROFILE_ALLOWLISTS = {
|
|
87
|
-
essential: new Set(['capture_feedback', 'recall', 'search_lessons', 'search_thumbgate', 'prevention_rules', 'enforcement_matrix', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard']),
|
|
88
|
-
readonly: new Set(['recall', 'feedback_summary', 'search_lessons', 'verify_claim', 'gate_stats', 'search_thumbgate', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard']),
|
|
89
|
-
locked: new Set(['feedback_summary', 'search_lessons', 'diagnose_failure', 'list_intents', 'plan_intent', 'list_harnesses', 'verify_claim']),
|
|
90
|
-
commerce: new Set(['capture_feedback', 'recall', 'search_thumbgate', 'commerce_recall', 'track_action', 'verify_claim', 'feedback_stats']),
|
|
87
|
+
essential: new Set(['capture_feedback', 'recall', 'search_lessons', 'search_thumbgate', 'prevention_rules', 'enforcement_matrix', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity']),
|
|
88
|
+
readonly: new Set(['recall', 'feedback_summary', 'search_lessons', 'verify_claim', 'gate_stats', 'search_thumbgate', 'feedback_stats', 'estimate_uncertainty', 'org_dashboard', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity']),
|
|
89
|
+
locked: new Set(['feedback_summary', 'search_lessons', 'diagnose_failure', 'list_intents', 'plan_intent', 'list_harnesses', 'verify_claim', 'get_scope_state', 'get_branch_governance', 'check_operational_integrity']),
|
|
90
|
+
commerce: new Set(['capture_feedback', 'recall', 'search_thumbgate', 'commerce_recall', 'track_action', 'verify_claim', 'feedback_stats', 'set_task_scope', 'get_scope_state', 'set_branch_governance', 'get_branch_governance', 'approve_protected_action', 'check_operational_integrity']),
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
6
7
|
|
|
7
8
|
const MAX_AUTO_GATES = 10;
|
|
8
9
|
const WARN_THRESHOLD = 3; // 3+ repeated failures surface a warning gate
|
|
@@ -20,6 +21,7 @@ function getFeedbackLogPath() {
|
|
|
20
21
|
if (fs.existsSync(localFallback)) return localFallback;
|
|
21
22
|
if (fs.existsSync(localClaude)) return localClaude;
|
|
22
23
|
return localFallback; // default even if doesn't exist
|
|
24
|
+
return path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
function getAutoGatesPath() {
|
|
@@ -15,35 +15,31 @@
|
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const {
|
|
19
|
+
cacheUpdateHookCommand,
|
|
20
|
+
preToolHookCommand,
|
|
21
|
+
sessionStartHookCommand,
|
|
22
|
+
statuslineCommand,
|
|
23
|
+
userPromptHookCommand,
|
|
24
|
+
} = require('./hook-runtime');
|
|
20
25
|
|
|
21
26
|
function getHome() {
|
|
22
27
|
return process.env.HOME || process.env.USERPROFILE || '';
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// --- Hook definitions ---
|
|
26
|
-
|
|
27
|
-
function preToolHookCommand() {
|
|
28
|
-
return 'bash scripts/generate-pretool-hook.sh';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function userPromptHookCommand() {
|
|
32
|
-
return 'bash scripts/hook-auto-capture.sh';
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function sessionStartHookCommand() {
|
|
36
|
-
return 'bash scripts/thumbgate_session_start.sh';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
31
|
const CLAUDE_HOOKS = {
|
|
40
32
|
PreToolUse: {
|
|
41
|
-
matcher: 'Bash',
|
|
33
|
+
matcher: 'Bash|Edit|Write|MultiEdit',
|
|
42
34
|
hooks: [{ type: 'command', command: preToolHookCommand() }],
|
|
43
35
|
},
|
|
44
36
|
UserPromptSubmit: {
|
|
45
37
|
hooks: [{ type: 'command', command: userPromptHookCommand() }],
|
|
46
38
|
},
|
|
39
|
+
PostToolUse: {
|
|
40
|
+
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
41
|
+
hooks: [{ type: 'command', command: cacheUpdateHookCommand() }],
|
|
42
|
+
},
|
|
47
43
|
SessionStart: {
|
|
48
44
|
hooks: [{ type: 'command', command: sessionStartHookCommand() }],
|
|
49
45
|
},
|
|
@@ -74,6 +70,10 @@ function claudeSettingsPath() {
|
|
|
74
70
|
return path.join(getHome(), '.claude', 'settings.local.json');
|
|
75
71
|
}
|
|
76
72
|
|
|
73
|
+
function claudeSharedSettingsPath() {
|
|
74
|
+
return path.join(getHome(), '.claude', 'settings.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
77
|
function loadJsonFile(filePath) {
|
|
78
78
|
if (!fs.existsSync(filePath)) return null;
|
|
79
79
|
try {
|
|
@@ -92,17 +92,67 @@ function hookAlreadyPresent(hookArray, command) {
|
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function pruneLegacyHookEntries(hookArray, expectedCommand, legacyPattern) {
|
|
96
|
+
if (!Array.isArray(hookArray)) {
|
|
97
|
+
return { hooks: [], removed: false };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let removed = false;
|
|
101
|
+
const hooks = hookArray.filter((entry) => {
|
|
102
|
+
const entryHooks = Array.isArray(entry && entry.hooks) ? entry.hooks : [];
|
|
103
|
+
const shouldRemove = entryHooks.some((hook) => {
|
|
104
|
+
const command = hook && typeof hook.command === 'string' ? hook.command : '';
|
|
105
|
+
return command !== expectedCommand && legacyPattern.test(command);
|
|
106
|
+
});
|
|
107
|
+
if (shouldRemove) {
|
|
108
|
+
removed = true;
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return { hooks, removed };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function syncClaudeStatusLine(settingsPath, desiredStatusLine, dryRun) {
|
|
118
|
+
const settings = loadJsonFile(settingsPath) || {};
|
|
119
|
+
if (settings.statusLine && settings.statusLine.command === desiredStatusLine) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
settings.statusLine = { type: 'command', command: desiredStatusLine };
|
|
124
|
+
if (!dryRun) {
|
|
125
|
+
const dir = path.dirname(settingsPath);
|
|
126
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
127
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
95
132
|
function wireClaudeHooks(options) {
|
|
96
133
|
const settingsPath = options.settingsPath || claudeSettingsPath();
|
|
134
|
+
const sharedSettingsPath = options.sharedSettingsPath || claudeSharedSettingsPath();
|
|
97
135
|
const dryRun = options.dryRun || false;
|
|
136
|
+
const desiredStatusLine = statuslineCommand();
|
|
98
137
|
|
|
99
138
|
let settings = loadJsonFile(settingsPath) || {};
|
|
100
139
|
settings.hooks = settings.hooks || {};
|
|
101
140
|
|
|
102
141
|
const added = [];
|
|
142
|
+
const legacyPatterns = {
|
|
143
|
+
PreToolUse: /(generate-pretool-hook\.sh|\bgate-check\b)/,
|
|
144
|
+
UserPromptSubmit: /(hook-auto-capture\.sh|hook-auto-capture\b)/,
|
|
145
|
+
PostToolUse: /(hook-thumbgate-cache-updater|cache-update\b)/,
|
|
146
|
+
SessionStart: /(thumbgate_session_start\.sh|session-start\b)/,
|
|
147
|
+
};
|
|
103
148
|
|
|
104
149
|
for (const [lifecycle, hookDef] of Object.entries(CLAUDE_HOOKS)) {
|
|
105
150
|
const hookCommand = hookDef.hooks[0].command;
|
|
151
|
+
const pruned = pruneLegacyHookEntries(settings.hooks[lifecycle], hookCommand, legacyPatterns[lifecycle]);
|
|
152
|
+
settings.hooks[lifecycle] = pruned.hooks;
|
|
153
|
+
if (pruned.removed) {
|
|
154
|
+
added.push({ lifecycle, command: `${hookCommand} (replaced legacy ThumbGate hook)` });
|
|
155
|
+
}
|
|
106
156
|
|
|
107
157
|
if (hookAlreadyPresent(settings.hooks[lifecycle], hookCommand)) {
|
|
108
158
|
continue;
|
|
@@ -118,15 +168,41 @@ function wireClaudeHooks(options) {
|
|
|
118
168
|
}
|
|
119
169
|
|
|
120
170
|
if (added.length === 0) {
|
|
121
|
-
|
|
171
|
+
if (!settings.statusLine || settings.statusLine.command !== desiredStatusLine) {
|
|
172
|
+
if (!dryRun) {
|
|
173
|
+
const dir = path.dirname(settingsPath);
|
|
174
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
175
|
+
}
|
|
176
|
+
settings.statusLine = { type: 'command', command: desiredStatusLine };
|
|
177
|
+
if (!dryRun) {
|
|
178
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
179
|
+
}
|
|
180
|
+
const addedEntries = [{ lifecycle: 'statusLine', command: desiredStatusLine }];
|
|
181
|
+
if (syncClaudeStatusLine(sharedSettingsPath, desiredStatusLine, dryRun)) {
|
|
182
|
+
addedEntries.push({ lifecycle: 'statusLine', command: `${desiredStatusLine} (synced ~/.claude/settings.json)` });
|
|
183
|
+
}
|
|
184
|
+
return { changed: true, settingsPath, added: addedEntries };
|
|
185
|
+
}
|
|
186
|
+
const sharedStatusChanged = syncClaudeStatusLine(sharedSettingsPath, desiredStatusLine, dryRun);
|
|
187
|
+
return {
|
|
188
|
+
changed: sharedStatusChanged,
|
|
189
|
+
settingsPath,
|
|
190
|
+
added: sharedStatusChanged ? [{ lifecycle: 'statusLine', command: `${desiredStatusLine} (synced ~/.claude/settings.json)` }] : [],
|
|
191
|
+
};
|
|
122
192
|
}
|
|
123
193
|
|
|
194
|
+
settings.statusLine = { type: 'command', command: desiredStatusLine };
|
|
195
|
+
|
|
124
196
|
if (!dryRun) {
|
|
125
197
|
const dir = path.dirname(settingsPath);
|
|
126
198
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
127
199
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
128
200
|
}
|
|
129
201
|
|
|
202
|
+
if (syncClaudeStatusLine(sharedSettingsPath, desiredStatusLine, dryRun)) {
|
|
203
|
+
added.push({ lifecycle: 'statusLine', command: `${desiredStatusLine} (synced ~/.claude/settings.json)` });
|
|
204
|
+
}
|
|
205
|
+
|
|
130
206
|
return { changed: true, settingsPath, added };
|
|
131
207
|
}
|
|
132
208
|
|
|
@@ -147,6 +223,11 @@ function wireCodexHooks(options) {
|
|
|
147
223
|
const preToolCmd = preToolHookCommand();
|
|
148
224
|
const userPromptCmd = userPromptHookCommand();
|
|
149
225
|
|
|
226
|
+
const preToolPruned = pruneLegacyHookEntries(config.hooks.PreToolUse, preToolCmd, /(generate-pretool-hook\.sh|\bgate-check\b)/);
|
|
227
|
+
config.hooks.PreToolUse = preToolPruned.hooks;
|
|
228
|
+
const userPromptPruned = pruneLegacyHookEntries(config.hooks.UserPromptSubmit, userPromptCmd, /(hook-auto-capture\.sh|hook-auto-capture\b)/);
|
|
229
|
+
config.hooks.UserPromptSubmit = userPromptPruned.hooks;
|
|
230
|
+
|
|
150
231
|
if (!hookAlreadyPresent(config.hooks.PreToolUse, preToolCmd)) {
|
|
151
232
|
config.hooks.PreToolUse = config.hooks.PreToolUse || [];
|
|
152
233
|
config.hooks.PreToolUse.push({
|
|
@@ -194,6 +275,11 @@ function wireGeminiHooks(options) {
|
|
|
194
275
|
const preToolCmd = preToolHookCommand();
|
|
195
276
|
const userPromptCmd = userPromptHookCommand();
|
|
196
277
|
|
|
278
|
+
const preToolPruned = pruneLegacyHookEntries(settings.hooks.PreToolUse, preToolCmd, /(generate-pretool-hook\.sh|\bgate-check\b)/);
|
|
279
|
+
settings.hooks.PreToolUse = preToolPruned.hooks;
|
|
280
|
+
const userPromptPruned = pruneLegacyHookEntries(settings.hooks.UserPromptSubmit, userPromptCmd, /(hook-auto-capture\.sh|hook-auto-capture\b)/);
|
|
281
|
+
settings.hooks.UserPromptSubmit = userPromptPruned.hooks;
|
|
282
|
+
|
|
197
283
|
if (!hookAlreadyPresent(settings.hooks.PreToolUse, preToolCmd)) {
|
|
198
284
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
199
285
|
settings.hooks.PreToolUse.push({
|
|
@@ -282,8 +368,10 @@ module.exports = {
|
|
|
282
368
|
loadJsonFile,
|
|
283
369
|
parseFlags,
|
|
284
370
|
claudeSettingsPath,
|
|
371
|
+
claudeSharedSettingsPath,
|
|
285
372
|
codexConfigPath,
|
|
286
373
|
geminiSettingsPath,
|
|
374
|
+
syncClaudeStatusLine,
|
|
287
375
|
CLAUDE_HOOKS,
|
|
288
376
|
preToolHookCommand,
|
|
289
377
|
userPromptHookCommand,
|
|
@@ -8,13 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
const envDir = process.env.THUMBGATE_FEEDBACK_DIR;
|
|
14
|
-
const localFallback = path.join(process.cwd(), '.thumbgate');
|
|
15
|
-
const localClaude = path.join(process.cwd(), '.claude', 'memory', 'feedback');
|
|
16
|
-
const baseDir = envDir || (fs.existsSync(localFallback) ? localFallback : localClaude);
|
|
17
|
-
|
|
13
|
+
const baseDir = resolveFeedbackDir();
|
|
18
14
|
const FEEDBACK_LOG_PATH = path.join(baseDir, 'feedback-log.jsonl');
|
|
19
15
|
const TRAITS_PATH = path.join(baseDir, 'behavioral-traits.json');
|
|
20
16
|
|
package/scripts/billing.js
CHANGED
|
@@ -16,7 +16,6 @@ function withTimeout(promise, ms = STRIPE_TIMEOUT_MS) {
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
18
|
const crypto = require('crypto');
|
|
19
|
-
const Stripe = require('stripe');
|
|
20
19
|
const { createTraceId } = require('./hosted-config');
|
|
21
20
|
const {
|
|
22
21
|
getFeedbackPaths,
|
|
@@ -71,6 +70,9 @@ const CONFIG = {
|
|
|
71
70
|
get LOCAL_CHECKOUT_SESSIONS_PATH() {
|
|
72
71
|
return process.env._TEST_LOCAL_CHECKOUT_SESSIONS_PATH || path.join(getFeedbackPaths().FEEDBACK_DIR, 'local-checkout-sessions.json');
|
|
73
72
|
},
|
|
73
|
+
get NEWSLETTER_SUBSCRIBERS_PATH() {
|
|
74
|
+
return process.env._TEST_NEWSLETTER_SUBSCRIBERS_PATH || path.join(getFeedbackPaths().FEEDBACK_DIR, 'newsletter-subscribers.jsonl');
|
|
75
|
+
},
|
|
74
76
|
CREDIT_PACKS: {
|
|
75
77
|
'mistake-free-starter': {
|
|
76
78
|
id: 'mistake-free-starter',
|
|
@@ -89,11 +91,27 @@ function resolveLegacyBillingPath(fileName) {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
let _stripeClient = null;
|
|
94
|
+
let _stripeCtor = null;
|
|
95
|
+
|
|
96
|
+
function getStripeConstructor() {
|
|
97
|
+
if (_stripeCtor) return _stripeCtor;
|
|
98
|
+
try {
|
|
99
|
+
_stripeCtor = require('stripe');
|
|
100
|
+
return _stripeCtor;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
103
|
+
throw new Error('stripe package is not installed. Live billing features are unavailable.');
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
92
109
|
function getStripeClient() {
|
|
93
110
|
if (!_stripeClient) {
|
|
94
111
|
if (!CONFIG.STRIPE_SECRET_KEY) {
|
|
95
112
|
throw new Error('STRIPE_SECRET_KEY is missing. Stripe client cannot be initialized.');
|
|
96
113
|
}
|
|
114
|
+
const Stripe = getStripeConstructor();
|
|
97
115
|
_stripeClient = new Stripe(CONFIG.STRIPE_SECRET_KEY);
|
|
98
116
|
}
|
|
99
117
|
return _stripeClient;
|
|
@@ -297,6 +315,11 @@ function buildBillingSourceDiagnostics(feedbackDir) {
|
|
|
297
315
|
legacyPath: resolveLegacyBillingPath('local-checkout-sessions.json'),
|
|
298
316
|
mode: 'fallback',
|
|
299
317
|
});
|
|
318
|
+
const newsletterSubscribers = describeDataFile({
|
|
319
|
+
primaryPath: CONFIG.NEWSLETTER_SUBSCRIBERS_PATH,
|
|
320
|
+
legacyPath: resolveLegacyBillingPath('newsletter-subscribers.jsonl'),
|
|
321
|
+
mode: 'fallback',
|
|
322
|
+
});
|
|
300
323
|
const telemetry = getTelemetrySourceDiagnostics(feedbackDir);
|
|
301
324
|
const warnings = [
|
|
302
325
|
...telemetry.warnings,
|
|
@@ -338,7 +361,7 @@ function buildBillingSourceDiagnostics(feedbackDir) {
|
|
|
338
361
|
));
|
|
339
362
|
}
|
|
340
363
|
|
|
341
|
-
const mixedRoots = [keyStore, funnelLedger, revenueLedger, checkoutSessions, telemetry]
|
|
364
|
+
const mixedRoots = [keyStore, funnelLedger, revenueLedger, checkoutSessions, newsletterSubscribers, telemetry]
|
|
342
365
|
.some((descriptor) => descriptor.mixedRoots || descriptor.activeMode === 'legacy_fallback');
|
|
343
366
|
if (mixedRoots) {
|
|
344
367
|
warnings.push(buildSourceWarning(
|
|
@@ -357,6 +380,7 @@ function buildBillingSourceDiagnostics(feedbackDir) {
|
|
|
357
380
|
funnelLedger,
|
|
358
381
|
revenueLedger,
|
|
359
382
|
checkoutSessions,
|
|
383
|
+
newsletterSubscribers,
|
|
360
384
|
telemetry,
|
|
361
385
|
},
|
|
362
386
|
warnings,
|
|
@@ -622,6 +646,13 @@ function loadRevenueLedger() {
|
|
|
622
646
|
);
|
|
623
647
|
}
|
|
624
648
|
|
|
649
|
+
function loadNewsletterSubscribers() {
|
|
650
|
+
return loadJsonlRecords(
|
|
651
|
+
CONFIG.NEWSLETTER_SUBSCRIBERS_PATH,
|
|
652
|
+
resolveLegacyBillingPath('newsletter-subscribers.jsonl')
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
625
656
|
function resolveRevenueLedgerFilePath() {
|
|
626
657
|
const primary = CONFIG.REVENUE_LEDGER_PATH;
|
|
627
658
|
const legacy = resolveLegacyBillingPath('revenue-events.jsonl');
|
|
@@ -1331,6 +1362,11 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1331
1362
|
analyticsWindow,
|
|
1332
1363
|
(entry) => entry && entry.submittedAt
|
|
1333
1364
|
);
|
|
1365
|
+
const newsletterSubscribers = filterEntriesForWindow(
|
|
1366
|
+
loadNewsletterSubscribers(),
|
|
1367
|
+
analyticsWindow,
|
|
1368
|
+
(entry) => entry && entry.subscribedAt
|
|
1369
|
+
);
|
|
1334
1370
|
const funnel = getFunnelAnalytics({ ...analyticsWindow, extraRevenueEvents });
|
|
1335
1371
|
const acquisitionEvents = events.filter((entry) => entry && entry.stage === 'acquisition');
|
|
1336
1372
|
const paidEvents = events.filter((entry) => entry && entry.stage === 'paid');
|
|
@@ -1544,6 +1580,17 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1544
1580
|
let workflowSprintLeadLatestAt = null;
|
|
1545
1581
|
let workflowSprintLeadContactable = 0;
|
|
1546
1582
|
let qualifiedWorkflowSprintLeadCount = 0;
|
|
1583
|
+
const newsletterBySource = {};
|
|
1584
|
+
const newsletterByCampaign = {};
|
|
1585
|
+
const newsletterByCreator = {};
|
|
1586
|
+
const newsletterByCommunity = {};
|
|
1587
|
+
const newsletterByPostId = {};
|
|
1588
|
+
const newsletterByCommentId = {};
|
|
1589
|
+
const newsletterByCampaignVariant = {};
|
|
1590
|
+
const newsletterByOfferCode = {};
|
|
1591
|
+
const newsletterSubscriberKeys = new Set();
|
|
1592
|
+
let newsletterLatest = null;
|
|
1593
|
+
let newsletterLatestAt = null;
|
|
1547
1594
|
|
|
1548
1595
|
for (const entry of workflowSprintLeads) {
|
|
1549
1596
|
if (!entry || typeof entry !== 'object') continue;
|
|
@@ -1584,6 +1631,46 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1584
1631
|
}
|
|
1585
1632
|
}
|
|
1586
1633
|
|
|
1634
|
+
for (const entry of newsletterSubscribers) {
|
|
1635
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
1636
|
+
const attribution = extractAttribution({
|
|
1637
|
+
...sanitizeMetadata(entry.attribution || {}),
|
|
1638
|
+
...sanitizeMetadata(entry),
|
|
1639
|
+
});
|
|
1640
|
+
incrementCounter(newsletterBySource, resolveAttributionSource(attribution, entry.source || 'newsletter'));
|
|
1641
|
+
incrementCounter(newsletterByCampaign, resolveAttributionCampaign(attribution));
|
|
1642
|
+
incrementCounter(newsletterByCreator, attribution.creator);
|
|
1643
|
+
incrementCounter(newsletterByCommunity, attribution.community);
|
|
1644
|
+
incrementCounter(newsletterByPostId, attribution.postId);
|
|
1645
|
+
incrementCounter(newsletterByCommentId, attribution.commentId);
|
|
1646
|
+
incrementCounter(newsletterByCampaignVariant, attribution.campaignVariant);
|
|
1647
|
+
incrementCounter(newsletterByOfferCode, attribution.offerCode);
|
|
1648
|
+
|
|
1649
|
+
newsletterSubscriberKeys.add(
|
|
1650
|
+
pickFirstText(
|
|
1651
|
+
entry.email,
|
|
1652
|
+
entry.acquisitionId,
|
|
1653
|
+
entry.visitorId,
|
|
1654
|
+
entry.sessionId,
|
|
1655
|
+
entry.subscribedAt
|
|
1656
|
+
)
|
|
1657
|
+
);
|
|
1658
|
+
|
|
1659
|
+
if (!newsletterLatestAt || String(entry.subscribedAt || '') > newsletterLatestAt) {
|
|
1660
|
+
newsletterLatestAt = entry.subscribedAt || null;
|
|
1661
|
+
newsletterLatest = {
|
|
1662
|
+
email: entry.email || null,
|
|
1663
|
+
subscribedAt: entry.subscribedAt || null,
|
|
1664
|
+
source: resolveAttributionSource(attribution, entry.source || 'newsletter'),
|
|
1665
|
+
campaign: resolveAttributionCampaign(attribution),
|
|
1666
|
+
creator: attribution.creator || null,
|
|
1667
|
+
community: attribution.community || null,
|
|
1668
|
+
landingPath: pickFirstText(entry.landingPath, attribution.landingPath),
|
|
1669
|
+
referrerHost: entry.referrerHost || null,
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1587
1674
|
const unreconciledPaidEvents = paidEvents.filter((entry) => {
|
|
1588
1675
|
const eventKey = resolvePaidProviderEventKey(entry);
|
|
1589
1676
|
if (!eventKey) return true;
|
|
@@ -1603,6 +1690,7 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1603
1690
|
checkoutLookupFailures: telemetry.ctas ? telemetry.ctas.lookupFailures || 0 : 0,
|
|
1604
1691
|
buyerLossFeedback: telemetry.buyerLoss ? telemetry.buyerLoss.totalSignals || 0 : 0,
|
|
1605
1692
|
seoLandingViews: telemetry.seo ? telemetry.seo.landingViews || 0 : 0,
|
|
1693
|
+
newsletterSignups: newsletterSubscribers.length,
|
|
1606
1694
|
};
|
|
1607
1695
|
|
|
1608
1696
|
const operatorGeneratedAcquisition = {
|
|
@@ -1632,6 +1720,7 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1632
1720
|
tracksInvoices: false,
|
|
1633
1721
|
tracksAttribution: true,
|
|
1634
1722
|
tracksWorkflowSprintLeads: true,
|
|
1723
|
+
tracksNewsletterSubscribers: true,
|
|
1635
1724
|
providerCoverage: {
|
|
1636
1725
|
stripe: processorReconciledOrders > 0 ? 'booked_revenue+processor_reconciled' : 'booked_revenue',
|
|
1637
1726
|
githubMarketplace: 'webhook_or_configured_plan_prices',
|
|
@@ -1701,6 +1790,20 @@ function getBusinessAnalytics(options = {}) {
|
|
|
1701
1790
|
byCreator: qualifiedWorkflowSprintLeadByCreator,
|
|
1702
1791
|
},
|
|
1703
1792
|
},
|
|
1793
|
+
newsletter: {
|
|
1794
|
+
total: newsletterSubscribers.length,
|
|
1795
|
+
uniqueSubscribers: newsletterSubscriberKeys.size,
|
|
1796
|
+
bySource: newsletterBySource,
|
|
1797
|
+
byCampaign: newsletterByCampaign,
|
|
1798
|
+
byCreator: newsletterByCreator,
|
|
1799
|
+
byCommunity: newsletterByCommunity,
|
|
1800
|
+
byPostId: newsletterByPostId,
|
|
1801
|
+
byCommentId: newsletterByCommentId,
|
|
1802
|
+
byCampaignVariant: newsletterByCampaignVariant,
|
|
1803
|
+
byOfferCode: newsletterByOfferCode,
|
|
1804
|
+
latestSubscribedAt: newsletterLatestAt,
|
|
1805
|
+
latestSubscriber: newsletterLatest,
|
|
1806
|
+
},
|
|
1704
1807
|
attribution: {
|
|
1705
1808
|
acquisitionBySource: signupsBySource,
|
|
1706
1809
|
acquisitionByCampaign: signupsByCampaign,
|
|
@@ -1822,6 +1925,7 @@ function getBillingSummary(options = {}) {
|
|
|
1822
1925
|
signups: business.signups,
|
|
1823
1926
|
revenue: business.revenue,
|
|
1824
1927
|
pipeline: business.pipeline,
|
|
1928
|
+
newsletter: business.newsletter,
|
|
1825
1929
|
attribution: business.attribution,
|
|
1826
1930
|
trafficMetrics: business.trafficMetrics,
|
|
1827
1931
|
operatorGeneratedAcquisition: business.operatorGeneratedAcquisition,
|
|
@@ -2426,7 +2530,7 @@ function handleGithubWebhook(event) {
|
|
|
2426
2530
|
}
|
|
2427
2531
|
|
|
2428
2532
|
module.exports = {
|
|
2429
|
-
CONFIG, createCheckoutSession, getCheckoutSessionStatus, provisionApiKey, rotateApiKey, validateApiKey, recordUsage, disableCustomerKeys, handleWebhook, verifyWebhookSignature, verifyGithubWebhookSignature, handleGithubWebhook, loadKeyStore, appendFunnelEvent, appendRevenueEvent, loadFunnelLedger, loadRevenueLedger, loadResolvedRevenueEvents, getFunnelAnalytics, getBusinessAnalytics, getBillingSummary, getBillingSummaryLive, listStripeReconciledRevenueEvents, repairGithubMarketplaceRevenueLedger,
|
|
2533
|
+
CONFIG, createCheckoutSession, getCheckoutSessionStatus, provisionApiKey, rotateApiKey, validateApiKey, recordUsage, disableCustomerKeys, handleWebhook, verifyWebhookSignature, verifyGithubWebhookSignature, handleGithubWebhook, loadKeyStore, appendFunnelEvent, appendRevenueEvent, loadFunnelLedger, loadRevenueLedger, loadNewsletterSubscribers, loadResolvedRevenueEvents, getFunnelAnalytics, getBusinessAnalytics, getBillingSummary, getBillingSummaryLive, listStripeReconciledRevenueEvents, repairGithubMarketplaceRevenueLedger,
|
|
2430
2534
|
_buildCheckoutSessionPayload: buildCheckoutSessionPayload,
|
|
2431
2535
|
_resolveSubscriptionCheckoutSelection: resolveSubscriptionCheckoutSelection,
|
|
2432
2536
|
_API_KEYS_PATH: () => CONFIG.API_KEYS_PATH,
|
package/scripts/budget-guard.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
const FEEDBACK_DIR = process.env.THUMBGATE_FEEDBACK_DIR || path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
|
|
6
|
+
const FEEDBACK_DIR = resolveFeedbackDir();
|
|
7
7
|
const LEDGER_PATH = path.join(FEEDBACK_DIR, 'budget-ledger.json');
|
|
8
8
|
const LOCK_PATH = `${LEDGER_PATH}.lock`;
|
|
9
9
|
|
|
@@ -3,6 +3,8 @@ const path = require('path');
|
|
|
3
3
|
|
|
4
4
|
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
5
5
|
const DEFAULT_BUILD_METADATA_PATH = path.join(PROJECT_ROOT, 'config', 'build-metadata.json');
|
|
6
|
+
const BUILD_SHA_ENV_KEY = 'THUMBGATE_BUILD_SHA';
|
|
7
|
+
const BUILD_GENERATED_AT_ENV_KEY = 'THUMBGATE_BUILD_GENERATED_AT';
|
|
6
8
|
|
|
7
9
|
function normalizeNullableText(value) {
|
|
8
10
|
if (typeof value !== 'string') {
|
|
@@ -18,6 +20,16 @@ function resolveBuildMetadata({ env = process.env, filePath } = {}) {
|
|
|
18
20
|
normalizeNullableText(filePath) ||
|
|
19
21
|
normalizeNullableText(env.THUMBGATE_BUILD_METADATA_PATH) ||
|
|
20
22
|
DEFAULT_BUILD_METADATA_PATH;
|
|
23
|
+
const envBuildSha = normalizeNullableText(env[BUILD_SHA_ENV_KEY]);
|
|
24
|
+
const envGeneratedAt = normalizeNullableText(env[BUILD_GENERATED_AT_ENV_KEY]);
|
|
25
|
+
|
|
26
|
+
if (envBuildSha || envGeneratedAt) {
|
|
27
|
+
return {
|
|
28
|
+
path: resolvedPath,
|
|
29
|
+
buildSha: envBuildSha,
|
|
30
|
+
generatedAt: envGeneratedAt,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
21
33
|
|
|
22
34
|
try {
|
|
23
35
|
const parsed = JSON.parse(fs.readFileSync(resolvedPath, 'utf8'));
|
|
@@ -91,6 +103,8 @@ if (require.main === module) {
|
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
module.exports = {
|
|
106
|
+
BUILD_GENERATED_AT_ENV_KEY,
|
|
107
|
+
BUILD_SHA_ENV_KEY,
|
|
94
108
|
DEFAULT_BUILD_METADATA_PATH,
|
|
95
109
|
resolveBuildMetadata,
|
|
96
110
|
writeBuildMetadataFile,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* MCP tool calls and context window consumption.
|
|
11
11
|
*
|
|
12
12
|
* Ported from Subway_RN_Demo/scripts/context-engine.js for ThumbGate.
|
|
13
|
+
* Ported from Subway_RN_Demo/scripts/context-engine.js for thumbgate.
|
|
13
14
|
* PATH: PROJECT_ROOT = path.join(__dirname, '..') — 1 level up from scripts/
|
|
14
15
|
*/
|
|
15
16
|
|
package/scripts/deploy-policy.js
CHANGED
|
@@ -39,24 +39,10 @@ const PROFILE_DEFS = {
|
|
|
39
39
|
},
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const ENV_ALIASES = {
|
|
43
|
-
THUMBGATE_API_KEY: ['THUMBGATE_API_KEY', 'RLHF_API_KEY'],
|
|
44
|
-
THUMBGATE_API_KEY_ROTATED_AT: ['THUMBGATE_API_KEY_ROTATED_AT', 'RLHF_API_KEY_ROTATED_AT'],
|
|
45
|
-
THUMBGATE_PUBLIC_APP_ORIGIN: ['THUMBGATE_PUBLIC_APP_ORIGIN', 'RLHF_PUBLIC_APP_ORIGIN'],
|
|
46
|
-
THUMBGATE_BILLING_API_BASE_URL: [
|
|
47
|
-
'THUMBGATE_BILLING_API_BASE_URL',
|
|
48
|
-
'RLHF_BILLING_API_BASE_URL',
|
|
49
|
-
'THUMBGATE_CANONICAL_API_BASE_URL',
|
|
50
|
-
],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
42
|
function resolveEnvValue(name, env = process.env) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (value) {
|
|
58
|
-
return value;
|
|
59
|
-
}
|
|
43
|
+
const value = String(env[name] || '').trim();
|
|
44
|
+
if (value) {
|
|
45
|
+
return value;
|
|
60
46
|
}
|
|
61
47
|
|
|
62
48
|
if (name === 'THUMBGATE_PUBLIC_APP_ORIGIN') {
|
package/scripts/dpo-optimizer.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const os = require('os');
|
|
17
16
|
const { getEffectiveSetting } = require('./evolution-state');
|
|
17
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
18
18
|
|
|
19
19
|
const DPO_BETA = 0.1;
|
|
20
20
|
|
|
@@ -154,11 +154,8 @@ function applyDpoAdjustments(modelPath, pairs) {
|
|
|
154
154
|
*/
|
|
155
155
|
function run(opts) {
|
|
156
156
|
const options = opts || {};
|
|
157
|
-
const feedbackDir = options.feedbackDir ||
|
|
158
|
-
|
|
159
|
-
path.join(os.homedir(), '.claude', 'memory', 'feedback');
|
|
160
|
-
const modelPath = options.modelPath ||
|
|
161
|
-
path.join(process.cwd(), '.claude', 'memory', 'feedback', 'feedback_model.json');
|
|
157
|
+
const feedbackDir = options.feedbackDir || resolveFeedbackDir();
|
|
158
|
+
const modelPath = options.modelPath || path.join(feedbackDir, 'feedback_model.json');
|
|
162
159
|
|
|
163
160
|
const pairs = buildPreferencePairs(feedbackDir);
|
|
164
161
|
|