thumbgate 1.26.8 → 1.27.3
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/plugin.json +1 -1
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +2 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +44 -31
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/mcp/server-stdio.js +41 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +184 -8
- package/bin/dashboard-cli.js +7 -0
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +60 -0
- package/config/mcp-allowlists.json +8 -7
- package/config/model-candidates.json +71 -6
- package/package.json +28 -12
- package/public/about.html +162 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-plugin.html +66 -14
- package/public/compare.html +2 -2
- package/public/dashboard.html +224 -36
- package/public/guide.html +2 -2
- package/public/index.html +122 -40
- package/public/learn.html +70 -0
- package/public/lessons.html +129 -6
- package/public/numbers.html +2 -2
- package/public/pricing.html +28 -23
- package/public/pro.html +3 -3
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-schema.js +26 -0
- package/scripts/commercial-offer.js +10 -2
- package/scripts/dashboard-chat.js +199 -51
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/gates-engine.js +301 -67
- package/scripts/hybrid-feedback-context.js +141 -7
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plausible-domain-config.js +86 -0
- package/scripts/plausible-server-events.js +4 -2
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/rate-limiter.js +2 -2
- package/scripts/repeat-metric.js +28 -12
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +80 -0
- package/scripts/seo-gsd.js +113 -0
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/tool-registry.js +37 -0
- package/scripts/workflow-sentinel.js +282 -54
- package/src/api/server.js +466 -60
- package/.claude-plugin/marketplace.json +0 -85
|
@@ -18,6 +18,11 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
20
20
|
const { readJsonl } = require('./fs-utils');
|
|
21
|
+
const {
|
|
22
|
+
TRANSPORT_WORDS,
|
|
23
|
+
sanitizeFeedbackText,
|
|
24
|
+
transportWordsOnly,
|
|
25
|
+
} = require('./feedback-sanitizer');
|
|
21
26
|
|
|
22
27
|
// ---------------------------------------------------------------------------
|
|
23
28
|
// Paths
|
|
@@ -51,6 +56,7 @@ const STOPWORDS = new Set([
|
|
|
51
56
|
'has', 'had', 'not', 'but', 'they', 'you', 'can', 'will', 'all', 'any',
|
|
52
57
|
'one', 'its', 'our', 'also', 'more', 'very', 'just', 'into', 'been',
|
|
53
58
|
'bash', 'edit', 'write', 'tool', 'hook', 'clear',
|
|
59
|
+
...TRANSPORT_WORDS,
|
|
54
60
|
]);
|
|
55
61
|
|
|
56
62
|
const NEG = new Set([
|
|
@@ -74,7 +80,7 @@ const HYBRID_JSONL_READ_LIMIT = 400;
|
|
|
74
80
|
*/
|
|
75
81
|
function normalize(text) {
|
|
76
82
|
if (!text || typeof text !== 'string') return '';
|
|
77
|
-
return text
|
|
83
|
+
return sanitizeFeedbackText(text)
|
|
78
84
|
.replace(/\/Users\/[^\s/]+/g, '/Users/redacted')
|
|
79
85
|
.replace(/:\d{4,5}\b/g, ':PORT')
|
|
80
86
|
.toLowerCase()
|
|
@@ -97,7 +103,9 @@ function stripFeedbackPrefix(text) {
|
|
|
97
103
|
* Compose normalize + stripFeedbackPrefix.
|
|
98
104
|
*/
|
|
99
105
|
function normalizePatternText(text) {
|
|
100
|
-
|
|
106
|
+
const normalized = normalize(stripFeedbackPrefix(text));
|
|
107
|
+
if (transportWordsOnly(normalized)) return '';
|
|
108
|
+
return normalized;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/**
|
|
@@ -125,6 +133,104 @@ function classify(entry) {
|
|
|
125
133
|
return 'neutral';
|
|
126
134
|
}
|
|
127
135
|
|
|
136
|
+
function isHookPromptEnvelope(context) {
|
|
137
|
+
if (!context || typeof context !== 'string') return false;
|
|
138
|
+
try {
|
|
139
|
+
const parsed = JSON.parse(context);
|
|
140
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
|
|
141
|
+
return Boolean(
|
|
142
|
+
parsed.prompt &&
|
|
143
|
+
(
|
|
144
|
+
parsed.hookEventName ||
|
|
145
|
+
parsed.hook_event_name ||
|
|
146
|
+
parsed.workspaceRoot ||
|
|
147
|
+
parsed.workspace_root ||
|
|
148
|
+
parsed.session_id ||
|
|
149
|
+
parsed.sessionId ||
|
|
150
|
+
parsed.transcript_path ||
|
|
151
|
+
parsed.transcriptPath
|
|
152
|
+
)
|
|
153
|
+
);
|
|
154
|
+
} catch (_) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function patternContext(entry) {
|
|
160
|
+
const context = entry && entry.context ? String(entry.context) : '';
|
|
161
|
+
if (!context) return '';
|
|
162
|
+
const hasExplicitFeedback = Boolean(
|
|
163
|
+
entry.whatWentWrong ||
|
|
164
|
+
entry.what_went_wrong ||
|
|
165
|
+
entry.whatToChange ||
|
|
166
|
+
entry.what_to_change ||
|
|
167
|
+
entry.failureType ||
|
|
168
|
+
(Array.isArray(entry.tags) && entry.tags.length > 0) ||
|
|
169
|
+
entry.structuredRule
|
|
170
|
+
);
|
|
171
|
+
if (isHookPromptEnvelope(context) && !hasExplicitFeedback) return '';
|
|
172
|
+
if (isHookPromptEnvelope(context) && hasExplicitFeedback) {
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
return context;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Check if the feedback entry is an automated enforcement log (e.g. from gates engine)
|
|
180
|
+
* rather than real developer/user feedback.
|
|
181
|
+
*/
|
|
182
|
+
function isAutomatedFeedback(entry) {
|
|
183
|
+
const tags = entry.tags || [];
|
|
184
|
+
if (tags.includes('auto-capture') || tags.includes('gates-engine') || tags.includes('audit-trail')) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
const context = String(entry.context || entry.whatWentWrong || '').toLowerCase();
|
|
188
|
+
return context.includes('gate "') || context.includes('blocked tool') || context.includes('warned tool');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
function isHookPromptEnvelope(context) {
|
|
193
|
+
if (!context || typeof context !== 'string') return false;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(context);
|
|
196
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false;
|
|
197
|
+
return Boolean(
|
|
198
|
+
parsed.prompt &&
|
|
199
|
+
(
|
|
200
|
+
parsed.hookEventName ||
|
|
201
|
+
parsed.hook_event_name ||
|
|
202
|
+
parsed.workspaceRoot ||
|
|
203
|
+
parsed.workspace_root ||
|
|
204
|
+
parsed.session_id ||
|
|
205
|
+
parsed.sessionId ||
|
|
206
|
+
parsed.transcript_path ||
|
|
207
|
+
parsed.transcriptPath
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
} catch (_) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function patternContext(entry) {
|
|
216
|
+
const context = entry && entry.context ? String(entry.context) : '';
|
|
217
|
+
if (!context) return '';
|
|
218
|
+
const hasExplicitFeedback = Boolean(
|
|
219
|
+
entry.whatWentWrong ||
|
|
220
|
+
entry.what_went_wrong ||
|
|
221
|
+
entry.whatToChange ||
|
|
222
|
+
entry.what_to_change ||
|
|
223
|
+
entry.failureType ||
|
|
224
|
+
(Array.isArray(entry.tags) && entry.tags.length > 0) ||
|
|
225
|
+
entry.structuredRule
|
|
226
|
+
);
|
|
227
|
+
if (isHookPromptEnvelope(context) && !hasExplicitFeedback) return '';
|
|
228
|
+
if (isHookPromptEnvelope(context) && hasExplicitFeedback) {
|
|
229
|
+
return '';
|
|
230
|
+
}
|
|
231
|
+
return context;
|
|
232
|
+
}
|
|
233
|
+
|
|
128
234
|
/**
|
|
129
235
|
* Extract ms from a timestamp value. Returns 0 on failure.
|
|
130
236
|
*/
|
|
@@ -212,13 +318,15 @@ function buildHybridState(opts) {
|
|
|
212
318
|
if (cls === 'positive') positive++;
|
|
213
319
|
if (cls === 'negative') {
|
|
214
320
|
negative++;
|
|
215
|
-
// Track tool-level negative counts
|
|
216
|
-
|
|
217
|
-
|
|
321
|
+
// Track tool-level negative counts (exclude automated gate logs)
|
|
322
|
+
if (!isAutomatedFeedback(entry)) {
|
|
323
|
+
const toolName = inferToolName(entry.toolName || entry.tool_name || 'unknown', entry.context || '');
|
|
324
|
+
toolNegatives[toolName] = (toolNegatives[toolName] || 0) + 1;
|
|
325
|
+
}
|
|
218
326
|
|
|
219
327
|
// Build pattern from context / whatWentWrong / what_went_wrong
|
|
220
328
|
const rawText = [
|
|
221
|
-
entry
|
|
329
|
+
patternContext(entry),
|
|
222
330
|
entry.whatWentWrong || entry.what_went_wrong || '',
|
|
223
331
|
entry.whatToChange || entry.what_to_change || '',
|
|
224
332
|
entry.failureType || '',
|
|
@@ -254,11 +362,13 @@ function buildHybridState(opts) {
|
|
|
254
362
|
|
|
255
363
|
// Process attributed feedback separately to track attributed tool counts
|
|
256
364
|
for (const entry of attributedEntries) {
|
|
365
|
+
if (classify(entry) !== 'negative') continue; // skip pruned/positive
|
|
366
|
+
if (isAutomatedFeedback(entry)) continue; // skip automated gate blocks
|
|
257
367
|
const toolName = inferToolName(entry.toolName || entry.tool_name || entry.attributed_tool || 'unknown', entry.context || '');
|
|
258
368
|
toolNegativesAttributed[toolName] = (toolNegativesAttributed[toolName] || 0) + 1;
|
|
259
369
|
|
|
260
370
|
const rawText = [
|
|
261
|
-
entry
|
|
371
|
+
patternContext(entry),
|
|
262
372
|
entry.whatWentWrong || entry.what_went_wrong || '',
|
|
263
373
|
...(Array.isArray(entry.tags) ? entry.tags : []),
|
|
264
374
|
...(entry.richContext && Array.isArray(entry.richContext.filePaths) ? entry.richContext.filePaths : []),
|
|
@@ -626,6 +736,29 @@ function evaluatePretool(toolName, toolInput, opts) {
|
|
|
626
736
|
return evaluatePretoolFromState(state, toolName, toolInput);
|
|
627
737
|
}
|
|
628
738
|
|
|
739
|
+
// Claw-style agent support (high-ROI for EnterpriseClaw / OpenShell agents from Automation Anywhere / Nvidia)
|
|
740
|
+
// Extends hybrid context for claw_action_type (file, screen, dynamic-tool, orchestration), agent_identity, hybrid_route.
|
|
741
|
+
// Use in evaluatePretool calls from claw-aware MCP/hooks: pass {clawContext: {actionType: 'dynamic-tool-creation', agentId: '...', route: 'local/cloud'}} in opts.
|
|
742
|
+
function evaluateClawPretool(toolName, toolInput, clawContext, opts) {
|
|
743
|
+
const o = opts || {};
|
|
744
|
+
const claw = clawContext || {};
|
|
745
|
+
// Merge claw metadata into toolInput for gate evaluation (so templates like block-dynamic-tool-creation can match)
|
|
746
|
+
const enrichedInput = {
|
|
747
|
+
...(typeof toolInput === 'object' ? toolInput : { raw: toolInput }),
|
|
748
|
+
_claw: {
|
|
749
|
+
actionType: claw.actionType || 'unknown',
|
|
750
|
+
agentId: claw.agentId || 'unknown',
|
|
751
|
+
hybridRoute: claw.hybridRoute || 'unknown',
|
|
752
|
+
screenInteraction: !!claw.screenInteraction,
|
|
753
|
+
fileAccess: !!claw.fileAccess,
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const result = evaluatePretool(toolName, JSON.stringify(enrichedInput), o);
|
|
757
|
+
// Tag result with claw metadata for logging/feedback
|
|
758
|
+
result.clawContext = claw;
|
|
759
|
+
return result;
|
|
760
|
+
}
|
|
761
|
+
|
|
629
762
|
// ---------------------------------------------------------------------------
|
|
630
763
|
// CLI main()
|
|
631
764
|
// ---------------------------------------------------------------------------
|
|
@@ -674,6 +807,7 @@ function main() {
|
|
|
674
807
|
module.exports = {
|
|
675
808
|
buildHybridState,
|
|
676
809
|
evaluatePretool,
|
|
810
|
+
evaluateClawPretool,
|
|
677
811
|
compileGuardArtifact,
|
|
678
812
|
writeGuardArtifact,
|
|
679
813
|
readGuardArtifact,
|
|
@@ -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,
|
|
@@ -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,
|