thumbgate 0.9.9 → 0.9.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/.claude-plugin/README.md +4 -4
  2. package/.claude-plugin/marketplace.json +4 -2
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +115 -312
  6. package/adapters/README.md +2 -2
  7. package/adapters/amp/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
  8. package/adapters/chatgpt/openapi.yaml +2 -2
  9. package/adapters/claude/.mcp.json +3 -3
  10. package/adapters/codex/config.toml +4 -4
  11. package/adapters/gemini/function-declarations.json +1 -1
  12. package/adapters/mcp/server-stdio.js +66 -6
  13. package/adapters/opencode/opencode.json +4 -2
  14. package/bin/cli.js +188 -39
  15. package/config/e2e-critical-flows.json +4 -0
  16. package/config/gates/default.json +74 -2
  17. package/config/github-about.json +1 -1
  18. package/config/mcp-allowlists.json +33 -6
  19. package/config/skill-packs/react-testing.json +1 -1
  20. package/config/tessl-tiles.json +3 -3
  21. package/openapi/openapi.yaml +2 -2
  22. package/package.json +23 -9
  23. package/plugins/amp-skill/INSTALL.md +3 -2
  24. package/plugins/amp-skill/SKILL.md +1 -0
  25. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  26. package/plugins/claude-codex-bridge/.mcp.json +5 -3
  27. package/plugins/claude-codex-bridge/README.md +1 -1
  28. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +1 -1
  29. package/plugins/claude-skill/INSTALL.md +4 -3
  30. package/plugins/claude-skill/SKILL.md +1 -1
  31. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  32. package/plugins/codex-profile/.mcp.json +5 -3
  33. package/plugins/codex-profile/INSTALL.md +2 -2
  34. package/plugins/codex-profile/README.md +1 -1
  35. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  36. package/plugins/cursor-marketplace/README.md +5 -5
  37. package/plugins/cursor-marketplace/mcp.json +4 -2
  38. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +1 -1
  39. package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
  40. package/plugins/gemini-extension/INSTALL.md +4 -4
  41. package/plugins/opencode-profile/INSTALL.md +5 -5
  42. package/public/dashboard.html +15 -8
  43. package/public/index.html +134 -375
  44. package/public/js/buyer-intent.js +252 -0
  45. package/public/pro.html +1085 -0
  46. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  47. package/scripts/adk-consolidator.js +17 -5
  48. package/scripts/agent-readiness.js +3 -1
  49. package/scripts/agent-security-hardening.js +4 -4
  50. package/scripts/auto-promote-gates.js +8 -0
  51. package/scripts/auto-wire-hooks.js +105 -21
  52. package/scripts/billing.js +111 -7
  53. package/scripts/build-metadata.js +14 -0
  54. package/scripts/check-congruence.js +1 -1
  55. package/scripts/context-engine.js +2 -1
  56. package/scripts/daemon-manager.js +2 -2
  57. package/scripts/dashboard.js +2 -2
  58. package/scripts/data-governance.js +1 -1
  59. package/scripts/deploy-gcp.sh +1 -1
  60. package/scripts/deploy-policy.js +22 -4
  61. package/scripts/dispatch-brief.js +1 -1
  62. package/scripts/ensure-repo-bootstrap.js +1 -1
  63. package/scripts/feedback-attribution.js +22 -10
  64. package/scripts/feedback-fallback.js +3 -2
  65. package/scripts/feedback-inbox-read.js +1 -1
  66. package/scripts/feedback-loop.js +41 -3
  67. package/scripts/feedback-paths.js +8 -8
  68. package/scripts/feedback-schema.js +1 -1
  69. package/scripts/feedback-to-memory.js +2 -2
  70. package/scripts/filesystem-search.js +2 -2
  71. package/scripts/gates-engine.js +765 -34
  72. package/scripts/generate-paperbanana-diagrams.sh +3 -3
  73. package/scripts/github-about.js +1 -1
  74. package/scripts/gtm-revenue-loop.js +20 -1
  75. package/scripts/hook-runtime.js +89 -0
  76. package/scripts/hook-stop-self-score.sh +3 -3
  77. package/scripts/hook-thumbgate-cache-updater.js +98 -37
  78. package/scripts/hosted-config.js +12 -10
  79. package/scripts/hybrid-feedback-context.js +54 -13
  80. package/scripts/install-mcp.js +14 -1
  81. package/scripts/intent-router.js +1 -1
  82. package/scripts/internal-agent-bootstrap.js +1 -1
  83. package/scripts/lesson-inference.js +6 -1
  84. package/scripts/license.js +54 -16
  85. package/scripts/mcp-config.js +69 -7
  86. package/scripts/memory-migration.js +1 -1
  87. package/scripts/money-watcher.js +166 -16
  88. package/scripts/operational-integrity.js +480 -0
  89. package/scripts/optimize-context.js +1 -1
  90. package/scripts/perplexity-marketing.js +1 -1
  91. package/scripts/post-everywhere.js +7 -12
  92. package/scripts/post-to-x.js +1 -1
  93. package/scripts/pr-manager.js +14 -11
  94. package/scripts/problem-detail.js +10 -10
  95. package/scripts/profile-router.js +2 -0
  96. package/scripts/prompt-dlp.js +1 -0
  97. package/scripts/prove-adapters.js +6 -6
  98. package/scripts/prove-automation.js +1 -1
  99. package/scripts/prove-autoresearch.js +1 -1
  100. package/scripts/prove-claim-verification.js +3 -3
  101. package/scripts/prove-data-pipeline.js +5 -5
  102. package/scripts/prove-data-quality.js +1 -1
  103. package/scripts/prove-evolution.js +7 -7
  104. package/scripts/prove-harnesses.js +2 -2
  105. package/scripts/prove-lancedb.js +2 -2
  106. package/scripts/prove-local-intelligence.js +1 -1
  107. package/scripts/prove-loop-closure.js +1 -1
  108. package/scripts/prove-predictive-insights.js +2 -2
  109. package/scripts/prove-runtime.js +6 -6
  110. package/scripts/prove-seo-gsd.js +1 -1
  111. package/scripts/prove-settings.js +4 -4
  112. package/scripts/prove-subway-upgrades.js +1 -1
  113. package/scripts/prove-tessl.js +2 -2
  114. package/scripts/prove-xmemory.js +2 -2
  115. package/scripts/publish-decision.js +10 -0
  116. package/scripts/published-cli.js +34 -0
  117. package/scripts/rate-limiter.js +2 -2
  118. package/scripts/reddit-monitor-cron.sh +2 -2
  119. package/scripts/reminder-engine.js +1 -1
  120. package/scripts/schedule-manager.js +3 -3
  121. package/scripts/self-healing-check.js +1 -1
  122. package/scripts/shieldcortex-memory-firewall-runner.mjs +1 -1
  123. package/scripts/skill-quality-tracker.js +1 -1
  124. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  125. package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
  126. package/scripts/social-analytics/engagement-audit.js +202 -0
  127. package/scripts/social-analytics/generate-instagram-card.js +1 -1
  128. package/scripts/social-analytics/instagram-thumbgate-post.js +5 -1
  129. package/scripts/social-analytics/install-growth-automation.js +114 -0
  130. package/scripts/social-analytics/publish-instagram-thumbgate.js +8 -2
  131. package/scripts/social-analytics/publish-thumbgate-launch.js +1 -1
  132. package/scripts/social-analytics/publishers/reddit.js +7 -12
  133. package/scripts/social-analytics/publishers/zernio.js +19 -0
  134. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
  135. package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
  136. package/scripts/social-analytics/sync-launch-assets.js +185 -0
  137. package/scripts/social-pipeline.js +2 -2
  138. package/scripts/social-post-hourly.js +185 -0
  139. package/scripts/social-quality-gate.js +119 -3
  140. package/scripts/social-reply-monitor.js +150 -34
  141. package/scripts/statusline-cache-path.js +27 -0
  142. package/scripts/statusline-meta.js +22 -0
  143. package/scripts/statusline.sh +24 -32
  144. package/scripts/sync-version.js +24 -12
  145. package/scripts/telemetry-analytics.js +4 -4
  146. package/scripts/tessl-export.js +1 -1
  147. package/scripts/test-coverage.js +20 -13
  148. package/scripts/thumbgate-search.js +2 -2
  149. package/scripts/tool-registry.js +98 -1
  150. package/scripts/train_from_feedback.py +1 -1
  151. package/scripts/user-profile.js +4 -4
  152. package/scripts/validate-feedback.js +1 -1
  153. package/scripts/vector-store.js +1 -1
  154. package/scripts/verification-loop.js +1 -1
  155. package/scripts/verify-run.js +1 -1
  156. package/scripts/weekly-auto-post.js +1 -1
  157. package/skills/{rlhf-feedback → thumbgate-feedback}/SKILL.md +1 -1
  158. package/src/api/server.js +291 -41
  159. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  160. package/scripts/social-analytics/db/social-analytics.db +0 -0
