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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +1 -0
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +105 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/forge/forge.yaml +28 -0
- package/adapters/mcp/server-stdio.js +41 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +18 -3
- package/config/mcp-allowlists.json +11 -0
- package/openapi/openapi.yaml +105 -0
- package/package.json +7 -5
- package/plugins/amp-skill/INSTALL.md +3 -4
- package/plugins/amp-skill/SKILL.md +0 -1
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-skill/INSTALL.md +1 -2
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +1 -0
- package/public/dashboard.html +1 -1
- package/public/guide.html +1 -1
- package/public/index.html +8 -4
- package/public/learn/agent-harness-pattern.html +1 -1
- package/public/learn/ai-agent-persistent-memory.html +1 -1
- package/public/learn/mcp-pre-action-gates-explained.html +1 -1
- package/public/learn/stop-ai-agent-force-push.html +1 -1
- package/public/learn/vibe-coding-safety-net.html +1 -1
- package/public/learn.html +1 -1
- package/public/lessons.html +1 -1
- package/public/pro.html +1 -1
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/async-job-runner.js +84 -24
- package/scripts/auto-wire-hooks.js +59 -1
- package/scripts/context-manager.js +330 -0
- package/scripts/dashboard.js +1 -1
- package/scripts/distribution-surfaces.js +12 -0
- package/scripts/ensure-repo-bootstrap.js +15 -14
- package/scripts/export-hf-dataset.js +293 -0
- package/scripts/gates-engine.js +96 -10
- package/scripts/hook-auto-capture.sh +1 -1
- package/scripts/hosted-job-launcher.js +260 -0
- package/scripts/managed-dpo-export.js +91 -0
- package/scripts/obsidian-export.js +0 -1
- package/scripts/operational-integrity.js +50 -7
- package/scripts/prove-lancedb.js +62 -4
- package/scripts/publish-decision.js +16 -0
- package/scripts/self-healing-check.js +6 -1
- package/scripts/social-analytics/load-env.js +33 -2
- package/scripts/social-analytics/store.js +200 -2
- package/scripts/sync-version.js +18 -11
- package/scripts/tool-registry.js +48 -0
- package/scripts/train_from_feedback.py +0 -4
- package/scripts/workflow-sentinel.js +793 -0
- package/src/api/server.js +205 -27
- /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
|
+
};
|
package/scripts/dashboard.js
CHANGED
|
@@ -523,7 +523,7 @@ function computeInstrumentationReadiness(analytics, billing) {
|
|
|
523
523
|
const cli = telemetry.cli || {};
|
|
524
524
|
|
|
525
525
|
return {
|
|
526
|
-
plausibleConfigured:
|
|
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
|
|
8
|
+
const THUMBGATE_ENTRY = {
|
|
9
9
|
command: 'npx',
|
|
10
10
|
args: ['-y', 'thumbgate@latest', 'serve'],
|
|
11
11
|
};
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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
|
|
40
|
+
function mergeThumbgateEntry(entry = {}) {
|
|
40
41
|
return {
|
|
41
42
|
...entry,
|
|
42
|
-
command:
|
|
43
|
-
args:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
110
|
-
const
|
|
111
|
-
if (fs.existsSync(
|
|
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(
|
|
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
|
-
|
|
122
|
+
createdThumbgateDir: ensureThumbgateDir(REPO_ROOT),
|
|
122
123
|
updatedMcpJson: ensureMcpJson(REPO_ROOT),
|
|
123
124
|
updatedClaudeSettings: ensureClaudeSettings(REPO_ROOT),
|
|
124
125
|
updatedInfoExclude: ensureInfoExclude(REPO_ROOT),
|