thumbgate 0.9.14 → 1.1.0

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 (64) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +1 -0
  5. package/adapters/README.md +1 -1
  6. package/adapters/chatgpt/openapi.yaml +105 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +2 -2
  9. package/adapters/forge/forge.yaml +28 -0
  10. package/adapters/mcp/server-stdio.js +41 -1
  11. package/adapters/opencode/opencode.json +1 -1
  12. package/bin/cli.js +18 -3
  13. package/config/mcp-allowlists.json +11 -0
  14. package/openapi/openapi.yaml +105 -0
  15. package/package.json +7 -5
  16. package/plugins/amp-skill/INSTALL.md +3 -4
  17. package/plugins/amp-skill/SKILL.md +0 -1
  18. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  19. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  20. package/plugins/claude-skill/INSTALL.md +1 -2
  21. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  22. package/plugins/codex-profile/.mcp.json +1 -1
  23. package/plugins/codex-profile/INSTALL.md +1 -1
  24. package/plugins/codex-profile/README.md +1 -1
  25. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  26. package/plugins/opencode-profile/INSTALL.md +1 -1
  27. package/public/blog.html +1 -0
  28. package/public/dashboard.html +1 -1
  29. package/public/guide.html +1 -1
  30. package/public/index.html +8 -4
  31. package/public/learn/agent-harness-pattern.html +1 -1
  32. package/public/learn/ai-agent-persistent-memory.html +1 -1
  33. package/public/learn/mcp-pre-action-gates-explained.html +1 -1
  34. package/public/learn/stop-ai-agent-force-push.html +1 -1
  35. package/public/learn/vibe-coding-safety-net.html +1 -1
  36. package/public/learn.html +1 -1
  37. package/public/lessons.html +1 -1
  38. package/public/pro.html +1 -1
  39. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  40. package/scripts/agent-security-hardening.js +4 -4
  41. package/scripts/async-job-runner.js +84 -24
  42. package/scripts/auto-wire-hooks.js +59 -1
  43. package/scripts/context-manager.js +330 -0
  44. package/scripts/dashboard.js +1 -1
  45. package/scripts/distribution-surfaces.js +12 -0
  46. package/scripts/ensure-repo-bootstrap.js +15 -14
  47. package/scripts/export-hf-dataset.js +293 -0
  48. package/scripts/gates-engine.js +96 -10
  49. package/scripts/hook-auto-capture.sh +1 -1
  50. package/scripts/hosted-job-launcher.js +260 -0
  51. package/scripts/managed-dpo-export.js +91 -0
  52. package/scripts/obsidian-export.js +0 -1
  53. package/scripts/operational-integrity.js +50 -7
  54. package/scripts/prove-lancedb.js +62 -4
  55. package/scripts/publish-decision.js +16 -0
  56. package/scripts/self-healing-check.js +6 -1
  57. package/scripts/social-analytics/load-env.js +33 -2
  58. package/scripts/social-analytics/store.js +200 -2
  59. package/scripts/sync-version.js +18 -11
  60. package/scripts/tool-registry.js +48 -0
  61. package/scripts/train_from_feedback.py +0 -4
  62. package/scripts/workflow-sentinel.js +793 -0
  63. package/src/api/server.js +205 -27
  64. /package/scripts/{rlhf_session_start.sh → thumbgate_session_start.sh} +0 -0