@@ -48,7 +48,7 @@ budget_check() {
48
48
 
49
49
  mkdir -p docs/diagrams
50
50
 
51
- cat > docs/diagrams/rlhf-architecture.txt <<'TXT'
51
+ cat > docs/diagrams/thumbgate-architecture.txt <<'TXT'
52
52
  The system starts when a user gives explicit thumbs up/down feedback. A capture layer enriches the signal with context and tags.
53
53
  An action resolver maps the signal to store-learning, store-mistake, or no-action.
54
54
  A schema validator enforces strict structure before memory promotion.
@@ -73,14 +73,14 @@ TXT
73
73
 
74
74
  budget_check "$PB_ESTIMATE_PER_DIAGRAM"
75
75
  paperbanana generate \
76
- --input docs/diagrams/rlhf-architecture.txt \
76
+ --input docs/diagrams/thumbgate-architecture.txt \
77
77
  --caption "ThumbGate architecture for AI coding agents with schema gate, memory store, prevention rules, and DPO export" \
78
78
  --vlm-provider gemini \
79
79
  --vlm-model "$PB_VLM_MODEL" \
80
80
  --image-provider google_imagen \
81
81
  --image-model "$PB_IMAGE_MODEL" \
82
82
  --iterations "$PB_MAX_ITERATIONS" \
83
- --output docs/diagrams/rlhf-architecture.png
83
+ --output docs/diagrams/thumbgate-architecture.png
84
84
  node scripts/budget-guard.js --add="$PB_ESTIMATE_PER_DIAGRAM" --source=paperbanana --note="architecture-overview"
85
85
 
86
86
  budget_check "$PB_ESTIMATE_PER_DIAGRAM"
@@ -79,7 +79,7 @@ function buildCanonicalRepoUrls(about) {
79
79
  licenseUrl: `${about.repositoryUrl}/blob/main/LICENSE`,
80
80
  quickStartUrl: `${about.repositoryUrl}#quick-start`,
81
81
  pluginsUrl: `${about.repositoryUrl}/tree/main/plugins`,
82
- readmeImageUrl: `https://raw.githubusercontent.com/${about.repo}/main/docs/diagrams/rlhf-architecture-pb.png`,
82
+ readmeImageUrl: `https://raw.githubusercontent.com/${about.repo}/main/docs/diagrams/thumbgate-architecture-pb.png`,
83
83
  verificationEvidenceUrl: `${about.repositoryUrl}/blob/main/docs/VERIFICATION_EVIDENCE.md`,
84
84
  compatibilityReportUrl: `${about.repositoryUrl}/blob/main/proof/compatibility/report.json`,
85
85
  automationReportUrl: `${about.repositoryUrl}/blob/main/proof/automation/report.json`,
@@ -4,13 +4,20 @@
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
  const { spawnSync } = require('node:child_process');
7
- const { GoogleGenAI } = require('@google/genai');
8
7
  const { resolveHostedBillingConfig } = require('./hosted-config');
9
8
  const { getOperationalBillingSummary } = require('./operational-summary');
10
9
 
11
10
  const COMMERCIAL_TRUTH_LINK = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md';
12
11
  const VERIFICATION_EVIDENCE_LINK = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md';
13
12
 
13
+ function getGoogleGenAI() {
14
+ try {
15
+ return require('@google/genai').GoogleGenAI;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
14
21
  function parseArgs(argv = []) {
15
22
  const options = {
16
23
  maxTargets: 6,
@@ -310,6 +317,18 @@ async function generateOutreachMessages(targets, motionCatalog = buildMotionCata
310
317
  });
311
318
  }
312
319
 
320
+ const GoogleGenAI = getGoogleGenAI();
321
+ if (!GoogleGenAI) {
322
+ return targets.map((target) => {
323
+ const selectedMotion = selectOutreachMotion(target, motionCatalog);
324
+ return {
325
+ ...target,
326
+ selectedMotion,
327
+ message: buildFallbackMessage(target, selectedMotion, motionCatalog),
328
+ };
329
+ });
330
+ }
331
+
313
332
  const ai = new GoogleGenAI({ apiKey });
314
333
  const results = [];
315
334
 
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const {
6
+ isSourceCheckout,
7
+ publishedCliAvailable,
8
+ } = require('./mcp-config');
9
+ const { runPublishedCliHelp, publishedCliArgs } = require('./published-cli');
10
+
11
+ const PKG_ROOT = path.join(__dirname, '..');
12
+ const featureSupportCache = new Map();
13
+
14
+ function packageVersion() {
15
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
16
+ return pkg.version;
17
+ }
18
+
19
+ function shellQuote(value) {
20
+ return JSON.stringify(String(value));
21
+ }
22
+
23
+ function publishedHookCommandsAvailable(version) {
24
+ if (!publishedCliAvailable(version)) {
25
+ return false;
26
+ }
27
+ if (featureSupportCache.has(version)) {
28
+ return featureSupportCache.get(version);
29
+ }
30
+
31
+ let available = false;
32
+ try {
33
+ const helpText = runPublishedCliHelp(version, { timeout: 8000 });
34
+ available = ['gate-check', 'cache-update', 'statusline-render', 'hook-auto-capture', 'session-start']
35
+ .every((command) => helpText.includes(command));
36
+ } catch {
37
+ available = false;
38
+ }
39
+
40
+ featureSupportCache.set(version, available);
41
+ return available;
42
+ }
43
+
44
+ function resolveCliBaseCommand() {
45
+ const version = packageVersion();
46
+ if (publishedHookCommandsAvailable(version)) {
47
+ return `npx ${publishedCliArgs(version).map(shellQuote).join(' ')}`;
48
+ }
49
+ if (isSourceCheckout(PKG_ROOT)) {
50
+ return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
51
+ }
52
+ return `npx -y thumbgate@${version}`;
53
+ }
54
+
55
+ function buildPortableHookCommand(subcommand) {
56
+ return `${resolveCliBaseCommand()} ${subcommand}`;
57
+ }
58
+
59
+ function preToolHookCommand() {
60
+ return buildPortableHookCommand('gate-check');
61
+ }
62
+
63
+ function userPromptHookCommand() {
64
+ return buildPortableHookCommand('hook-auto-capture');
65
+ }
66
+
67
+ function sessionStartHookCommand() {
68
+ return buildPortableHookCommand('session-start');
69
+ }
70
+
71
+ function cacheUpdateHookCommand() {
72
+ return buildPortableHookCommand('cache-update');
73
+ }
74
+
75
+ function statuslineCommand() {
76
+ return buildPortableHookCommand('statusline-render');
77
+ }
78
+
79
+ module.exports = {
80
+ buildPortableHookCommand,
81
+ cacheUpdateHookCommand,
82
+ packageVersion,
83
+ publishedHookCommandsAvailable,
84
+ preToolHookCommand,
85
+ resolveCliBaseCommand,
86
+ sessionStartHookCommand,
87
+ statuslineCommand,
88
+ userPromptHookCommand,
89
+ };
@@ -18,9 +18,9 @@ node -e '
18
18
  const path = require("path");
19
19
 
20
20
  // Resolve modules relative to ThumbGate package root
21
- const rlhfRoot = process.env.THUMBGATE_ROOT;
22
- const { selfAuditAndLog } = require(path.join(rlhfRoot, "scripts", "rlaif-self-audit"));
23
- const { getFeedbackPaths } = require(path.join(rlhfRoot, "scripts", "feedback-loop"));
21
+ const thumbgateRoot = process.env.THUMBGATE_ROOT;
22
+ const { selfAuditAndLog } = require(path.join(thumbgateRoot, "scripts", "rlaif-self-audit"));
23
+ const { getFeedbackPaths } = require(path.join(thumbgateRoot, "scripts", "feedback-loop"));
24
24
 
25
25
  const stopReason = process.env.CLAUDE_STOP_REASON || "unknown";
26
26
 
@@ -1,48 +1,109 @@
1
1
  #!/usr/bin/env node
2
+ 'use strict';
3
+
2
4
  /**
3
- * PostToolUse hook: updates ThumbGate statusline cache after feedback_stats calls.
4
- * Installed by: npx thumbgate init --agent claude-code
5
+ * PostToolUse hook: updates ThumbGate statusline cache after dashboard/stat calls.
6
+ * Also used directly by the CLI to refresh statusline counters after feedback capture.
5
7
  */
6
- 'use strict';
8
+
7
9
  const fs = require('fs');
8
10
  const path = require('path');
9
11
  const { resolveFeedbackDir } = require('./feedback-paths');
10
12
 
11
- const CACHE_PATH = path.join(resolveFeedbackDir(), 'statusline_cache.json');
13
+ function getCachePath() {
14
+ return path.join(resolveFeedbackDir(), 'statusline_cache.json');
15
+ }
12
16
 
13
- let input = '';
14
- process.stdin.setEncoding('utf8');
15
- process.stdin.on('data', chunk => { input += chunk; });
16
- process.stdin.on('end', () => {
17
+ function readExistingCache(cachePath = getCachePath()) {
17
18
  try {
18
- const event = JSON.parse(input);
19
- const tool = event.tool_name || '';
20
- if (tool !== 'mcp__rlhf__feedback_stats' && tool !== 'mcp__rlhf__dashboard') return;
21
-
22
- const raw = event.tool_response;
23
- if (!raw) return;
24
- const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
25
-
26
- const cache = {};
27
- if (tool === 'mcp__rlhf__feedback_stats') {
28
- cache.thumbs_up = String(data.totalPositive || 0);
29
- cache.thumbs_down = String(data.totalNegative || 0);
30
- cache.lessons = String((data.rubric || {}).samples || 0);
31
- cache.approval_rate = String(Math.round((data.approvalRate || 0) * 1000) / 10);
32
- cache.trend = data.trend || '?';
33
- cache.total_feedback = String(data.total || 0);
34
- } else if (tool === 'mcp__rlhf__dashboard') {
35
- const approval = data.approval || {};
36
- cache.thumbs_up = String(approval.totalPositive || 0);
37
- cache.thumbs_down = String(approval.totalNegative || 0);
38
- cache.lessons = String((data.rubric || {}).samples || 0);
39
- cache.approval_rate = String(approval.approvalRate || '?');
40
- cache.trend = approval.trendDirection || '?';
41
- cache.total_feedback = String(approval.total || 0);
19
+ return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ function writeStatuslineCache(nextCache, cachePath = getCachePath()) {
26
+ fs.mkdirSync(path.dirname(cachePath), { recursive: true });
27
+ fs.writeFileSync(cachePath, JSON.stringify(nextCache));
28
+ }
29
+
30
+ function normalizeDashboardPayload(payload = {}) {
31
+ const approval = payload.approval || {};
32
+ return {
33
+ thumbs_up: String(approval.totalPositive || 0),
34
+ thumbs_down: String(approval.totalNegative || 0),
35
+ lessons: String((payload.rubric || {}).samples || 0),
36
+ approval_rate: String(approval.approvalRate || '?'),
37
+ trend: approval.trendDirection || '?',
38
+ total_feedback: String(approval.total || 0),
39
+ };
40
+ }
41
+
42
+ function normalizeStatsPayload(payload = {}) {
43
+ return {
44
+ thumbs_up: String(payload.totalPositive || 0),
45
+ thumbs_down: String(payload.totalNegative || 0),
46
+ lessons: String((payload.rubric || {}).samples || 0),
47
+ approval_rate: String(Math.round((payload.approvalRate || 0) * 1000) / 10),
48
+ trend: payload.trend || '?',
49
+ total_feedback: String(payload.total || 0),
50
+ };
51
+ }
52
+
53
+ function refreshStatuslineCache(statsPayload = {}, cachePath = getCachePath()) {
54
+ const cache = {
55
+ ...readExistingCache(cachePath),
56
+ ...normalizeStatsPayload(statsPayload),
57
+ updated_at: String(Math.floor(Date.now() / 1000)),
58
+ };
59
+ writeStatuslineCache(cache, cachePath);
60
+ return cache;
61
+ }
62
+
63
+ function updateCacheFromEvent(event = {}, cachePath = getCachePath()) {
64
+ const tool = event.tool_name || '';
65
+ if (tool !== 'mcp__thumbgate__feedback_stats' && tool !== 'mcp__thumbgate__dashboard') {
66
+ return null;
67
+ }
68
+
69
+ const raw = event.tool_response;
70
+ if (!raw) return null;
71
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
72
+ const cache = {
73
+ ...readExistingCache(cachePath),
74
+ ...(tool === 'mcp__thumbgate__feedback_stats'
75
+ ? normalizeStatsPayload(data)
76
+ : normalizeDashboardPayload(data)),
77
+ updated_at: String(Math.floor(Date.now() / 1000)),
78
+ };
79
+ writeStatuslineCache(cache, cachePath);
80
+ return cache;
81
+ }
82
+
83
+ function runFromStdin() {
84
+ let input = '';
85
+ process.stdin.setEncoding('utf8');
86
+ process.stdin.on('data', (chunk) => { input += chunk; });
87
+ process.stdin.on('end', () => {
88
+ try {
89
+ updateCacheFromEvent(input ? JSON.parse(input) : {});
90
+ } catch {
91
+ /* statusline cache is best-effort */
42
92
  }
43
- cache.updated_at = String(Math.floor(Date.now() / 1000));
93
+ });
94
+ }
95
+
96
+ if (require.main === module) {
97
+ runFromStdin();
98
+ }
44
99
 
45
- fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
46
- fs.writeFileSync(CACHE_PATH, JSON.stringify(cache));
47
- } catch (_) { /* silent — statusline cache is best-effort */ }
48
- });
100
+ module.exports = {
101
+ getCachePath,
102
+ normalizeDashboardPayload,
103
+ normalizeStatsPayload,
104
+ readExistingCache,
105
+ refreshStatuslineCache,
106
+ runFromStdin,
107
+ updateCacheFromEvent,
108
+ writeStatuslineCache,
109
+ };
@@ -76,6 +76,10 @@ function normalizeTrackingId(value, pattern) {
76
76
  return trimmed;
77
77
  }
78
78
 
79
+ function resolveNormalizedOrigin(value, fallback = '') {
80
+ return normalizeOrigin(value) || normalizeOrigin(fallback);
81
+ }
82
+
79
83
  function joinPublicUrl(baseOrigin, pathname) {
80
84
  const normalized = normalizeOrigin(baseOrigin);
81
85
  if (!normalized) {
@@ -112,16 +116,14 @@ function buildHostedCancelUrl(appOrigin, traceId) {
112
116
  return `${joinPublicUrl(appOrigin, '/cancel')}${traceQuery}`;
113
117
  }
114
118
 
115
- function resolveHostedBillingConfig({ requestOrigin } = {}) {
119
+ function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
116
120
  const inferredOrigin = normalizeOrigin(requestOrigin) || DEFAULT_PUBLIC_APP_ORIGIN;
117
- const appOrigin = normalizeOrigin(process.env.THUMBGATE_PUBLIC_APP_ORIGIN) || inferredOrigin;
118
- const billingApiBaseUrl = normalizeOrigin(
119
- process.env.THUMBGATE_BILLING_API_BASE_URL || process.env.THUMBGATE_CANONICAL_API_BASE_URL || appOrigin
120
- ) || appOrigin;
121
- const proPriceDollars = normalizePriceDollars(process.env.THUMBGATE_PRO_PRICE_DOLLARS) || DEFAULT_PRO_PRICE_DOLLARS;
122
- const proPriceLabel = process.env.THUMBGATE_PRO_PRICE_LABEL || DEFAULT_PRO_PRICE_LABEL;
123
- const gaMeasurementId = normalizeTrackingId(process.env.THUMBGATE_GA_MEASUREMENT_ID, GA_MEASUREMENT_ID_PATTERN);
124
- const googleSiteVerification = normalizeTrackingId(process.env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
121
+ const appOrigin = resolveNormalizedOrigin(env.THUMBGATE_PUBLIC_APP_ORIGIN, inferredOrigin) || inferredOrigin;
122
+ const billingApiBaseUrl = resolveNormalizedOrigin(env.THUMBGATE_BILLING_API_BASE_URL, appOrigin) || appOrigin;
123
+ const proPriceDollars = normalizePriceDollars(env.THUMBGATE_PRO_PRICE_DOLLARS) || DEFAULT_PRO_PRICE_DOLLARS;
124
+ const proPriceLabel = env.THUMBGATE_PRO_PRICE_LABEL || DEFAULT_PRO_PRICE_LABEL;
125
+ const gaMeasurementId = normalizeTrackingId(env.THUMBGATE_GA_MEASUREMENT_ID, GA_MEASUREMENT_ID_PATTERN);
126
+ const googleSiteVerification = normalizeTrackingId(env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
125
127
 
126
128
  return {
127
129
  appOrigin,
@@ -129,7 +131,7 @@ function resolveHostedBillingConfig({ requestOrigin } = {}) {
129
131
  checkoutEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/checkout'),
130
132
  sessionEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/session'),
131
133
  checkoutFallbackUrl: normalizeAbsoluteUrl(
132
- process.env.THUMBGATE_CHECKOUT_FALLBACK_URL || DEFAULT_CHECKOUT_FALLBACK_URL
134
+ env.THUMBGATE_CHECKOUT_FALLBACK_URL || DEFAULT_CHECKOUT_FALLBACK_URL
133
135
  ) || DEFAULT_CHECKOUT_FALLBACK_URL,
134
136
  proPriceDollars,
135
137
  proPriceLabel,
@@ -22,14 +22,24 @@ const { resolveFeedbackDir } = require('./feedback-paths');
22
22
  // Paths
23
23
  // ---------------------------------------------------------------------------
24
24
 
25
- const FEEDBACK_DIR = resolveFeedbackDir();
26
- const PATHS = {
27
- feedbackLog: path.join(FEEDBACK_DIR, 'feedback-log.jsonl'),
28
- inbox: path.join(FEEDBACK_DIR, 'inbox.jsonl'),
29
- pendingSync: path.join(FEEDBACK_DIR, 'pending_cortex_sync.jsonl'),
30
- attributedFeedback: path.join(FEEDBACK_DIR, 'attributed-feedback.jsonl'),
31
- guardArtifact: path.join(FEEDBACK_DIR, 'pretool-guards.json'),
32
- };
25
+ function getHybridPaths(options = {}) {
26
+ const feedbackDir = resolveFeedbackDir({
27
+ cwd: options.cwd,
28
+ env: options.env,
29
+ feedbackDir: options.feedbackDir,
30
+ home: options.home,
31
+ });
32
+ return {
33
+ feedbackDir,
34
+ feedbackLog: path.join(feedbackDir, 'feedback-log.jsonl'),
35
+ inbox: path.join(feedbackDir, 'inbox.jsonl'),
36
+ pendingSync: path.join(feedbackDir, 'pending_cortex_sync.jsonl'),
37
+ attributedFeedback: path.join(feedbackDir, 'attributed-feedback.jsonl'),
38
+ guardArtifact: path.join(feedbackDir, 'pretool-guards.json'),
39
+ };
40
+ }
41
+
42
+ const PATHS = getHybridPaths();
33
43
 
34
44
  // ---------------------------------------------------------------------------
35
45
  // Constants
@@ -192,10 +202,11 @@ function hashText(text) {
192
202
  */
193
203
  function buildHybridState(opts) {
194
204
  const o = opts || {};
195
- const feedbackLogPath = o.feedbackLogPath || process.env.THUMBGATE_FEEDBACK_LOG || PATHS.feedbackLog;
196
- const inboxPath = o.inboxPath || process.env.THUMBGATE_FEEDBACK_INBOX || PATHS.inbox;
197
- const pendingSyncPath = o.pendingSyncPath || process.env.THUMBGATE_PENDING_SYNC || PATHS.pendingSync;
198
- const attributedFeedbackPath = o.attributedFeedbackPath || process.env.THUMBGATE_ATTRIBUTED_FEEDBACK || PATHS.attributedFeedback;
205
+ const paths = getHybridPaths(o);
206
+ const feedbackLogPath = o.feedbackLogPath || process.env.THUMBGATE_FEEDBACK_LOG || paths.feedbackLog;
207
+ const inboxPath = o.inboxPath || process.env.THUMBGATE_FEEDBACK_INBOX || paths.inbox;
208
+ const pendingSyncPath = o.pendingSyncPath || process.env.THUMBGATE_PENDING_SYNC || paths.pendingSync;
209
+ const attributedFeedbackPath = o.attributedFeedbackPath || process.env.THUMBGATE_ATTRIBUTED_FEEDBACK || paths.attributedFeedback;
199
210
 
200
211
  const feedbackEntries = readJsonl(feedbackLogPath);
201
212
  const inboxEntries = readJsonl(inboxPath);
@@ -236,6 +247,21 @@ function buildHybridState(opts) {
236
247
  entry.context || '',
237
248
  entry.whatWentWrong || entry.what_went_wrong || '',
238
249
  entry.whatToChange || entry.what_to_change || '',
250
+ entry.failureType || '',
251
+ ...(Array.isArray(entry.tags) ? entry.tags : []),
252
+ ...(entry.richContext && Array.isArray(entry.richContext.filePaths) ? entry.richContext.filePaths : []),
253
+ ...(entry.structuredRule && entry.structuredRule.metadata && Array.isArray(entry.structuredRule.metadata.filesInvolved)
254
+ ? entry.structuredRule.metadata.filesInvolved
255
+ : []),
256
+ entry.structuredRule && entry.structuredRule.action ? entry.structuredRule.action.description || '' : '',
257
+ entry.structuredRule && entry.structuredRule.trigger ? entry.structuredRule.trigger.condition || '' : '',
258
+ entry.richContext && entry.richContext.enforcement
259
+ ? [
260
+ entry.richContext.enforcement.scopeViolation ? 'scope violation' : '',
261
+ entry.richContext.enforcement.approvalFailure ? 'approval failure' : '',
262
+ entry.richContext.enforcement.protectedFileViolation ? 'protected file violation' : '',
263
+ ].join(' ')
264
+ : '',
239
265
  ].join(' ');
240
266
  const norm = normalizePatternText(rawText);
241
267
  if (!norm) continue;
@@ -260,6 +286,20 @@ function buildHybridState(opts) {
260
286
  const rawText = [
261
287
  entry.context || '',
262
288
  entry.whatWentWrong || entry.what_went_wrong || '',
289
+ ...(Array.isArray(entry.tags) ? entry.tags : []),
290
+ ...(entry.richContext && Array.isArray(entry.richContext.filePaths) ? entry.richContext.filePaths : []),
291
+ ...(entry.structuredRule && entry.structuredRule.metadata && Array.isArray(entry.structuredRule.metadata.filesInvolved)
292
+ ? entry.structuredRule.metadata.filesInvolved
293
+ : []),
294
+ entry.structuredRule && entry.structuredRule.action ? entry.structuredRule.action.description || '' : '',
295
+ entry.structuredRule && entry.structuredRule.trigger ? entry.structuredRule.trigger.condition || '' : '',
296
+ entry.richContext && entry.richContext.enforcement
297
+ ? [
298
+ entry.richContext.enforcement.scopeViolation ? 'scope violation' : '',
299
+ entry.richContext.enforcement.approvalFailure ? 'approval failure' : '',
300
+ entry.richContext.enforcement.protectedFileViolation ? 'protected file violation' : '',
301
+ ].join(' ')
302
+ : '',
263
303
  ].join(' ');
264
304
  const norm = normalizePatternText(rawText);
265
305
  if (!norm) continue;
@@ -586,7 +626,7 @@ function evaluatePretool(toolName, toolInput, opts) {
586
626
  const o = opts || {};
587
627
 
588
628
  // Fast path: compiled artifact
589
- const artifactPath = o.guardArtifactPath || process.env.THUMBGATE_GUARDS_PATH || PATHS.guardArtifact;
629
+ const artifactPath = o.guardArtifactPath || process.env.THUMBGATE_GUARDS_PATH || getHybridPaths(o).guardArtifact;
590
630
  const artifact = readGuardArtifact(artifactPath);
591
631
  if (artifact) {
592
632
  const result = evaluateCompiledGuards(artifact, toolName, toolInput);
@@ -667,6 +707,7 @@ module.exports = {
667
707
  hashText,
668
708
  hasTwoKeywordHits,
669
709
  readJsonl,
710
+ getHybridPaths,
670
711
  PATHS,
671
712
  };
672
713
 
@@ -16,7 +16,8 @@ const fs = require('fs');
16
16
  const path = require('path');
17
17
  const { resolveMcpEntry } = require('./mcp-config');
18
18
 
19
- const MCP_SERVER_KEY = 'rlhf';
19
+ const MCP_SERVER_KEY = 'thumbgate';
20
+ const LEGACY_MCP_SERVER_KEYS = ['mcp-memory-gateway', 'rlhf'];
20
21
  const PKG_ROOT = path.join(__dirname, '..');
21
22
  const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
22
23
 
@@ -80,9 +81,15 @@ function serverConfigMatches(entry, flags = {}) {
80
81
  }
81
82
 
82
83
  function isAlreadyInstalled(settings, flags = {}) {
84
+ const hasLegacyAliases = Boolean(
85
+ settings &&
86
+ settings.mcpServers &&
87
+ LEGACY_MCP_SERVER_KEYS.some((key) => Object.prototype.hasOwnProperty.call(settings.mcpServers, key))
88
+ );
83
89
  return !!(
84
90
  settings &&
85
91
  settings.mcpServers &&
92
+ !hasLegacyAliases &&
86
93
  serverConfigMatches(settings.mcpServers[MCP_SERVER_KEY], flags)
87
94
  );
88
95
  }
@@ -120,6 +127,11 @@ function installMcp(flags) {
120
127
  }
121
128
 
122
129
  settings.mcpServers[MCP_SERVER_KEY] = serverConfig;
130
+ for (const legacyKey of LEGACY_MCP_SERVER_KEYS) {
131
+ if (Object.prototype.hasOwnProperty.call(settings.mcpServers, legacyKey)) {
132
+ delete settings.mcpServers[legacyKey];
133
+ }
134
+ }
123
135
 
124
136
  // Ensure parent directory exists
125
137
  const dir = path.dirname(settingsPath);
@@ -142,6 +154,7 @@ function installMcp(flags) {
142
154
  // Exported for testing
143
155
  module.exports = {
144
156
  MCP_SERVER_KEY,
157
+ LEGACY_MCP_SERVER_KEYS,
145
158
  MCP_SERVER_CONFIG,
146
159
  resolveMcpServerConfig,
147
160
  resolveSettingsPath,
@@ -265,7 +265,7 @@ const ACTION_CATEGORY_MAP = {
265
265
  feedback_summary: 'debugging',
266
266
  search_lessons: 'search',
267
267
  retrieve_lessons: 'search',
268
- search_rlhf: 'search',
268
+ search_thumbgate: 'search',
269
269
  prevention_rules: 'security',
270
270
  construct_context_pack: 'architecture',
271
271
  export_dpo_pairs: 'testing',
@@ -17,7 +17,7 @@ const { formatCodeGraphRecallSection } = require('./codegraph-context');
17
17
 
18
18
  const KNOWN_SOURCES = new Set(['github', 'slack', 'linear', 'api', 'cli']);
19
19
  const DEFAULT_SOURCE = 'api';
20
- const DEFAULT_SANDBOX_ROOT = path.join(os.tmpdir(), 'rlhf-internal-agent-sandboxes');
20
+ const DEFAULT_SANDBOX_ROOT = path.join(os.tmpdir(), 'thumbgate-internal-agent-sandboxes');
21
21
 
22
22
  function normalizeText(value) {
23
23
  if (value === undefined || value === null) return '';
@@ -31,6 +31,11 @@ function getFeedbackDir() {
31
31
  return resolveFeedbackDir();
32
32
  }
33
33
 
34
+ function getLessonBaseUrl() {
35
+ const configuredOrigin = String(process.env.THUMBGATE_PUBLIC_APP_ORIGIN || '').trim().replace(/\/+$/, '');
36
+ return configuredOrigin || 'http://localhost:3456';
37
+ }
38
+
34
39
  function getLessonsPath() { return path.join(getFeedbackDir(), LESSONS_FILE); }
35
40
  function getRecentLessonPath() { return path.join(getFeedbackDir(), RECENT_LESSON_FILE); }
36
41
 
@@ -138,7 +143,7 @@ function createLesson({ feedbackId, signal, inferredLesson, triggerMessage, prio
138
143
  };
139
144
 
140
145
  // Stable link: dashboard deep-link to this lesson
141
- lesson.link = `http://localhost:9876/lessons#${lesson.id}`;
146
+ lesson.link = `${getLessonBaseUrl()}/lessons#${lesson.id}`;
142
147
 
143
148
  const lessonsPath = getLessonsPath();
144
149
  ensureDir(lessonsPath);