thumbgate 1.27.18 → 1.27.19
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 +6 -6
- package/.claude-plugin/plugin.json +4 -3
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +33 -12
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +249 -30
- package/adapters/chatgpt/openapi.yaml +12 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/gemini/function-declarations.json +1 -0
- package/adapters/mcp/server-stdio.js +263 -11
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +1429 -121
- package/bin/postinstall.js +1 -8
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +216 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +31 -2
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +23 -13
- package/config/merge-quality-checks.json +0 -1
- package/config/model-candidates.json +121 -6
- package/config/post-deploy-marketing-pages.json +80 -0
- package/config/tessl-tiles.json +1 -3
- package/openapi/openapi.yaml +12 -0
- package/package.json +1 -1
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +72 -20
- package/public/compare.html +31 -8
- package/public/dashboard.html +930 -166
- package/public/federal.html +2 -2
- package/public/guide.html +33 -13
- package/public/index.html +469 -111
- package/public/learn.html +183 -18
- package/public/lessons.html +168 -10
- package/public/numbers.html +7 -7
- package/public/pro.html +34 -11
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/agent-readiness.js +20 -3
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/auto-promote-gates.js +82 -10
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/billing.js +93 -1
- package/scripts/bot-detection.js +61 -3
- package/scripts/build-metadata.js +50 -10
- package/scripts/cli-feedback.js +4 -2
- package/scripts/cli-schema.js +97 -0
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/commercial-offer.js +82 -2
- package/scripts/context-manager.js +74 -6
- package/scripts/dashboard.js +68 -2
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-loop.js +123 -1
- package/scripts/feedback-quality.js +87 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +1176 -85
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-runtime.js +20 -14
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +142 -7
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/license.js +10 -10
- package/scripts/llm-client.js +169 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/mcp-config.js +7 -1
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/meta-agent-loop.js +36 -0
- package/scripts/operational-integrity.js +39 -5
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/plausible-server-events.js +9 -6
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/rate-limiter.js +64 -13
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +260 -10
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/seo-gsd.js +916 -7
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +28 -2
- package/scripts/statusline.sh +20 -4
- package/scripts/telemetry-analytics.js +357 -0
- package/scripts/thompson-sampling.js +31 -10
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-registry.js +169 -1
- package/scripts/vector-store.js +45 -0
- package/scripts/workflow-sentinel.js +286 -53
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +2683 -319
- package/scripts/bot-detector.js +0 -50
|
@@ -111,7 +111,8 @@ function isSparseAttentionFamily(modelFamily) {
|
|
|
111
111
|
|
|
112
112
|
function resolveProviderMode(env = process.env) {
|
|
113
113
|
const explicit = normalizeSlug(env.THUMBGATE_PROVIDER_MODE || env.THUMBGATE_MODEL_PROVIDER_MODE);
|
|
114
|
-
if (explicit === 'local' || explicit === 'managed') return explicit;
|
|
114
|
+
if (explicit === 'local' || explicit === 'managed' || explicit === 'vertex') return explicit;
|
|
115
|
+
if (env.VERTEX_PROJECT_ID || env.VERTEX_API_ENDPOINT) return 'vertex';
|
|
115
116
|
if (env.THUMBGATE_LOCAL_MODEL_FAMILY || env.THUMBGATE_LOCAL_MODEL_SERVER) return 'local';
|
|
116
117
|
return 'managed';
|
|
117
118
|
}
|
|
@@ -133,6 +134,7 @@ function resolveModelFamily(env = process.env) {
|
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
function buildBackendLabel(providerMode, modelFamily) {
|
|
137
|
+
if (providerMode === 'vertex') return 'Vertex AI secure cloud backend';
|
|
136
138
|
if (providerMode === 'managed') return 'Managed API backend';
|
|
137
139
|
if (modelFamily.startsWith('deepseek')) return 'Local DeepSeek sparse backend';
|
|
138
140
|
if (modelFamily.startsWith('glm')) return 'Local GLM sparse backend';
|
|
@@ -148,14 +150,18 @@ function detectInferenceBackend(env = process.env) {
|
|
|
148
150
|
&& supportsSparseAttention
|
|
149
151
|
&& INDEXCACHE_SERVER_ENGINES.has(serverEngine);
|
|
150
152
|
const indexCacheEnabled = indexCacheEligible && parseBoolean(env.THUMBGATE_INDEXCACHE_ENABLED, false);
|
|
151
|
-
const id = providerMode === '
|
|
152
|
-
? '
|
|
153
|
-
:
|
|
154
|
-
?
|
|
155
|
-
:
|
|
153
|
+
const id = providerMode === 'vertex'
|
|
154
|
+
? 'vertex-api'
|
|
155
|
+
: providerMode === 'managed'
|
|
156
|
+
? 'managed-api'
|
|
157
|
+
: supportsSparseAttention
|
|
158
|
+
? `local-${modelFamily}-sparse`
|
|
159
|
+
: 'local-dense';
|
|
156
160
|
|
|
157
161
|
let rationale = 'Baseline backend with no sparse-attention acceleration.';
|
|
158
|
-
if (providerMode === '
|
|
162
|
+
if (providerMode === 'vertex') {
|
|
163
|
+
rationale = 'Vertex AI secure cloud backend providing compliant enterprise Gemini models inside VPC boundary.';
|
|
164
|
+
} else if (providerMode === 'managed') {
|
|
159
165
|
rationale = 'Managed API path does not expose sparse-attention kernel controls like IndexCache.';
|
|
160
166
|
} else if (indexCacheEnabled) {
|
|
161
167
|
rationale = `Local ${modelFamily} backend is sparse-attention capable and IndexCache-ready on ${serverEngine}.`;
|
|
@@ -336,7 +342,8 @@ function resolveModelRole(role, env) {
|
|
|
336
342
|
const envKey = `THUMBGATE_MODEL_ROLE_${normalized.toUpperCase()}`;
|
|
337
343
|
const modelFamily = resolveModelFamily(e);
|
|
338
344
|
const isLocalGlm = modelFamily.startsWith('glm');
|
|
339
|
-
const
|
|
345
|
+
const providerMode = resolveProviderMode(e);
|
|
346
|
+
const provider = isLocalGlm ? 'local' : (providerMode === 'vertex' ? 'vertex' : 'gemini');
|
|
340
347
|
const defaultModel = isLocalGlm ? (GLM_MODEL_ROLES[normalized] || MODEL_ROLES[normalized]) : MODEL_ROLES[normalized];
|
|
341
348
|
const model = (e[envKey] && String(e[envKey]).trim()) || defaultModel;
|
|
342
349
|
return { role: normalized, model, provider, envKey };
|
package/scripts/mcp-config.js
CHANGED
|
@@ -107,9 +107,15 @@ function portableMcpEntry(pkgVersion) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function codexAutoUpdateCliEntry(commandArgs = []) {
|
|
110
|
+
// Fast-start from the installed runtime binary; only resolve @latest via npx
|
|
111
|
+
// when the runtime is absent (e.g. first launch). This matches the hook
|
|
112
|
+
// commands and must never block MCP server startup on a per-launch reinstall
|
|
113
|
+
// (a blocking `npm install thumbgate@latest` on every launch caused
|
|
114
|
+
// capture/serve timeouts on slow or offline networks — a failed install left
|
|
115
|
+
// the chained `exec` unreached, so the server never started).
|
|
110
116
|
return {
|
|
111
117
|
command: 'sh',
|
|
112
|
-
args: ['-lc', publishedCliShellCommand('latest', commandArgs
|
|
118
|
+
args: ['-lc', publishedCliShellCommand('latest', commandArgs)],
|
|
113
119
|
};
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const REQUIRED_SCOPE_FIELDS = ['entityId', 'projectId', 'processId', 'sessionId'];
|
|
5
|
+
const MEMORY_OS_LAYERS = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
id: 'file_layer',
|
|
8
|
+
name: 'File Layer',
|
|
9
|
+
purpose: 'Raw feedback, tool receipts, sessions, and memory rows are durably stored before interpretation.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'vector_db_layer',
|
|
13
|
+
name: 'Vector DB Layer',
|
|
14
|
+
purpose: 'Semantic retrieval can find related lessons without stuffing every raw memory into context.',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'structured_facts_layer',
|
|
18
|
+
name: 'Structured Facts Layer',
|
|
19
|
+
purpose: 'Confirmed account, project, policy, and budget facts are typed separately from fuzzy memories.',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'auto_curation_layer',
|
|
23
|
+
name: 'Auto Curation Layer',
|
|
24
|
+
purpose: 'Duplicate, stale, contradictory, and unscoped memories are consolidated before retrieval quality decays.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'context_layer',
|
|
28
|
+
name: 'Context Layer',
|
|
29
|
+
purpose: 'Only relevant scoped memories enter a given tool call, PR, deployment, or support session.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'interface_layer',
|
|
33
|
+
name: 'Interface Layer',
|
|
34
|
+
purpose: 'The memory contract is exposed through CLI, MCP, hooks, dashboards, and agent adapters without model lock-in.',
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
5
37
|
|
|
6
38
|
const FIELD_ALIASES = {
|
|
7
39
|
entityId: [
|
|
@@ -228,6 +260,128 @@ function buildRecommendations({ unscopedRecords, crossScopeDuplicates }) {
|
|
|
228
260
|
return recommendations;
|
|
229
261
|
}
|
|
230
262
|
|
|
263
|
+
function hasEmbeddingEvidence(record = {}) {
|
|
264
|
+
return Boolean(
|
|
265
|
+
record.embedding
|
|
266
|
+
|| record.vector
|
|
267
|
+
|| record.embeddingId
|
|
268
|
+
|| record.metadata?.embedding
|
|
269
|
+
|| record.metadata?.embeddingId
|
|
270
|
+
|| record.metadata?.vectorId
|
|
271
|
+
|| record.semanticKey
|
|
272
|
+
|| record.metadata?.semanticKey
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function hasStructuredFactEvidence(record = {}) {
|
|
277
|
+
const type = String(record.type || record.kind || record.memoryType || record.metadata?.type || '').toLowerCase();
|
|
278
|
+
return type === 'fact'
|
|
279
|
+
|| type === 'structured_fact'
|
|
280
|
+
|| Boolean(record.factKey || record.fact || record.metadata?.factKey || record.metadata?.fact);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function hasContextEvidence(record = {}) {
|
|
284
|
+
return Boolean(
|
|
285
|
+
record.contextPackId
|
|
286
|
+
|| record.contextPack
|
|
287
|
+
|| record.metadata?.contextPackId
|
|
288
|
+
|| record.metadata?.contextPack
|
|
289
|
+
|| record.retrievalQuery
|
|
290
|
+
|| record.metadata?.retrievalQuery
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function boolCapability(capabilities = {}, ...keys) {
|
|
295
|
+
return keys.some((key) => capabilities[key] === true);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function buildMemoryOsLayerReport(records = [], capabilities = {}) {
|
|
299
|
+
const scopeReport = buildMemoryScopeReadinessReport(records);
|
|
300
|
+
const semanticRecords = records.filter(hasEmbeddingEvidence);
|
|
301
|
+
const structuredFactRecords = records.filter(hasStructuredFactEvidence);
|
|
302
|
+
const contextRecords = records.filter(hasContextEvidence);
|
|
303
|
+
const curationReady = scopeReport.unscopedRecords === 0 && scopeReport.crossScopeDuplicates.length === 0;
|
|
304
|
+
|
|
305
|
+
const checks = [
|
|
306
|
+
{
|
|
307
|
+
id: 'file_layer',
|
|
308
|
+
ok: records.length > 0 || boolCapability(capabilities, 'rawStorage', 'fileLayer'),
|
|
309
|
+
evidence: {
|
|
310
|
+
records: records.length,
|
|
311
|
+
durableStore: Boolean(records.length > 0 || capabilities.rawStorage || capabilities.fileLayer),
|
|
312
|
+
},
|
|
313
|
+
recommendation: 'Capture raw feedback, action receipts, and tool outcomes before promoting memories.',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: 'vector_db_layer',
|
|
317
|
+
ok: semanticRecords.length > 0 || boolCapability(capabilities, 'semanticSearch', 'vectorDbLayer'),
|
|
318
|
+
evidence: {
|
|
319
|
+
semanticRecords: semanticRecords.length,
|
|
320
|
+
semanticSearch: Boolean(capabilities.semanticSearch || capabilities.vectorDbLayer),
|
|
321
|
+
},
|
|
322
|
+
recommendation: 'Index lessons with semantic keys or embeddings so related failures are retrieved before action.',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'structured_facts_layer',
|
|
326
|
+
ok: structuredFactRecords.length > 0 || boolCapability(capabilities, 'structuredFacts', 'structuredFactsLayer'),
|
|
327
|
+
evidence: {
|
|
328
|
+
structuredFactRecords: structuredFactRecords.length,
|
|
329
|
+
structuredFacts: Boolean(capabilities.structuredFacts || capabilities.structuredFactsLayer),
|
|
330
|
+
},
|
|
331
|
+
recommendation: 'Store confirmed customer, project, policy, and budget facts as typed records, not just prose.',
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: 'auto_curation_layer',
|
|
335
|
+
ok: curationReady && boolCapability(capabilities, 'autoCuration', 'dedupe', 'autoCurationLayer'),
|
|
336
|
+
evidence: {
|
|
337
|
+
unscopedRecords: scopeReport.unscopedRecords,
|
|
338
|
+
crossScopeDuplicates: scopeReport.crossScopeDuplicates.length,
|
|
339
|
+
autoCuration: Boolean(capabilities.autoCuration || capabilities.dedupe || capabilities.autoCurationLayer),
|
|
340
|
+
},
|
|
341
|
+
recommendation: 'Run dedupe, contradiction, stale-memory, and scope-isolation checks before memories can become gates.',
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
id: 'context_layer',
|
|
345
|
+
ok: contextRecords.length > 0 || boolCapability(capabilities, 'contextPacks', 'contextLayer', 'scopedRetrieval'),
|
|
346
|
+
evidence: {
|
|
347
|
+
contextRecords: contextRecords.length,
|
|
348
|
+
scopedRetrieval: Boolean(capabilities.contextPacks || capabilities.contextLayer || capabilities.scopedRetrieval),
|
|
349
|
+
},
|
|
350
|
+
recommendation: 'Inject scoped context packs per task instead of loading every memory into the model window.',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
id: 'interface_layer',
|
|
354
|
+
ok: boolCapability(capabilities, 'mcp', 'cli', 'hooks', 'dashboard', 'interfaceLayer'),
|
|
355
|
+
evidence: {
|
|
356
|
+
cli: Boolean(capabilities.cli),
|
|
357
|
+
mcp: Boolean(capabilities.mcp),
|
|
358
|
+
hooks: Boolean(capabilities.hooks),
|
|
359
|
+
dashboard: Boolean(capabilities.dashboard),
|
|
360
|
+
},
|
|
361
|
+
recommendation: 'Expose the same memory contract through CLI, MCP, hooks, dashboard, and agent adapters.',
|
|
362
|
+
},
|
|
363
|
+
].map((check) => {
|
|
364
|
+
const layer = MEMORY_OS_LAYERS.find((candidate) => candidate.id === check.id);
|
|
365
|
+
return {
|
|
366
|
+
...layer,
|
|
367
|
+
...check,
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const missingLayers = checks.filter((check) => !check.ok).map((check) => check.id);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
ready: missingLayers.length === 0,
|
|
375
|
+
riskLevel: missingLayers.length === 0 ? 'low' : missingLayers.length <= 2 ? 'medium' : 'high',
|
|
376
|
+
layers: checks,
|
|
377
|
+
missingLayers,
|
|
378
|
+
scopeReport,
|
|
379
|
+
recommendations: checks
|
|
380
|
+
.filter((check) => !check.ok)
|
|
381
|
+
.map((check) => check.recommendation),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
231
385
|
function selectRecordsForScope(records = [], requestedScope = {}, options = {}) {
|
|
232
386
|
const requested = normalizeScope(requestedScope);
|
|
233
387
|
const requestedKey = memoryScopeKey(requested);
|
|
@@ -265,6 +419,7 @@ function buildMemoriStyleBenchmarkRecords() {
|
|
|
265
419
|
projectId: 'thumbgate',
|
|
266
420
|
processId: 'agent-a',
|
|
267
421
|
sessionId: 'session-1',
|
|
422
|
+
metadata: { semanticKey: 'checkout-readiness', contextPackId: 'checkout-pro' },
|
|
268
423
|
content: 'Use the paid sprint checklist before changing checkout code.',
|
|
269
424
|
},
|
|
270
425
|
{
|
|
@@ -298,14 +453,18 @@ function buildMemoriStyleBenchmarkRecords() {
|
|
|
298
453
|
processId: 'agent-a',
|
|
299
454
|
sessionId: 'session-1',
|
|
300
455
|
visibility: 'shared',
|
|
456
|
+
type: 'fact',
|
|
457
|
+
factKey: 'checkout.mutation_policy',
|
|
301
458
|
content: 'Shared rule: checkout mutations require audit evidence.',
|
|
302
459
|
},
|
|
303
460
|
];
|
|
304
461
|
}
|
|
305
462
|
|
|
306
463
|
module.exports = {
|
|
464
|
+
MEMORY_OS_LAYERS,
|
|
307
465
|
REQUIRED_SCOPE_FIELDS,
|
|
308
466
|
buildMemoriStyleBenchmarkRecords,
|
|
467
|
+
buildMemoryOsLayerReport,
|
|
309
468
|
buildMemoryScopeReadinessReport,
|
|
310
469
|
isSharedMemory,
|
|
311
470
|
memoryScopeKey,
|
|
@@ -282,6 +282,8 @@ function buildPromotedGate(candidate, metrics, runId) {
|
|
|
282
282
|
occurrences: metrics.hits,
|
|
283
283
|
promotedAt: new Date().toISOString(),
|
|
284
284
|
source: 'meta-agent',
|
|
285
|
+
// origin distinguishes silent-failure-clustered candidates from feedback-derived ones
|
|
286
|
+
origin: candidate.origin || 'user-feedback',
|
|
285
287
|
runId,
|
|
286
288
|
score: parseFloat(metrics.score.toFixed(3)),
|
|
287
289
|
hitRate: parseFloat(metrics.hitRate.toFixed(3)),
|
|
@@ -371,6 +373,38 @@ async function runMetaAgentLoop({ dryRun = false, verbose = false } = {}) {
|
|
|
371
373
|
candidates = generateCandidatesHeuristic(failures, blockPatterns);
|
|
372
374
|
}
|
|
373
375
|
|
|
376
|
+
// Tag existing-pipeline candidates with their origin so downstream precision
|
|
377
|
+
// measurement (silentFailureDerivedGates vs user-feedback-derived) is possible.
|
|
378
|
+
candidates = candidates.map((c) => (c.origin ? c : { ...c, origin: 'user-feedback' }));
|
|
379
|
+
|
|
380
|
+
// Step 3b: Silent-failure clustering — DEFAULT-ON as of 2026-05-21
|
|
381
|
+
// (flipped from opt-in by PR #2289). Opt out via
|
|
382
|
+
// THUMBGATE_SILENT_FAILURE_CLUSTERING=0 (or NODE_ENV=test). Candidates flow
|
|
383
|
+
// through the SAME scoring / fp-rate eval below; we do not bypass any
|
|
384
|
+
// guardrail. The point of this clustering is to cover the case where users
|
|
385
|
+
// are lazy and never give thumbs-down — keeping it opt-in meant the users
|
|
386
|
+
// who need it most never got the benefit.
|
|
387
|
+
let silentFailureStats = null;
|
|
388
|
+
const { isSilentFailureClusteringEnabled, generateSilentFailureCandidates } = require('./silent-failure-cluster');
|
|
389
|
+
if (isSilentFailureClusteringEnabled()) {
|
|
390
|
+
try {
|
|
391
|
+
const sfResult = generateSilentFailureCandidates({ feedbackLogPath });
|
|
392
|
+
silentFailureStats = sfResult.stats;
|
|
393
|
+
if (sfResult.candidates && sfResult.candidates.length > 0) {
|
|
394
|
+
candidates = candidates.concat(sfResult.candidates);
|
|
395
|
+
}
|
|
396
|
+
if (verbose) {
|
|
397
|
+
process.stdout.write(
|
|
398
|
+
`[meta-agent] silent-failure-cluster: candidates=${sfResult.candidates.length} `
|
|
399
|
+
+ `failed=${sfResult.stats.failedCalls} clusters=${sfResult.stats.clusters} `
|
|
400
|
+
+ `skipped=${sfResult.stats.skippedReason || 'none'}\n`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
if (verbose) process.stdout.write(`[meta-agent] silent-failure-cluster failed (non-fatal): ${err.message}\n`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
374
408
|
if (verbose) {
|
|
375
409
|
process.stdout.write(`[meta-agent] candidates generated: ${candidates.length} (mode=${analysisMode})\n`);
|
|
376
410
|
}
|
|
@@ -507,6 +541,8 @@ async function runMetaAgentLoop({ dryRun = false, verbose = false } = {}) {
|
|
|
507
541
|
skipped: evolutionResult.skipped || false,
|
|
508
542
|
}
|
|
509
543
|
: null,
|
|
544
|
+
silentFailureCluster: silentFailureStats,
|
|
545
|
+
silentFailureDerivedGates: promotedGates.filter((g) => g.origin === 'silent-failure-cluster').length,
|
|
510
546
|
};
|
|
511
547
|
|
|
512
548
|
if (!dryRun) {
|
|
@@ -580,17 +580,51 @@ function resolveGitHubRepository(env = process.env) {
|
|
|
580
580
|
function findOpenPrForBranch({ branchName, runner = runGh, env = process.env } = {}) {
|
|
581
581
|
const normalizedBranch = String(branchName || '').trim();
|
|
582
582
|
if (!normalizedBranch) return null;
|
|
583
|
-
|
|
583
|
+
const token = env.GH_TOKEN || env.GITHUB_TOKEN;
|
|
584
|
+
if (!token) {
|
|
584
585
|
return null;
|
|
585
586
|
}
|
|
586
|
-
const args = ['pr', 'list'];
|
|
587
587
|
const repository = resolveGitHubRepository(env);
|
|
588
|
-
if (repository)
|
|
589
|
-
|
|
588
|
+
if (!repository) return null;
|
|
589
|
+
|
|
590
|
+
// Try REST API first as it is much more robust and doesn't rely on gh CLI's GraphQL auth
|
|
591
|
+
try {
|
|
592
|
+
const owner = repository.split('/')[0];
|
|
593
|
+
const url = `https://api.github.com/repos/${repository}/pulls?head=${encodeURIComponent(owner + ':' + normalizedBranch)}&state=open`;
|
|
594
|
+
const curlConfig = `header = "Authorization: token ${token}"\nheader = "User-Agent: ThumbGate-CI"`;
|
|
595
|
+
const curlResult = spawnSync('curl', [
|
|
596
|
+
'-s',
|
|
597
|
+
'-K', '-',
|
|
598
|
+
url
|
|
599
|
+
], {
|
|
600
|
+
encoding: 'utf8',
|
|
601
|
+
input: curlConfig
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (curlResult && curlResult.status === 0) {
|
|
605
|
+
const prs = JSON.parse(curlResult.stdout || '[]');
|
|
606
|
+
if (Array.isArray(prs) && prs.length > 0) {
|
|
607
|
+
return {
|
|
608
|
+
number: prs[0].number,
|
|
609
|
+
state: prs[0].state,
|
|
610
|
+
isDraft: prs[0].draft || false,
|
|
611
|
+
url: prs[0].html_url
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
} catch (err) {
|
|
616
|
+
console.warn(`[findOpenPrForBranch] REST fallback failed: ${err.message}`);
|
|
590
617
|
}
|
|
591
|
-
|
|
618
|
+
|
|
619
|
+
// Fall back to gh CLI
|
|
620
|
+
const args = ['pr', 'list', '--repo', repository, '--head', normalizedBranch, '--state', 'open', '--json', 'number,state,isDraft,url'];
|
|
592
621
|
const result = runner(args);
|
|
593
622
|
if (!result || result.status !== 0) {
|
|
623
|
+
if (result) {
|
|
624
|
+
console.warn(`[findOpenPrForBranch] gh command failed with status ${result.status}. Stderr: ${result.stderr || ''}`);
|
|
625
|
+
} else {
|
|
626
|
+
console.warn(`[findOpenPrForBranch] gh command failed to spawn.`);
|
|
627
|
+
}
|
|
594
628
|
return null;
|
|
595
629
|
}
|
|
596
630
|
try {
|
|
@@ -8,6 +8,7 @@ const DEFAULT_PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
|
8
8
|
const DEFAULT_OUTPUT_DIR = path.join(__dirname, '..', 'docs', 'marketing');
|
|
9
9
|
const KNOWN_REPOS = Object.freeze({
|
|
10
10
|
'@anthropic-ai/sdk': 'anthropics/anthropic-sdk-typescript',
|
|
11
|
+
'@modelcontextprotocol/sdk': 'modelcontextprotocol/typescript-sdk',
|
|
11
12
|
'@google/genai': 'googleapis/js-genai',
|
|
12
13
|
'@huggingface/transformers': 'huggingface/transformers.js',
|
|
13
14
|
'@lancedb/lancedb': 'lancedb/lancedb',
|
|
@@ -23,6 +24,24 @@ const KNOWN_REPOS = Object.freeze({
|
|
|
23
24
|
undici: 'nodejs/undici',
|
|
24
25
|
});
|
|
25
26
|
|
|
27
|
+
// Communities where ThumbGate's buyers live even though they are not npm
|
|
28
|
+
// dependencies. ThumbGate ships an MCP server, so the Model Context Protocol
|
|
29
|
+
// repos are the single highest-ROI ecosystem to contribute to — but the
|
|
30
|
+
// dependency-scan above would never surface them. These are always scouted on
|
|
31
|
+
// the default (no explicit --dependencies) path, de-duped against package.json.
|
|
32
|
+
const STRATEGIC_DEPENDENCIES = Object.freeze([
|
|
33
|
+
'@modelcontextprotocol/sdk', // MCP TypeScript SDK — the protocol ThumbGate implements
|
|
34
|
+
'modelcontextprotocol/servers', // MCP servers ecosystem — where MCP authors (our buyers) collaborate
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Honest, repo-accurate framing for the outreach draft. ThumbGate does not
|
|
38
|
+
// `import` these — it implements the protocol — so the generic "while using X"
|
|
39
|
+
// line would be a false claim. Keep drafts truthful (CEO honesty rule).
|
|
40
|
+
const RELATIONSHIP_OVERRIDES = Object.freeze({
|
|
41
|
+
'@modelcontextprotocol/sdk': 'building ThumbGate as an MCP server against the Model Context Protocol spec',
|
|
42
|
+
'modelcontextprotocol/servers': 'building and testing ThumbGate as an MCP server alongside the reference MCP servers',
|
|
43
|
+
});
|
|
44
|
+
|
|
26
45
|
const BOUNTY_KEYWORDS = [
|
|
27
46
|
'bug bounty',
|
|
28
47
|
'bounty',
|
|
@@ -70,7 +89,10 @@ function dependencyNames(pkg = {}) {
|
|
|
70
89
|
}
|
|
71
90
|
|
|
72
91
|
function repoFromDependency(name) {
|
|
73
|
-
|
|
92
|
+
if (KNOWN_REPOS[name]) return KNOWN_REPOS[name];
|
|
93
|
+
// A strategic identifier may itself be an "owner/repo" slug (not an npm name).
|
|
94
|
+
if (!name.startsWith('@') && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(name)) return name;
|
|
95
|
+
return '';
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
function buildIssueSearchQueries(repo) {
|
|
@@ -89,14 +111,18 @@ function scoreOpportunity(depName, repo, options = {}) {
|
|
|
89
111
|
score += 20;
|
|
90
112
|
reasons.push('known upstream repository');
|
|
91
113
|
}
|
|
92
|
-
if (/sdk|genai|stripe|playwright|lancedb|transformers|sqlite|undici/i.test(depName)) {
|
|
114
|
+
if (/sdk|genai|stripe|playwright|lancedb|transformers|sqlite|undici|mcp|modelcontext/i.test(depName)) {
|
|
93
115
|
score += 20;
|
|
94
116
|
reasons.push('high product adjacency for agent tooling');
|
|
95
117
|
}
|
|
96
|
-
if (/anthropic|google|huggingface|stripe|microsoft|nodejs/i.test(repo)) {
|
|
118
|
+
if (/anthropic|google|huggingface|stripe|microsoft|nodejs|modelcontextprotocol/i.test(repo)) {
|
|
97
119
|
score += 15;
|
|
98
120
|
reasons.push('large ecosystem visibility');
|
|
99
121
|
}
|
|
122
|
+
if (/modelcontext|mcp/i.test(depName) || /modelcontextprotocol/i.test(repo)) {
|
|
123
|
+
score += 12;
|
|
124
|
+
reasons.push("ThumbGate's own protocol surface — buyers are MCP authors");
|
|
125
|
+
}
|
|
100
126
|
if (options.includeBounties) {
|
|
101
127
|
score += 10;
|
|
102
128
|
reasons.push('bounty search enabled');
|
|
@@ -138,7 +164,7 @@ function buildOpportunity(depName, options = {}) {
|
|
|
138
164
|
'no bounty, security, or maintainer-policy claim without source link',
|
|
139
165
|
],
|
|
140
166
|
outreachDraft: repo
|
|
141
|
-
? `I found this while using ${depName} in ThumbGate. I reproduced the issue, added a minimal fix with tests, and kept the PR scoped to the maintainer's issue.`
|
|
167
|
+
? `I found this while ${RELATIONSHIP_OVERRIDES[depName] || `using ${depName} in ThumbGate`}. I reproduced the issue, added a minimal fix with tests, and kept the PR scoped to the maintainer's issue.`
|
|
142
168
|
: '',
|
|
143
169
|
};
|
|
144
170
|
}
|
|
@@ -149,7 +175,9 @@ function buildOssPrOpportunityScoutPlan(rawOptions = {}) {
|
|
|
149
175
|
const explicitDeps = splitList(rawOptions.dependencies || rawOptions.deps);
|
|
150
176
|
const includeBounties = rawOptions.includeBounties !== false && rawOptions['include-bounties'] !== false;
|
|
151
177
|
const maxRepos = Math.max(1, Number.parseInt(String(rawOptions.maxRepos || rawOptions['max-repos'] || 12), 10) || 12);
|
|
152
|
-
const deps = explicitDeps.length
|
|
178
|
+
const deps = explicitDeps.length
|
|
179
|
+
? explicitDeps
|
|
180
|
+
: [...new Set([...dependencyNames(pkg), ...STRATEGIC_DEPENDENCIES])];
|
|
153
181
|
const opportunities = deps
|
|
154
182
|
.map((dep) => buildOpportunity(dep, { includeBounties }))
|
|
155
183
|
.filter((opportunity) => opportunity.repo)
|
|
@@ -223,6 +251,8 @@ function writeOssPrOpportunityScoutPack(outputDir = DEFAULT_OUTPUT_DIR, options
|
|
|
223
251
|
|
|
224
252
|
module.exports = {
|
|
225
253
|
KNOWN_REPOS,
|
|
254
|
+
STRATEGIC_DEPENDENCIES,
|
|
255
|
+
RELATIONSHIP_OVERRIDES,
|
|
226
256
|
buildIssueSearchQueries,
|
|
227
257
|
buildOpportunity,
|
|
228
258
|
buildOssPrOpportunityScoutPlan,
|
|
@@ -24,23 +24,25 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
const https = require('node:https');
|
|
27
|
+
const {
|
|
28
|
+
resolvePlausibleDataDomain,
|
|
29
|
+
} = require('./plausible-domain-config');
|
|
27
30
|
|
|
28
|
-
const DEFAULT_PLAUSIBLE_DOMAIN = 'thumbgate-production.up.railway.app';
|
|
29
31
|
const PLAUSIBLE_ENDPOINT = 'https://plausible.io/api/event';
|
|
30
32
|
const REQUEST_TIMEOUT_MS = 2_000;
|
|
31
33
|
|
|
32
34
|
function isPlausibleDisabled() {
|
|
33
35
|
if (process.env.THUMBGATE_PLAUSIBLE_DISABLE === '1') return true;
|
|
34
36
|
if (process.env.DO_NOT_TRACK === '1') return true;
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Automatically disable Plausible events in local development unless explicitly overridden in tests
|
|
38
|
+
if (process.env.THUMBGATE_PLAUSIBLE_DISABLE !== '0' && process.env.NODE_ENV !== 'production') {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
39
41
|
return false;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function getPlausibleDomain() {
|
|
43
|
-
return
|
|
45
|
+
return resolvePlausibleDataDomain();
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
@@ -142,6 +144,7 @@ const CHECKOUT_EVENT_NAMES = Object.freeze({
|
|
|
142
144
|
view: 'Checkout Pro Viewed',
|
|
143
145
|
emailSubmitted: 'Checkout Pro Email Submitted',
|
|
144
146
|
stripeRedirect: 'Checkout Pro Stripe Redirect Started',
|
|
147
|
+
purchase: 'Checkout Pro Purchase Completed',
|
|
145
148
|
});
|
|
146
149
|
|
|
147
150
|
function recordCheckoutFunnelEvent(stage, options = {}) {
|
|
@@ -16,7 +16,7 @@ const CREATOR_SYNTHETIC_KEY = process.env.THUMBGATE_DEV_KEY || '';
|
|
|
16
16
|
* 2. Env var: THUMBGATE_DEV_BYPASS=[set via THUMBGATE_DEV_SECRET env var]
|
|
17
17
|
* Requires a specific non-obvious value (not boolean) to prevent accidental activation.
|
|
18
18
|
*/
|
|
19
|
-
function isCreatorDev({ env = process.env, homeDir = os.homedir() } = {}) {
|
|
19
|
+
function isCreatorDev({ env = process.env, homeDir = env.HOME || env.USERPROFILE || os.homedir() } = {}) {
|
|
20
20
|
// Layer 1: env var with specific value
|
|
21
21
|
if (CREATOR_BYPASS_VALUE && String(env[CREATOR_BYPASS_ENV] || '') === CREATOR_BYPASS_VALUE) {
|
|
22
22
|
return true;
|
|
@@ -37,7 +37,7 @@ function isCreatorDev({ env = process.env, homeDir = os.homedir() } = {}) {
|
|
|
37
37
|
* with any non-empty bypass value. No env var needed — just the config file.
|
|
38
38
|
* Used by the server to skip auth on localhost during local development.
|
|
39
39
|
*/
|
|
40
|
-
function hasDevOverride(homeDir = os.homedir()) {
|
|
40
|
+
function hasDevOverride(homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir()) {
|
|
41
41
|
// Disabled during test runs to avoid interfering with auth assertions
|
|
42
42
|
if (process.env.NODE_TEST_CONTEXT || process.env.THUMBGATE_TESTING) return false;
|
|
43
43
|
try {
|
|
@@ -47,11 +47,11 @@ function hasDevOverride(homeDir = os.homedir()) {
|
|
|
47
47
|
} catch { return false; }
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function getLicenseDir(homeDir = os.homedir()) {
|
|
50
|
+
function getLicenseDir(homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir()) {
|
|
51
51
|
return path.join(homeDir, '.thumbgate');
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function getLicensePath(homeDir = os.homedir()) {
|
|
54
|
+
function getLicensePath(homeDir = process.env.HOME || process.env.USERPROFILE || os.homedir()) {
|
|
55
55
|
return path.join(getLicenseDir(homeDir), 'license.json');
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -39,9 +39,23 @@ function normalizeOptions(options = {}) {
|
|
|
39
39
|
...splitCsv(options.documents),
|
|
40
40
|
...splitCsv(options['document-ids']),
|
|
41
41
|
]);
|
|
42
|
+
const sourcePointers = unique([
|
|
43
|
+
...splitCsv(options['source-pointers']),
|
|
44
|
+
...splitCsv(options.pointers),
|
|
45
|
+
...splitCsv(options.sources),
|
|
46
|
+
]);
|
|
42
47
|
const candidateImages = Number.isFinite(Number(options['candidate-images']))
|
|
43
48
|
? Number(options['candidate-images'])
|
|
44
49
|
: null;
|
|
50
|
+
const extractedEntities = Number.isFinite(Number(options['extracted-entities']))
|
|
51
|
+
? Number(options['extracted-entities'])
|
|
52
|
+
: 0;
|
|
53
|
+
const extractedRelations = Number.isFinite(Number(options['extracted-relations']))
|
|
54
|
+
? Number(options['extracted-relations'])
|
|
55
|
+
: 0;
|
|
56
|
+
const promotionThreshold = Number.isFinite(Number(options['promotion-threshold']))
|
|
57
|
+
? Number(options['promotion-threshold'])
|
|
58
|
+
: 3;
|
|
45
59
|
|
|
46
60
|
return {
|
|
47
61
|
ragTool: String(options['rag-tool'] || options.tool || 'proxy-pointer-rag').trim() || 'proxy-pointer-rag',
|
|
@@ -49,10 +63,15 @@ function normalizeOptions(options = {}) {
|
|
|
49
63
|
sectionIds,
|
|
50
64
|
imagePointers,
|
|
51
65
|
documentIds,
|
|
66
|
+
sourcePointers,
|
|
52
67
|
candidateImages,
|
|
68
|
+
extractedEntities,
|
|
69
|
+
extractedRelations,
|
|
70
|
+
promotionThreshold,
|
|
53
71
|
crossDocumentPolicy: String(options['cross-doc-policy'] || options['cross-document-policy'] || '').trim().toLowerCase(),
|
|
54
72
|
visionFilter: normalizeBoolean(options['vision-filter']),
|
|
55
73
|
visualClaims: normalizeBoolean(options['visual-claims']),
|
|
74
|
+
pointerFirst: normalizeBoolean(options['pointer-first']) || normalizeBoolean(options['proxy-pointer']),
|
|
56
75
|
};
|
|
57
76
|
}
|
|
58
77
|
|
|
@@ -72,6 +91,14 @@ function gateApplicability(template, options) {
|
|
|
72
91
|
return false;
|
|
73
92
|
}
|
|
74
93
|
|
|
94
|
+
function hasExtractionSprawl(options) {
|
|
95
|
+
const extractedFacts = options.extractedEntities + options.extractedRelations;
|
|
96
|
+
if (extractedFacts === 0) return false;
|
|
97
|
+
if (options.pointerFirst) return true;
|
|
98
|
+
if (options.sourcePointers.length === 0) return true;
|
|
99
|
+
return extractedFacts > options.sourcePointers.length * Math.max(2, options.promotionThreshold);
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
function buildSignalSummary(options) {
|
|
76
103
|
const signals = [];
|
|
77
104
|
if (options.treePath || options.sectionIds.length > 0) {
|
|
@@ -110,6 +137,19 @@ function buildSignalSummary(options) {
|
|
|
110
137
|
risk: 'answers that describe image content may need a vision-model sanity check',
|
|
111
138
|
});
|
|
112
139
|
}
|
|
140
|
+
if (hasExtractionSprawl(options)) {
|
|
141
|
+
signals.push({
|
|
142
|
+
id: 'entity_relation_sprawl',
|
|
143
|
+
label: 'Entity/relation extraction sprawl',
|
|
144
|
+
values: unique([
|
|
145
|
+
`${options.extractedEntities} extracted entities`,
|
|
146
|
+
`${options.extractedRelations} extracted relations`,
|
|
147
|
+
`${options.sourcePointers.length} source pointers`,
|
|
148
|
+
`promotion threshold ${options.promotionThreshold}`,
|
|
149
|
+
]),
|
|
150
|
+
risk: 'eager graph extraction can create stale aliases, weak edges, and unauditable memory; keep source pointers first and promote relations only after repeated retrieval value',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
113
153
|
return signals;
|
|
114
154
|
}
|
|
115
155
|
|
|
@@ -139,11 +179,12 @@ function buildProxyPointerRagGuardrailsPlan(rawOptions = {}, templatesPath) {
|
|
|
139
179
|
templates: recommendedTemplates,
|
|
140
180
|
nextActions: [
|
|
141
181
|
'Preserve document hierarchy, section IDs, and image file paths during ingestion.',
|
|
182
|
+
'Store source pointers before extracting entities or relations; promote a relation only after repeated retrieval value and source verification.',
|
|
142
183
|
'Pass section-tree and image-pointer metadata into the agent before it answers with visuals.',
|
|
143
184
|
'Enable the recommended Document RAG Safety templates as pre-action gates.',
|
|
144
185
|
'Use a vision filter only for high-impact answers that make claims about visual content.',
|
|
145
186
|
],
|
|
146
|
-
exampleCommand: 'npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --
|
|
187
|
+
exampleCommand: 'npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --source-pointers=lesson/fb_123,tool/run_456 --extracted-entities=120 --extracted-relations=80 --pointer-first --json',
|
|
147
188
|
};
|
|
148
189
|
}
|
|
149
190
|
|
package/scripts/published-cli.js
CHANGED
|
@@ -37,14 +37,6 @@ function publishedCliShellCommand(pkgVersion, commandArgs = [], options = {}) {
|
|
|
37
37
|
const escapedArgs = commandArgs.map(shellQuote).join(' ');
|
|
38
38
|
const fastPath = `[ -x ${shellQuote(runtimeBin)} ] && exec ${shellQuote(runtimeBin)}${escapedArgs ? ` ${escapedArgs}` : ''}`;
|
|
39
39
|
const installPath = `mkdir -p ${shellQuote(prefixDir)} && exec npm ${publishedCliArgs(pkgVersion, commandArgs, { prefixDir }).map(shellQuote).join(' ')}`;
|
|
40
|
-
if (options.preferInstalled === false) {
|
|
41
|
-
const packageSpec = `thumbgate@${pkgVersion}`;
|
|
42
|
-
return [
|
|
43
|
-
`mkdir -p ${shellQuote(prefixDir)}`,
|
|
44
|
-
`npm "install" "--prefix" ${shellQuote(prefixDir)} "--no-save" "--omit=dev" ${shellQuote(packageSpec)} >/dev/null 2>&1`,
|
|
45
|
-
`exec ${shellQuote(runtimeBin)}${escapedArgs ? ` ${escapedArgs}` : ''}`,
|
|
46
|
-
].join(' && ');
|
|
47
|
-
}
|
|
48
40
|
return `${fastPath} || ${installPath}`;
|
|
49
41
|
}
|
|
50
42
|
|