@@ -0,0 +1,330 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Context Manager — Unified Context-Augmented Generation (CAG) Orchestrator
6
+ *
7
+ * Single entry point that assembles a normalized context object from:
8
+ * - Session state (primer / handoff)
9
+ * - User profile (role, preferences, agent type)
10
+ * - Relevant lessons (per-action retrieval)
11
+ * - Prevention rules / pre-tool guards
12
+ * - Context pack (ContextFS retrieval)
13
+ * - Code-graph impact (optional, for coding tasks)
14
+ *
15
+ * Implements tiered graceful degradation:
16
+ * Tier 1 (full) — session + lessons + rules + context pack + code-graph
17
+ * Tier 2 (warm) — lessons + rules + context pack (no session)
18
+ * Tier 3 (cold) — prevention rules + global defaults only
19
+ *
20
+ * Role-aware filtering shapes output by agent type and license tier.
21
+ */
22
+
23
+ const {
24
+ ensureContextFs,
25
+ constructContextPack,
26
+ readSessionHandoff,
27
+ recordProvenance,
28
+ } = require('./contextfs');
29
+ const { retrieveRelevantLessons } = require('./lesson-retrieval');
30
+ const { evaluatePretool } = require('./hybrid-feedback-context');
31
+ const { loadProfile } = require('./user-profile');
32
+ const {
33
+ analyzeCodeGraphImpact,
34
+ formatCodeGraphRecallSection,
35
+ } = require('./codegraph-context');
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Agent capability profiles — shapes what context each agent type receives
39
+ // ---------------------------------------------------------------------------
40
+
41
+ const AGENT_PROFILES = {
42
+ claude: {
43
+ maxLessons: 8,
44
+ includeCodeGraph: true,
45
+ includeStructuredRules: true,
46
+ contextBudget: 10000,
47
+ },
48
+ cursor: {
49
+ maxLessons: 5,
50
+ includeCodeGraph: true,
51
+ includeStructuredRules: true,
52
+ contextBudget: 6000,
53
+ },
54
+ forgecode: {
55
+ maxLessons: 5,
56
+ includeCodeGraph: false,
57
+ includeStructuredRules: true,
58
+ contextBudget: 6000,
59
+ },
60
+ codex: {
61
+ maxLessons: 6,
62
+ includeCodeGraph: true,
63
+ includeStructuredRules: true,
64
+ contextBudget: 8000,
65
+ },
66
+ default: {
67
+ maxLessons: 5,
68
+ includeCodeGraph: false,
69
+ includeStructuredRules: true,
70
+ contextBudget: 6000,
71
+ },
72
+ };
73
+
74
+ function getAgentProfile(agentType) {
75
+ const key = String(agentType || 'default').toLowerCase();
76
+ return AGENT_PROFILES[key] || AGENT_PROFILES.default;
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Tier assembly helpers
81
+ // ---------------------------------------------------------------------------
82
+
83
+ function assembleSession() {
84
+ try {
85
+ return readSessionHandoff();
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ function assembleLessons(query, agentProfile, options = {}) {
92
+ try {
93
+ return retrieveRelevantLessons(
94
+ options.toolName || '',
95
+ query,
96
+ { maxResults: agentProfile.maxLessons, feedbackDir: options.feedbackDir },
97
+ );
98
+ } catch {
99
+ return [];
100
+ }
101
+ }
102
+
103
+ function assembleGuards(toolName, toolInput) {
104
+ try {
105
+ return evaluatePretool(toolName || '', toolInput || {});
106
+ } catch {
107
+ return { mode: 'allow', reason: 'guard-unavailable' };
108
+ }
109
+ }
110
+
111
+ function assembleContextPack(query, agentProfile) {
112
+ try {
113
+ ensureContextFs();
114
+ return constructContextPack({
115
+ query,
116
+ maxItems: Math.min(8, Math.ceil(agentProfile.contextBudget / 1000)),
117
+ maxChars: agentProfile.contextBudget,
118
+ });
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ function assembleCodeGraph(query, repoPath, agentProfile) {
125
+ if (!agentProfile.includeCodeGraph) return null;
126
+ try {
127
+ const impact = analyzeCodeGraphImpact({
128
+ intentId: null,
129
+ context: query,
130
+ repoPath,
131
+ });
132
+ return formatCodeGraphRecallSection(impact) || null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ function assembleUserProfile() {
139
+ try {
140
+ const profile = loadProfile();
141
+ if (!profile || !profile.entries || profile.entries.length === 0) return null;
142
+ return {
143
+ entries: profile.entries,
144
+ charCount: profile.charCount || 0,
145
+ };
146
+ } catch {
147
+ return null;
148
+ }
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Tier classification
153
+ // ---------------------------------------------------------------------------
154
+
155
+ function classifyTier(components) {
156
+ const hasSession = !!components.session;
157
+ const hasLessons = components.lessons && components.lessons.length > 0;
158
+ const hasPack = !!components.contextPack;
159
+
160
+ if (hasSession && (hasLessons || hasPack)) return 'full';
161
+ if (hasLessons || hasPack) return 'warm';
162
+ return 'cold';
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Main orchestrator
167
+ // ---------------------------------------------------------------------------
168
+
169
+ /**
170
+ * Assemble a unified context object for a given query.
171
+ *
172
+ * @param {Object} params
173
+ * @param {string} params.query - Task description / context query
174
+ * @param {string} [params.toolName] - Current tool being invoked (for lesson retrieval)
175
+ * @param {Object} [params.toolInput] - Current tool input (for guard evaluation)
176
+ * @param {string} [params.agentType] - Agent type: claude, cursor, forgecode, codex
177
+ * @param {string} [params.repoPath] - Repo path for code-graph analysis
178
+ * @param {string} [params.feedbackDir] - Override feedback directory
179
+ * @returns {Object} Normalized context object
180
+ */
181
+ function assembleUnifiedContext(params = {}) {
182
+ const {
183
+ query = '',
184
+ toolName,
185
+ toolInput,
186
+ agentType,
187
+ repoPath,
188
+ feedbackDir,
189
+ } = params;
190
+
191
+ const agentProfile = getAgentProfile(agentType);
192
+
193
+ // Assemble all components — each is fault-tolerant
194
+ const session = assembleSession();
195
+ const userProfile = assembleUserProfile();
196
+ const lessons = assembleLessons(query, agentProfile, { toolName, feedbackDir });
197
+ const guards = assembleGuards(toolName, toolInput);
198
+ const contextPack = assembleContextPack(query, agentProfile);
199
+ const codeGraph = assembleCodeGraph(query, repoPath, agentProfile);
200
+
201
+ const components = { session, userProfile, lessons, guards, contextPack, codeGraph };
202
+ const tier = classifyTier(components);
203
+
204
+ const result = {
205
+ tier,
206
+ agentType: agentType || 'default',
207
+ agentProfile: {
208
+ maxLessons: agentProfile.maxLessons,
209
+ contextBudget: agentProfile.contextBudget,
210
+ includeCodeGraph: agentProfile.includeCodeGraph,
211
+ },
212
+ session: session || null,
213
+ userProfile: userProfile || null,
214
+ lessons,
215
+ guards,
216
+ contextPack: contextPack ? {
217
+ packId: contextPack.packId,
218
+ itemCount: Array.isArray(contextPack.items) ? contextPack.items.length : 0,
219
+ items: (contextPack.items || []).slice(0, 5).map((item) => ({
220
+ id: item.id,
221
+ namespace: item.namespace,
222
+ title: item.title,
223
+ tags: item.tags || [],
224
+ score: item.score,
225
+ })),
226
+ visibility: contextPack.visibility || null,
227
+ cached: !!(contextPack.cache && contextPack.cache.hit),
228
+ } : null,
229
+ codeGraph: codeGraph || null,
230
+ assembledAt: new Date().toISOString(),
231
+ };
232
+
233
+ // Record provenance for audit trail
234
+ try {
235
+ recordProvenance({
236
+ type: 'unified_context_assembled',
237
+ tier,
238
+ agentType: result.agentType,
239
+ lessonCount: lessons.length,
240
+ guardDecision: guards.mode || 'allow',
241
+ hasSession: !!session,
242
+ hasUserProfile: !!userProfile,
243
+ hasCodeGraph: !!codeGraph,
244
+ packId: result.contextPack ? result.contextPack.packId : null,
245
+ });
246
+ } catch {
247
+ // Provenance write failure must never break context assembly
248
+ }
249
+
250
+ return result;
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Formatting for MCP tool response
255
+ // ---------------------------------------------------------------------------
256
+
257
+ function formatUnifiedContext(ctx) {
258
+ const lines = [];
259
+
260
+ lines.push(`## Unified Context (Tier: ${ctx.tier})`);
261
+ lines.push(`Agent: ${ctx.agentType} | Assembled: ${ctx.assembledAt}`);
262
+ lines.push('');
263
+
264
+ // Session
265
+ if (ctx.session) {
266
+ lines.push('### Session');
267
+ if (ctx.session.lastTask) lines.push(`Last task: ${ctx.session.lastTask}`);
268
+ if (ctx.session.nextStep) lines.push(`Next step: ${ctx.session.nextStep}`);
269
+ if (ctx.session.blockers && ctx.session.blockers.length > 0) {
270
+ lines.push(`Blockers: ${ctx.session.blockers.join(', ')}`);
271
+ }
272
+ lines.push('');
273
+ }
274
+
275
+ // User profile
276
+ if (ctx.userProfile) {
277
+ lines.push('### User Profile');
278
+ ctx.userProfile.entries.slice(0, 3).forEach((entry) => {
279
+ lines.push(`- ${entry.slice(0, 120)}`);
280
+ });
281
+ lines.push('');
282
+ }
283
+
284
+ // Guards
285
+ if (ctx.guards && ctx.guards.mode !== 'allow') {
286
+ lines.push(`### Guard: ${ctx.guards.mode.toUpperCase()}`);
287
+ lines.push(ctx.guards.reason || 'No reason provided');
288
+ lines.push('');
289
+ }
290
+
291
+ // Lessons
292
+ if (ctx.lessons && ctx.lessons.length > 0) {
293
+ lines.push(`### Lessons (${ctx.lessons.length})`);
294
+ ctx.lessons.forEach((lesson) => {
295
+ const signal = lesson.signal === 'negative' ? '[-]' : '[+]';
296
+ lines.push(`${signal} ${lesson.title || lesson.id} (score: ${lesson.relevanceScore})`);
297
+ if (lesson.rule) {
298
+ lines.push(` Rule: IF ${lesson.rule.condition || '?'} THEN ${lesson.rule.action || '?'}`);
299
+ }
300
+ });
301
+ lines.push('');
302
+ }
303
+
304
+ // Context pack
305
+ if (ctx.contextPack) {
306
+ lines.push(`### Context Pack (${ctx.contextPack.itemCount} items)`);
307
+ ctx.contextPack.items.forEach((item) => {
308
+ lines.push(`- [${item.namespace}] ${item.title} (score: ${item.score})`);
309
+ });
310
+ if (ctx.contextPack.cached) lines.push('(cached)');
311
+ lines.push('');
312
+ }
313
+
314
+ // Code graph
315
+ if (ctx.codeGraph) {
316
+ lines.push('### Code Graph Impact');
317
+ lines.push(ctx.codeGraph);
318
+ lines.push('');
319
+ }
320
+
321
+ return lines.join('\n');
322
+ }
323
+
324
+ module.exports = {
325
+ assembleUnifiedContext,
326
+ formatUnifiedContext,
327
+ getAgentProfile,
328
+ AGENT_PROFILES,
329
+ classifyTier,
330
+ };
@@ -523,7 +523,7 @@ function computeInstrumentationReadiness(analytics, billing) {
523
523
  const cli = telemetry.cli || {};
524
524
 
525
525
  return {
526
- plausibleConfigured: /\/js\/analytics\.js/.test(landingPage),
526
+ plausibleConfigured: /plausible\.io\/js\/script\.js|\/js\/analytics\.js/.test(landingPage),
527
527
  ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
528
528
  googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
529
529
  softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
@@ -6,6 +6,7 @@ const path = require('node:path');
6
6
  const ROOT = path.join(__dirname, '..');
7
7
  const PRODUCTHUNT_URL = 'https://www.producthunt.com/products/thumbgate';
8
8
  const CLAUDE_PLUGIN_LATEST_ASSET_NAME = 'thumbgate-claude-desktop.mcpb';
9
+ const CLAUDE_PLUGIN_NEXT_ASSET_NAME = 'thumbgate-claude-desktop-next.mcpb';
9
10
 
10
11
  function readJson(root, relativePath) {
11
12
  return JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8'));
@@ -24,6 +25,14 @@ function getClaudePluginVersionedAssetName(version = getPackageVersion(ROOT)) {
24
25
  return `thumbgate-claude-desktop-v${normalized}.mcpb`;
25
26
  }
26
27
 
28
+ function isPrereleaseVersion(version = getPackageVersion(ROOT)) {
29
+ return /^\d+\.\d+\.\d+-[0-9A-Za-z.-]+$/.test(String(version || '').trim());
30
+ }
31
+
32
+ function getClaudePluginChannelAssetName(version = getPackageVersion(ROOT)) {
33
+ return isPrereleaseVersion(version) ? CLAUDE_PLUGIN_NEXT_ASSET_NAME : CLAUDE_PLUGIN_LATEST_ASSET_NAME;
34
+ }
35
+
27
36
  function getClaudePluginLatestDownloadUrl(root = ROOT) {
28
37
  return `${getRepositoryUrl(root)}/releases/latest/download/${CLAUDE_PLUGIN_LATEST_ASSET_NAME}`;
29
38
  }
@@ -35,10 +44,13 @@ function getClaudePluginVersionedDownloadUrl(root = ROOT, version = getPackageVe
35
44
 
36
45
  module.exports = {
37
46
  CLAUDE_PLUGIN_LATEST_ASSET_NAME,
47
+ CLAUDE_PLUGIN_NEXT_ASSET_NAME,
38
48
  PRODUCTHUNT_URL,
49
+ getClaudePluginChannelAssetName,
39
50
  getClaudePluginLatestDownloadUrl,
40
51
  getClaudePluginVersionedAssetName,
41
52
  getClaudePluginVersionedDownloadUrl,
42
53
  getPackageVersion,
43
54
  getRepositoryUrl,
55
+ isPrereleaseVersion,
44
56
  };
@@ -5,12 +5,13 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const REPO_ROOT = path.resolve(process.argv[2] || process.cwd());
8
- const RLHF_ENTRY = {
8
+ const THUMBGATE_ENTRY = {
9
9
  command: 'npx',
10
10
  args: ['-y', 'thumbgate@latest', 'serve'],
11
11
  };
12
- const LEGACY_SERVER_NAMES = ['thumbgate', 'rlhf_feedback_loop'];
13
- const INFO_EXCLUDE_ENTRIES = ['.rlhf/', '.thumbgate/', '.mcp.json'];
12
+ const MCP_SERVER_KEY = 'thumbgate';
13
+ const LEGACY_SERVER_NAMES = ['rlhf', 'mcp-memory-gateway', 'rlhf_feedback_loop'];
14
+ const INFO_EXCLUDE_ENTRIES = ['.thumbgate/', '.mcp.json'];
14
15
 
15
16
  function readJson(filePath) {
16
17
  try {
@@ -36,11 +37,11 @@ function writeJsonIfChanged(filePath, value) {
36
37
  return true;
37
38
  }
38
39
 
39
- function mergeRlhfEntry(entry = {}) {
40
+ function mergeThumbgateEntry(entry = {}) {
40
41
  return {
41
42
  ...entry,
42
- command: RLHF_ENTRY.command,
43
- args: RLHF_ENTRY.args.slice(),
43
+ command: THUMBGATE_ENTRY.command,
44
+ args: THUMBGATE_ENTRY.args.slice(),
44
45
  };
45
46
  }
46
47
 
@@ -49,7 +50,7 @@ function ensureMcpJson(repoRoot) {
49
50
  const existing = readJson(filePath);
50
51
  const config = existing && typeof existing === 'object' ? existing : {};
51
52
  config.mcpServers = config.mcpServers && typeof config.mcpServers === 'object' ? config.mcpServers : {};
52
- config.mcpServers.rlhf = mergeRlhfEntry(config.mcpServers.rlhf);
53
+ config.mcpServers[MCP_SERVER_KEY] = mergeThumbgateEntry(config.mcpServers[MCP_SERVER_KEY]);
53
54
  for (const legacyName of LEGACY_SERVER_NAMES) {
54
55
  delete config.mcpServers[legacyName];
55
56
  }
@@ -63,13 +64,13 @@ function ensureClaudeSettings(repoRoot) {
63
64
  return false;
64
65
  }
65
66
  const hasRelevantServer =
66
- Boolean(existing.mcpServers && existing.mcpServers.rlhf) ||
67
+ Boolean(existing.mcpServers && existing.mcpServers[MCP_SERVER_KEY]) ||
67
68
  LEGACY_SERVER_NAMES.some((name) => Boolean(existing.mcpServers && existing.mcpServers[name]));
68
69
  if (!hasRelevantServer) {
69
70
  return false;
70
71
  }
71
72
  existing.mcpServers = existing.mcpServers && typeof existing.mcpServers === 'object' ? existing.mcpServers : {};
72
- existing.mcpServers.rlhf = mergeRlhfEntry(existing.mcpServers.rlhf);
73
+ existing.mcpServers[MCP_SERVER_KEY] = mergeThumbgateEntry(existing.mcpServers[MCP_SERVER_KEY]);
73
74
  for (const legacyName of LEGACY_SERVER_NAMES) {
74
75
  delete existing.mcpServers[legacyName];
75
76
  }
@@ -106,19 +107,19 @@ function ensureInfoExclude(repoRoot) {
106
107
  return true;
107
108
  }
108
109
 
109
- function ensureRlhfDir(repoRoot) {
110
- const rlhfDir = path.join(repoRoot, '.rlhf');
111
- if (fs.existsSync(rlhfDir)) {
110
+ function ensureThumbgateDir(repoRoot) {
111
+ const thumbgateDir = path.join(repoRoot, '.thumbgate');
112
+ if (fs.existsSync(thumbgateDir)) {
112
113
  return false;
113
114
  }
114
- fs.mkdirSync(rlhfDir, { recursive: true });
115
+ fs.mkdirSync(thumbgateDir, { recursive: true });
115
116
  return true;
116
117
  }
117
118
 
118
119
  function main() {
119
120
  const results = {
120
121
  repoRoot: REPO_ROOT,
121
- createdRlhfDir: ensureRlhfDir(REPO_ROOT),
122
+ createdThumbgateDir: ensureThumbgateDir(REPO_ROOT),
122
123
  updatedMcpJson: ensureMcpJson(REPO_ROOT),
123
124
  updatedClaudeSettings: ensureClaudeSettings(REPO_ROOT),
124
125
  updatedInfoExclude: ensureInfoExclude(REPO_ROOT),