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
|
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');
|
|
@@ -22,7 +21,7 @@ const { trackEvent, shouldInjectReminder, injectReminder } = require('./reminder
|
|
|
22
21
|
const { validateApiKey } = require('./billing');
|
|
23
22
|
|
|
24
23
|
// Keep track of the last processed ID to avoid re-consolidating the exact same logs
|
|
25
|
-
const STATE_FILE = process.env.ADK_STATE_FILE || path.join(PROJECT_ROOT, '.
|
|
24
|
+
const STATE_FILE = process.env.ADK_STATE_FILE || path.join(PROJECT_ROOT, '.thumbgate', 'adk-state.json');
|
|
26
25
|
|
|
27
26
|
function ensureDir(dirPath) {
|
|
28
27
|
if (!fs.existsSync(dirPath)) {
|
|
@@ -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
|
|
|
@@ -193,7 +205,7 @@ Output ONLY valid JSON:
|
|
|
193
205
|
if (shouldInjectReminder('guardrail_spike')) {
|
|
194
206
|
const topRule = criticalInsights[0].rule;
|
|
195
207
|
const reminderTurns = injectReminder([], 'guardrail_spike', { rule: topRule });
|
|
196
|
-
const reminderPath = path.join(PROJECT_ROOT, '.
|
|
208
|
+
const reminderPath = path.join(PROJECT_ROOT, '.thumbgate', `reminder_${Date.now()}.json`);
|
|
197
209
|
fs.writeFileSync(reminderPath, JSON.stringify(reminderTurns[0], null, 2));
|
|
198
210
|
console.log(`[ADK Consolidator] Emitted system reminder: ${reminderPath}`);
|
|
199
211
|
}
|
|
@@ -202,7 +214,7 @@ Output ONLY valid JSON:
|
|
|
202
214
|
// Emit A2UI components (New Model)
|
|
203
215
|
result.consolidatedInsights.forEach(insight => {
|
|
204
216
|
const proposal = createRuleProposal(insight.pattern, insight.rule, insight.severity);
|
|
205
|
-
const a2uiPath = path.join(PROJECT_ROOT, '.
|
|
217
|
+
const a2uiPath = path.join(PROJECT_ROOT, '.thumbgate', `a2ui_proposal_${Date.now()}.json`);
|
|
206
218
|
fs.writeFileSync(a2uiPath, JSON.stringify(proposal, null, 2));
|
|
207
219
|
console.log(`[ADK Consolidator] Emitted A2UI Proposal: ${a2uiPath}`);
|
|
208
220
|
});
|
|
@@ -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', '
|
|
88
|
-
readonly: new Set(['recall', 'feedback_summary', 'search_lessons', 'verify_claim', 'gate_stats', '
|
|
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', '
|
|
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
|
/**
|
|
@@ -13,6 +13,14 @@ const WINDOW_DAYS = 30;
|
|
|
13
13
|
const NEG_SIGNALS = new Set(['negative', 'negative_strong', 'down', 'thumbs_down']);
|
|
14
14
|
|
|
15
15
|
function getFeedbackLogPath() {
|
|
16
|
+
if (process.env.THUMBGATE_FEEDBACK_DIR) {
|
|
17
|
+
return path.join(process.env.THUMBGATE_FEEDBACK_DIR, 'feedback-log.jsonl');
|
|
18
|
+
}
|
|
19
|
+
const localFallback = path.join(process.cwd(), '.thumbgate', 'feedback-log.jsonl');
|
|
20
|
+
const localClaude = path.join(process.cwd(), '.claude', 'memory', 'feedback', 'feedback-log.jsonl');
|
|
21
|
+
if (fs.existsSync(localFallback)) return localFallback;
|
|
22
|
+
if (fs.existsSync(localClaude)) return localClaude;
|
|
23
|
+
return localFallback; // default even if doesn't exist
|
|
16
24
|
return path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -15,39 +15,31 @@
|
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const path = require('path');
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
const {
|
|
19
|
+
cacheUpdateHookCommand,
|
|
20
|
+
preToolHookCommand,
|
|
21
|
+
sessionStartHookCommand,
|
|
22
|
+
statuslineCommand,
|
|
23
|
+
userPromptHookCommand,
|
|
24
|
+
} = require('./hook-runtime');
|
|
24
25
|
|
|
25
26
|
function getHome() {
|
|
26
27
|
return process.env.HOME || process.env.USERPROFILE || '';
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
// --- Hook definitions ---
|
|
30
|
-
|
|
31
|
-
function preToolHookCommand() {
|
|
32
|
-
return `bash ${quoteShellPath(path.join(PKG_ROOT, 'scripts', 'generate-pretool-hook.sh'))}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function userPromptHookCommand() {
|
|
36
|
-
return `bash ${quoteShellPath(path.join(PKG_ROOT, 'scripts', 'hook-auto-capture.sh'))}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function sessionStartHookCommand() {
|
|
40
|
-
return `bash ${quoteShellPath(path.join(PKG_ROOT, 'scripts', 'rlhf_session_start.sh'))}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
31
|
const CLAUDE_HOOKS = {
|
|
44
32
|
PreToolUse: {
|
|
45
|
-
matcher: 'Bash',
|
|
33
|
+
matcher: 'Bash|Edit|Write|MultiEdit',
|
|
46
34
|
hooks: [{ type: 'command', command: preToolHookCommand() }],
|
|
47
35
|
},
|
|
48
36
|
UserPromptSubmit: {
|
|
49
37
|
hooks: [{ type: 'command', command: userPromptHookCommand() }],
|
|
50
38
|
},
|
|
39
|
+
PostToolUse: {
|
|
40
|
+
matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
|
|
41
|
+
hooks: [{ type: 'command', command: cacheUpdateHookCommand() }],
|
|
42
|
+
},
|
|
51
43
|
SessionStart: {
|
|
52
44
|
hooks: [{ type: 'command', command: sessionStartHookCommand() }],
|
|
53
45
|
},
|
|
@@ -78,6 +70,10 @@ function claudeSettingsPath() {
|
|
|
78
70
|
return path.join(getHome(), '.claude', 'settings.local.json');
|
|
79
71
|
}
|
|
80
72
|
|
|
73
|
+
function claudeSharedSettingsPath() {
|
|
74
|
+
return path.join(getHome(), '.claude', 'settings.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
81
77
|
function loadJsonFile(filePath) {
|
|
82
78
|
if (!fs.existsSync(filePath)) return null;
|
|
83
79
|
try {
|
|
@@ -96,17 +92,67 @@ function hookAlreadyPresent(hookArray, command) {
|
|
|
96
92
|
);
|
|
97
93
|
}
|
|
98
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
|
+
|
|
99
132
|
function wireClaudeHooks(options) {
|
|
100
133
|
const settingsPath = options.settingsPath || claudeSettingsPath();
|
|
134
|
+
const sharedSettingsPath = options.sharedSettingsPath || claudeSharedSettingsPath();
|
|
101
135
|
const dryRun = options.dryRun || false;
|
|
136
|
+
const desiredStatusLine = statuslineCommand();
|
|
102
137
|
|
|
103
138
|
let settings = loadJsonFile(settingsPath) || {};
|
|
104
139
|
settings.hooks = settings.hooks || {};
|
|
105
140
|
|
|
106
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
|
+
};
|
|
107
148
|
|
|
108
149
|
for (const [lifecycle, hookDef] of Object.entries(CLAUDE_HOOKS)) {
|
|
109
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
|
+
}
|
|
110
156
|
|
|
111
157
|
if (hookAlreadyPresent(settings.hooks[lifecycle], hookCommand)) {
|
|
112
158
|
continue;
|
|
@@ -122,15 +168,41 @@ function wireClaudeHooks(options) {
|
|
|
122
168
|
}
|
|
123
169
|
|
|
124
170
|
if (added.length === 0) {
|
|
125
|
-
|
|
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
|
+
};
|
|
126
192
|
}
|
|
127
193
|
|
|
194
|
+
settings.statusLine = { type: 'command', command: desiredStatusLine };
|
|
195
|
+
|
|
128
196
|
if (!dryRun) {
|
|
129
197
|
const dir = path.dirname(settingsPath);
|
|
130
198
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
131
199
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
132
200
|
}
|
|
133
201
|
|
|
202
|
+
if (syncClaudeStatusLine(sharedSettingsPath, desiredStatusLine, dryRun)) {
|
|
203
|
+
added.push({ lifecycle: 'statusLine', command: `${desiredStatusLine} (synced ~/.claude/settings.json)` });
|
|
204
|
+
}
|
|
205
|
+
|
|
134
206
|
return { changed: true, settingsPath, added };
|
|
135
207
|
}
|
|
136
208
|
|
|
@@ -151,6 +223,11 @@ function wireCodexHooks(options) {
|
|
|
151
223
|
const preToolCmd = preToolHookCommand();
|
|
152
224
|
const userPromptCmd = userPromptHookCommand();
|
|
153
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
|
+
|
|
154
231
|
if (!hookAlreadyPresent(config.hooks.PreToolUse, preToolCmd)) {
|
|
155
232
|
config.hooks.PreToolUse = config.hooks.PreToolUse || [];
|
|
156
233
|
config.hooks.PreToolUse.push({
|
|
@@ -198,6 +275,11 @@ function wireGeminiHooks(options) {
|
|
|
198
275
|
const preToolCmd = preToolHookCommand();
|
|
199
276
|
const userPromptCmd = userPromptHookCommand();
|
|
200
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
|
+
|
|
201
283
|
if (!hookAlreadyPresent(settings.hooks.PreToolUse, preToolCmd)) {
|
|
202
284
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse || [];
|
|
203
285
|
settings.hooks.PreToolUse.push({
|
|
@@ -286,8 +368,10 @@ module.exports = {
|
|
|
286
368
|
loadJsonFile,
|
|
287
369
|
parseFlags,
|
|
288
370
|
claudeSettingsPath,
|
|
371
|
+
claudeSharedSettingsPath,
|
|
289
372
|
codexConfigPath,
|
|
290
373
|
geminiSettingsPath,
|
|
374
|
+
syncClaudeStatusLine,
|
|
291
375
|
CLAUDE_HOOKS,
|
|
292
376
|
preToolHookCommand,
|
|
293
377
|
userPromptHookCommand,
|
package/scripts/billing.js
CHANGED
|
@@ -16,12 +16,11 @@ 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,
|
|
23
22
|
getLegacyFeedbackDir,
|
|
24
|
-
|
|
23
|
+
getFallbackFeedbackDir,
|
|
25
24
|
resolveFallbackArtifactPath,
|
|
26
25
|
} = require('./feedback-paths');
|
|
27
26
|
const { getTelemetryAnalytics, getTelemetrySourceDiagnostics } = require('./telemetry-analytics');
|
|
@@ -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(
|
|
@@ -349,7 +372,7 @@ function buildBillingSourceDiagnostics(feedbackDir) {
|
|
|
349
372
|
|
|
350
373
|
return {
|
|
351
374
|
feedbackDir,
|
|
352
|
-
|
|
375
|
+
fallbackFeedbackDir: getFallbackFeedbackDir(),
|
|
353
376
|
legacyFeedbackDir: getLegacyFeedbackDir(),
|
|
354
377
|
mixedRoots,
|
|
355
378
|
files: {
|
|
@@ -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,
|
|
@@ -2078,7 +2182,7 @@ function provisionApiKey(customerId, opts = {}) {
|
|
|
2078
2182
|
return { key, customerId, createdAt: meta.createdAt, installId: meta.installId || null, reused: true, remainingCredits: meta.remainingCredits };
|
|
2079
2183
|
}
|
|
2080
2184
|
|
|
2081
|
-
const key = `
|
|
2185
|
+
const key = `tg_${crypto.randomBytes(16).toString('hex')}`;
|
|
2082
2186
|
const createdAt = new Date().toISOString();
|
|
2083
2187
|
store.keys[key] = {
|
|
2084
2188
|
customerId,
|
|
@@ -2101,7 +2205,7 @@ function rotateApiKey(oldKey) {
|
|
|
2101
2205
|
|
|
2102
2206
|
meta.active = false;
|
|
2103
2207
|
meta.disabledAt = new Date().toISOString();
|
|
2104
|
-
const newKey = `
|
|
2208
|
+
const newKey = `tg_${crypto.randomBytes(16).toString('hex')}`;
|
|
2105
2209
|
store.keys[newKey] = {
|
|
2106
2210
|
customerId: meta.customerId,
|
|
2107
2211
|
active: true,
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -126,7 +126,7 @@ async function main() {
|
|
|
126
126
|
'README.md contains unresolved merge conflict markers'
|
|
127
127
|
);
|
|
128
128
|
check(
|
|
129
|
-
landingHtml.includes('doesn\'t touch the model') || landingHtml.includes('different from
|
|
129
|
+
landingHtml.includes('doesn\'t touch the model') || landingHtml.includes('different from model-training feedback loops'),
|
|
130
130
|
'public/index.html missing honest disclaimer (FAQ or inline)'
|
|
131
131
|
);
|
|
132
132
|
check(
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* topical bundles and route queries to the most relevant subset. This reduces
|
|
10
10
|
* MCP tool calls and context window consumption.
|
|
11
11
|
*
|
|
12
|
+
* Ported from Subway_RN_Demo/scripts/context-engine.js for ThumbGate.
|
|
12
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
|
*/
|
|
@@ -311,7 +312,7 @@ function routeQuery(query, indexPath, topN) {
|
|
|
311
312
|
if (lowerQuery.includes('build') || lowerQuery.includes('ci')) intentBoost = 'ci-cd';
|
|
312
313
|
if (lowerQuery.includes('security') || lowerQuery.includes('audit')) intentBoost = 'security';
|
|
313
314
|
if (lowerQuery.includes('mobile') || lowerQuery.includes('android')) intentBoost = 'mobile-dev';
|
|
314
|
-
if (lowerQuery.includes('memory') || lowerQuery.includes('
|
|
315
|
+
if (lowerQuery.includes('memory') || lowerQuery.includes('thumbgate')) intentBoost = 'mcp-ai';
|
|
315
316
|
|
|
316
317
|
// Step 2: Contextual Ranking
|
|
317
318
|
const queryTokens = query
|
|
@@ -6,11 +6,11 @@ const path = require('path');
|
|
|
6
6
|
const { execSync } = require('child_process');
|
|
7
7
|
const os = require('os');
|
|
8
8
|
|
|
9
|
-
const LABEL = 'com.thumbgate.
|
|
9
|
+
const LABEL = 'com.thumbgate.daemon';
|
|
10
10
|
const PLIST_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
11
11
|
const NODE_PATH = process.execPath;
|
|
12
12
|
const GATEWAY_BIN = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
13
|
-
const LOG_DIR = path.join(os.homedir(), '.
|
|
13
|
+
const LOG_DIR = path.join(os.homedir(), '.thumbgate', 'logs');
|
|
14
14
|
|
|
15
15
|
function generatePlist() {
|
|
16
16
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
package/scripts/dashboard.js
CHANGED
|
@@ -105,7 +105,7 @@ function computeGateStats() {
|
|
|
105
105
|
const autoGatesPath = getAutoGatesPath();
|
|
106
106
|
const statsPath = path.join(
|
|
107
107
|
process.env.HOME || '/tmp',
|
|
108
|
-
'.
|
|
108
|
+
'.thumbgate',
|
|
109
109
|
'gate-stats.json'
|
|
110
110
|
);
|
|
111
111
|
const stats = readJsonFile(statsPath) || { blocked: 0, warned: 0, passed: 0, byGate: {} };
|
|
@@ -828,7 +828,7 @@ function printDashboard(data) {
|
|
|
828
828
|
: '\u2192';
|
|
829
829
|
|
|
830
830
|
console.log('');
|
|
831
|
-
console.log('\uD83D\uDCCA
|
|
831
|
+
console.log('\uD83D\uDCCA ThumbGate Dashboard');
|
|
832
832
|
console.log('\u2550'.repeat(46));
|
|
833
833
|
console.log(` Approval Rate : ${approval.approvalRate}% \u2192 ${approval.recentRate}% (7-day trend ${trendArrow})`);
|
|
834
834
|
console.log(` Total Signals : ${approval.total} (${approval.positive} positive, ${approval.negative} negative)`);
|
|
@@ -136,7 +136,7 @@ function enforceRetention() {
|
|
|
136
136
|
*/
|
|
137
137
|
function generateDataUsageSummary() {
|
|
138
138
|
const prefs = loadPreferences();
|
|
139
|
-
const feedbackDir = process.env.THUMBGATE_FEEDBACK_DIR || path.join(process.cwd(), '.
|
|
139
|
+
const feedbackDir = process.env.THUMBGATE_FEEDBACK_DIR || path.join(process.cwd(), '.thumbgate');
|
|
140
140
|
const logPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
141
141
|
let entryCount = 0;
|
|
142
142
|
if (fs.existsSync(logPath)) {
|