thumbgate 1.16.13 → 1.16.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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +3 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +26 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +420 -1
- package/config/gate-templates.json +372 -0
- package/config/mcp-allowlists.json +25 -0
- package/config/model-candidates.json +59 -2
- package/config/model-tiers.json +4 -1
- package/package.json +79 -22
- package/public/compare.html +6 -0
- package/public/index.html +144 -11
- package/public/numbers.html +8 -8
- package/public/pro.html +22 -24
- package/scripts/agent-design-governance.js +211 -0
- package/scripts/agent-reasoning-traces.js +683 -0
- package/scripts/agent-reward-model.js +438 -0
- package/scripts/agent-stack-survival-audit.js +231 -0
- package/scripts/ai-engineering-stack-guardrails.js +256 -0
- package/scripts/billing.js +16 -4
- package/scripts/chatgpt-ads-readiness-pack.js +195 -0
- package/scripts/cli-schema.js +277 -0
- package/scripts/code-graph-guardrails.js +176 -0
- package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
- package/scripts/gemini-embedding-policy.js +198 -0
- package/scripts/inference-cache-policy.js +39 -0
- package/scripts/judge-reward-function.js +396 -0
- package/scripts/llm-behavior-monitor.js +251 -0
- package/scripts/long-running-agent-context-guardrails.js +176 -0
- package/scripts/multimodal-retrieval-plan.js +31 -11
- package/scripts/oss-pr-opportunity-scout.js +240 -0
- package/scripts/proactive-agent-eval-guardrails.js +230 -0
- package/scripts/profile-router.js +5 -4
- package/scripts/prompting-operating-system.js +273 -0
- package/scripts/proxy-pointer-rag-guardrails.js +189 -0
- package/scripts/rag-precision-guardrails.js +202 -0
- package/scripts/rate-limiter.js +1 -1
- package/scripts/reasoning-efficiency-guardrails.js +176 -0
- package/scripts/reward-hacking-guardrails.js +251 -0
- package/scripts/seo-gsd.js +1201 -11
- package/scripts/single-use-credential-gate.js +182 -0
- package/scripts/structured-prompt-driven.js +226 -0
- package/scripts/telemetry-analytics.js +31 -6
- package/scripts/tool-registry.js +92 -0
- package/scripts/upstream-contribution-engine.js +379 -0
- package/scripts/vector-store.js +119 -4
- package/src/api/server.js +333 -100
- package/scripts/agents-sdk-sandbox-plan.js +0 -57
- package/scripts/ai-org-governance.js +0 -98
- package/scripts/artifact-agent-plan.js +0 -81
- package/scripts/enterprise-agent-rollout.js +0 -34
- package/scripts/experience-replay-governance.js +0 -69
- package/scripts/inference-economics.js +0 -53
- package/scripts/knowledge-layer-plan.js +0 -108
- package/scripts/memory-store-governance.js +0 -60
- package/scripts/post-training-governance.js +0 -34
- package/scripts/production-agent-readiness.js +0 -40
- package/scripts/scaling-law-claims.js +0 -60
- package/scripts/student-consistent-training.js +0 -73
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawnSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.join(__dirname, '..');
|
|
9
|
+
const DEFAULT_OUTPUT_DIR = path.join(ROOT, 'docs', 'marketing');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_REPO_OVERRIDES = Object.freeze({
|
|
12
|
+
'@anthropic-ai/sdk': 'anthropics/anthropic-sdk-typescript',
|
|
13
|
+
'@google/genai': 'googleapis/js-genai',
|
|
14
|
+
'@huggingface/transformers': 'huggingface/transformers.js',
|
|
15
|
+
'@lancedb/lancedb': 'lancedb/lancedb',
|
|
16
|
+
'apache-arrow': 'apache/arrow',
|
|
17
|
+
'better-sqlite3': 'WiseLibs/better-sqlite3',
|
|
18
|
+
dotenv: 'motdotla/dotenv',
|
|
19
|
+
'playwright-core': 'microsoft/playwright',
|
|
20
|
+
protobufjs: 'protobufjs/protobuf.js',
|
|
21
|
+
stripe: 'stripe/stripe-node',
|
|
22
|
+
'@changesets/cli': 'changesets/changesets',
|
|
23
|
+
'@changesets/changelog-github': 'changesets/changesets',
|
|
24
|
+
c8: 'bcoe/c8',
|
|
25
|
+
undici: 'nodejs/undici',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function normalizeRepo(value) {
|
|
29
|
+
if (!value) return '';
|
|
30
|
+
let source = String(value).trim();
|
|
31
|
+
source = source.replace(/^git\+/, '').replace(/\.git$/, '');
|
|
32
|
+
source = source.replace(/^https:\/\/github\.com\//i, '');
|
|
33
|
+
source = source.replace(/^git@github\.com:/i, '');
|
|
34
|
+
const match = source.match(/^([^/\s]+)\/([^/\s#?]+)/);
|
|
35
|
+
return match ? `${match[1]}/${match[2]}` : '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readJson(filePath, fallback = null) {
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
41
|
+
} catch (_) {
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function listDirectPackages(root = ROOT) {
|
|
47
|
+
const pkg = readJson(path.join(root, 'package.json'), {});
|
|
48
|
+
const runtime = Object.keys(pkg.dependencies || {}).map((name) => ({ name, dependencyType: 'runtime' }));
|
|
49
|
+
const dev = Object.keys(pkg.devDependencies || {}).map((name) => ({ name, dependencyType: 'dev' }));
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
return [...runtime, ...dev].filter((entry) => {
|
|
52
|
+
if (seen.has(entry.name)) return false;
|
|
53
|
+
seen.add(entry.name);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolvePackageRepo(name, options = {}) {
|
|
59
|
+
const overrides = { ...DEFAULT_REPO_OVERRIDES, ...(options.repoOverrides || {}) };
|
|
60
|
+
if (overrides[name]) return overrides[name];
|
|
61
|
+
|
|
62
|
+
const packageLock = options.packageLock || readJson(path.join(options.root || ROOT, 'package-lock.json'), {});
|
|
63
|
+
const lockEntry = packageLock.packages && packageLock.packages[`node_modules/${name}`];
|
|
64
|
+
const fromLock = normalizeRepo(lockEntry && lockEntry.repository && (lockEntry.repository.url || lockEntry.repository));
|
|
65
|
+
if (fromLock) return fromLock;
|
|
66
|
+
|
|
67
|
+
const metadata = options.packageMetadata && options.packageMetadata[name];
|
|
68
|
+
const fromMetadata = normalizeRepo(metadata && metadata.repository && (metadata.repository.url || metadata.repository));
|
|
69
|
+
if (fromMetadata) return fromMetadata;
|
|
70
|
+
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function packageIssueQueries(pkg) {
|
|
75
|
+
const terms = [
|
|
76
|
+
'is:issue is:open label:bug',
|
|
77
|
+
'is:issue is:open label:"good first issue"',
|
|
78
|
+
'is:issue is:open label:"help wanted"',
|
|
79
|
+
'is:issue is:open bounty',
|
|
80
|
+
'is:issue is:open "bug bounty"',
|
|
81
|
+
'is:issue is:open security',
|
|
82
|
+
'is:issue is:open regression',
|
|
83
|
+
'is:issue is:open docs OR documentation',
|
|
84
|
+
'is:issue is:open typescript OR types',
|
|
85
|
+
'is:issue is:open test OR ci OR flake',
|
|
86
|
+
];
|
|
87
|
+
return terms.map((term) => `repo:${pkg.repo} ${term}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function packageIssueSearchTerms() {
|
|
91
|
+
return [
|
|
92
|
+
'label:bug',
|
|
93
|
+
'label:"good first issue"',
|
|
94
|
+
'label:"help wanted"',
|
|
95
|
+
'bounty',
|
|
96
|
+
'"bug bounty"',
|
|
97
|
+
'security',
|
|
98
|
+
'regression',
|
|
99
|
+
'docs OR documentation',
|
|
100
|
+
'typescript OR types',
|
|
101
|
+
'test OR ci OR flake',
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseGhIssueList(stdout, repo) {
|
|
106
|
+
const payload = readJsonFromString(stdout, []);
|
|
107
|
+
return payload.map((issue) => ({
|
|
108
|
+
repo,
|
|
109
|
+
number: issue.number,
|
|
110
|
+
title: issue.title || '',
|
|
111
|
+
url: issue.url || `https://github.com/${repo}/issues/${issue.number}`,
|
|
112
|
+
labels: (issue.labels || []).map((label) => typeof label === 'string' ? label : label.name).filter(Boolean),
|
|
113
|
+
updatedAt: issue.updatedAt || issue.updated_at || '',
|
|
114
|
+
commentSignals: detectCommentSignals((issue.comments || []).map((comment) => comment.body || '')),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readJsonFromString(source, fallback) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(source);
|
|
121
|
+
} catch (_) {
|
|
122
|
+
return fallback;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function detectCommentSignals(commentBodies = []) {
|
|
127
|
+
const combined = commentBodies.join('\n').toLowerCase();
|
|
128
|
+
const claimed = /\b(take|taking this|take this up|would like to work|like to work on this|i'?d like to work|i'?d like to take|i'?ll work|working on this)\b/.test(combined);
|
|
129
|
+
const existingPr = /\b(opened|made|created|proposal)\s+(a\s+)?pr\b|\/pull\/\d+|pull\/new|resolved in #\d+|will be resolved in #\d+/.test(combined);
|
|
130
|
+
return {
|
|
131
|
+
claimed,
|
|
132
|
+
existingPr,
|
|
133
|
+
blocked: claimed || existingPr,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function fetchRepoIssues(repo, options = {}) {
|
|
138
|
+
if (options.offline) return [];
|
|
139
|
+
const gh = options.ghBinary || 'gh';
|
|
140
|
+
const limit = Math.max(1, Number(options.limit || 30));
|
|
141
|
+
const perQueryLimit = Math.max(5, Math.ceil(limit / 2));
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const issues = [];
|
|
144
|
+
|
|
145
|
+
for (const search of packageIssueSearchTerms()) {
|
|
146
|
+
if (issues.length >= limit) break;
|
|
147
|
+
const result = spawnSync(gh, [
|
|
148
|
+
'issue',
|
|
149
|
+
'list',
|
|
150
|
+
'--repo',
|
|
151
|
+
repo,
|
|
152
|
+
'--state',
|
|
153
|
+
'open',
|
|
154
|
+
'--search',
|
|
155
|
+
search,
|
|
156
|
+
'--limit',
|
|
157
|
+
String(perQueryLimit),
|
|
158
|
+
'--json',
|
|
159
|
+
'number,title,url,labels,updatedAt,comments',
|
|
160
|
+
], {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
timeout: options.timeoutMs || 10000,
|
|
163
|
+
});
|
|
164
|
+
if (result.status !== 0) continue;
|
|
165
|
+
for (const issue of parseGhIssueList(result.stdout, repo)) {
|
|
166
|
+
const key = `${repo}#${issue.number}`;
|
|
167
|
+
if (seen.has(key)) continue;
|
|
168
|
+
seen.add(key);
|
|
169
|
+
issues.push(issue);
|
|
170
|
+
if (issues.length >= limit) break;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return issues;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function issueScore(issue, pkg) {
|
|
178
|
+
const labels = (issue.labels || []).map((label) => String(label).toLowerCase());
|
|
179
|
+
const title = String(issue.title || '').toLowerCase();
|
|
180
|
+
let score = pkg.dependencyType === 'runtime' ? 20 : 12;
|
|
181
|
+
if (labels.some((label) => /bug|regression|defect/.test(label)) || /\bbug|regression|crash|fail/.test(title)) score += 25;
|
|
182
|
+
if (labels.some((label) => /good first issue|help wanted|up for grabs/.test(label))) score += 18;
|
|
183
|
+
if (labels.some((label) => /security|vulnerability/.test(label)) || /\bsecurity|vulnerability|cve\b/.test(title)) score += 16;
|
|
184
|
+
if (labels.some((label) => /bounty|reward|paid/.test(label)) || /\bbounty|reward|paid\b/.test(title)) score += 22;
|
|
185
|
+
if (/\b(?:docs?|test|typescript|types|ci|flake)\b/.test(title)) score += 10;
|
|
186
|
+
return score;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function buildPatchReadiness(issue, pkg) {
|
|
190
|
+
const labels = (issue.labels || []).map((label) => String(label).toLowerCase());
|
|
191
|
+
const title = String(issue.title || '').toLowerCase();
|
|
192
|
+
const commentSignals = issue.commentSignals || {};
|
|
193
|
+
const explicitSmallLabel = labels.some((label) => /good first issue|documentation|docs|test|typescript|types|ci/.test(label));
|
|
194
|
+
const smallPatchTitle = /\b(?:docs?|documentation|readme|example|test|typescript|types|ci|flake|lint|typo)\b/.test(title);
|
|
195
|
+
const helpWantedOnly = labels.some((label) => /help wanted|up for grabs/.test(label))
|
|
196
|
+
&& !explicitSmallLabel
|
|
197
|
+
&& !smallPatchTitle;
|
|
198
|
+
const highRiskTitle = /\bcrash|segfault|sigill|security|vulnerability|cve|attestation|supply chain|silent data|corruption\b/.test(title);
|
|
199
|
+
const smallPatch = (explicitSmallLabel || smallPatchTitle) && (!highRiskTitle || smallPatchTitle);
|
|
200
|
+
const canAutofix = smallPatch && !helpWantedOnly && !commentSignals.blocked;
|
|
201
|
+
const evidenceGate = commentSignals.blocked
|
|
202
|
+
? 'claimed-or-existing-pr'
|
|
203
|
+
: canAutofix ? 'autonomous-patch-ready' : 'triage-before-pr';
|
|
204
|
+
return {
|
|
205
|
+
canAutofix,
|
|
206
|
+
prAllowed: canAutofix,
|
|
207
|
+
effort: canAutofix ? 'small' : 'needs-triage',
|
|
208
|
+
evidenceGate,
|
|
209
|
+
blockers: [
|
|
210
|
+
commentSignals.claimed ? 'issue appears claimed in comments' : '',
|
|
211
|
+
commentSignals.existingPr ? 'issue appears to have an existing PR or proposal' : '',
|
|
212
|
+
].filter(Boolean),
|
|
213
|
+
requiredProof: [
|
|
214
|
+
'Fork or branch only; never push to upstream default branch.',
|
|
215
|
+
'Reproduce or cite the issue before changing code.',
|
|
216
|
+
'Run the upstream repo test command for the touched package path.',
|
|
217
|
+
'Do not open a public PR if reproduction, test proof, or maintainer relevance is missing.',
|
|
218
|
+
'Open a PR only with a minimal patch, issue link, test proof, and no ThumbGate sales copy.',
|
|
219
|
+
],
|
|
220
|
+
promotionRule: 'Earn trust by fixing the dependency; mention ThumbGate only in profile/context, not in the PR body unless directly relevant.',
|
|
221
|
+
suggestedBranch: `codex/upstream-${pkg.name.replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').toLowerCase()}-${issue.number || 'issue'}`,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function buildUpstreamContributionPlan(options = {}) {
|
|
226
|
+
const root = options.root || ROOT;
|
|
227
|
+
const issuesByRepo = options.issuesByRepo || {};
|
|
228
|
+
const packages = listDirectPackages(root)
|
|
229
|
+
.map((pkg) => ({ ...pkg, repo: resolvePackageRepo(pkg.name, { ...options, root }) }))
|
|
230
|
+
.filter((pkg) => pkg.repo)
|
|
231
|
+
.sort((left, right) => {
|
|
232
|
+
const leftHasEvidence = Object.prototype.hasOwnProperty.call(issuesByRepo, left.repo) ? 1 : 0;
|
|
233
|
+
const rightHasEvidence = Object.prototype.hasOwnProperty.call(issuesByRepo, right.repo) ? 1 : 0;
|
|
234
|
+
if (leftHasEvidence !== rightHasEvidence) return rightHasEvidence - leftHasEvidence;
|
|
235
|
+
return left.name.localeCompare(right.name);
|
|
236
|
+
});
|
|
237
|
+
const maxRepos = Math.max(1, Number(options.maxRepos || options['max-repos'] || 12));
|
|
238
|
+
const maxIssues = Math.max(1, Number(options.maxIssues || options['max-issues'] || 5));
|
|
239
|
+
|
|
240
|
+
const repoRows = packages
|
|
241
|
+
.slice(0, maxRepos)
|
|
242
|
+
.map((pkg) => {
|
|
243
|
+
const issues = issuesByRepo[pkg.repo] || fetchRepoIssues(pkg.repo, {
|
|
244
|
+
offline: options.offline !== false,
|
|
245
|
+
limit: options.issueFetchLimit || 30,
|
|
246
|
+
ghBinary: options.ghBinary,
|
|
247
|
+
timeoutMs: options.timeoutMs,
|
|
248
|
+
});
|
|
249
|
+
const rankedIssues = issues
|
|
250
|
+
.map((issue) => ({
|
|
251
|
+
...issue,
|
|
252
|
+
score: issueScore(issue, pkg),
|
|
253
|
+
readiness: buildPatchReadiness(issue, pkg),
|
|
254
|
+
}))
|
|
255
|
+
.sort((left, right) => right.score - left.score)
|
|
256
|
+
.slice(0, maxIssues);
|
|
257
|
+
return {
|
|
258
|
+
package: pkg.name,
|
|
259
|
+
dependencyType: pkg.dependencyType,
|
|
260
|
+
repo: pkg.repo,
|
|
261
|
+
searchQueries: packageIssueQueries(pkg),
|
|
262
|
+
issues: rankedIssues,
|
|
263
|
+
nextAction: rankedIssues.some((issue) => issue.readiness.canAutofix)
|
|
264
|
+
? 'Clone/fork the top autofix-ready issue, produce a minimal patch, run upstream tests, then open PR with proof.'
|
|
265
|
+
: 'Monitor issue search queries; wait for a small bug, docs, CI, type, or test issue before patching.',
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const opportunities = repoRows
|
|
270
|
+
.flatMap((row) => row.issues.map((issue) => ({
|
|
271
|
+
package: row.package,
|
|
272
|
+
repo: row.repo,
|
|
273
|
+
issueNumber: issue.number,
|
|
274
|
+
issueUrl: issue.url,
|
|
275
|
+
title: issue.title,
|
|
276
|
+
score: issue.score,
|
|
277
|
+
canAutofix: issue.readiness.canAutofix,
|
|
278
|
+
evidenceGate: issue.readiness.evidenceGate,
|
|
279
|
+
blockers: issue.readiness.blockers,
|
|
280
|
+
suggestedBranch: issue.readiness.suggestedBranch,
|
|
281
|
+
})))
|
|
282
|
+
.sort((left, right) => right.score - left.score);
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
name: 'thumbgate-upstream-contribution-engine',
|
|
286
|
+
generatedAt: new Date().toISOString(),
|
|
287
|
+
status: opportunities.length > 0 ? 'actionable' : 'discovery-ready',
|
|
288
|
+
summary: {
|
|
289
|
+
packageCount: packages.length,
|
|
290
|
+
repoCount: repoRows.length,
|
|
291
|
+
issueCount: repoRows.reduce((sum, row) => sum + row.issues.length, 0),
|
|
292
|
+
autofixReadyCount: opportunities.filter((entry) => entry.canAutofix).length,
|
|
293
|
+
},
|
|
294
|
+
guardrails: [
|
|
295
|
+
'Only target repos ThumbGate actually depends on or uses in shipped workflows.',
|
|
296
|
+
'Do not create promotional PRs; fix real upstream issues with tests.',
|
|
297
|
+
'Prefer small bugs, tests, docs, types, CI flakes, and security hardening over large feature work.',
|
|
298
|
+
'Open external PRs only after reproduction evidence, a minimal patch, and upstream tests pass.',
|
|
299
|
+
'Never paste secrets, customer data, or private ThumbGate context into upstream issues or PRs.',
|
|
300
|
+
],
|
|
301
|
+
autonomousWorkflow: [
|
|
302
|
+
'Run live discovery on schedule and rank only dependency-backed upstream repos.',
|
|
303
|
+
'Clone/fork the highest autonomous-patch-ready issue into the suggested branch.',
|
|
304
|
+
'Capture reproduction, apply the smallest patch, and run upstream tests.',
|
|
305
|
+
'Open a public PR only when the evidence gate is autonomous-patch-ready and proof artifacts exist.',
|
|
306
|
+
'Stop at a local worktree and operator report when the issue is high-risk, security-sensitive, or unreproduced.',
|
|
307
|
+
],
|
|
308
|
+
repos: repoRows,
|
|
309
|
+
opportunities,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function renderUpstreamContributionPlan(plan) {
|
|
314
|
+
const lines = [
|
|
315
|
+
'# Upstream Contribution Engine',
|
|
316
|
+
'',
|
|
317
|
+
'Use this to earn developer trust by fixing repos ThumbGate actually depends on. This is not a spam lane.',
|
|
318
|
+
'',
|
|
319
|
+
`Status: ${plan.status}`,
|
|
320
|
+
`Repos scanned: ${plan.summary.repoCount}`,
|
|
321
|
+
`Issues ranked: ${plan.summary.issueCount}`,
|
|
322
|
+
`Autofix-ready: ${plan.summary.autofixReadyCount}`,
|
|
323
|
+
'',
|
|
324
|
+
'## Guardrails',
|
|
325
|
+
'',
|
|
326
|
+
...plan.guardrails.map((item) => `- ${item}`),
|
|
327
|
+
'',
|
|
328
|
+
'## Autonomous Workflow',
|
|
329
|
+
'',
|
|
330
|
+
...plan.autonomousWorkflow.map((item) => `- ${item}`),
|
|
331
|
+
'',
|
|
332
|
+
'## Top Opportunities',
|
|
333
|
+
'',
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
if (plan.opportunities.length === 0) {
|
|
337
|
+
lines.push('No live issues were provided or discovered. Run with GitHub access enabled or review the search queries below.');
|
|
338
|
+
} else {
|
|
339
|
+
for (const item of plan.opportunities.slice(0, 20)) {
|
|
340
|
+
lines.push(`- ${item.repo}#${item.issueNumber || 'n/a'} (${item.score}, ${item.evidenceGate}) ${item.title}`);
|
|
341
|
+
lines.push(` ${item.issueUrl || `https://github.com/${item.repo}/issues`}`);
|
|
342
|
+
lines.push(` Branch: ${item.suggestedBranch}`);
|
|
343
|
+
if (item.blockers.length > 0) lines.push(` Blockers: ${item.blockers.join('; ')}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
lines.push('', '## Repo Search Queries', '');
|
|
348
|
+
for (const repo of plan.repos) {
|
|
349
|
+
lines.push(`### ${repo.package} -> ${repo.repo}`);
|
|
350
|
+
for (const query of repo.searchQueries) lines.push(`- ${query}`);
|
|
351
|
+
lines.push(`- Next: ${repo.nextAction}`, '');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function writeUpstreamContributionPlan(plan, outputDir = DEFAULT_OUTPUT_DIR) {
|
|
358
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
359
|
+
const jsonPath = path.join(outputDir, 'upstream-contribution-engine.json');
|
|
360
|
+
const mdPath = path.join(outputDir, 'upstream-contribution-engine.md');
|
|
361
|
+
fs.writeFileSync(jsonPath, `${JSON.stringify(plan, null, 2)}\n`);
|
|
362
|
+
fs.writeFileSync(mdPath, renderUpstreamContributionPlan(plan));
|
|
363
|
+
return { jsonPath, mdPath };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
module.exports = {
|
|
367
|
+
DEFAULT_REPO_OVERRIDES,
|
|
368
|
+
buildPatchReadiness,
|
|
369
|
+
buildUpstreamContributionPlan,
|
|
370
|
+
detectCommentSignals,
|
|
371
|
+
fetchRepoIssues,
|
|
372
|
+
issueScore,
|
|
373
|
+
listDirectPackages,
|
|
374
|
+
normalizeRepo,
|
|
375
|
+
packageIssueQueries,
|
|
376
|
+
renderUpstreamContributionPlan,
|
|
377
|
+
resolvePackageRepo,
|
|
378
|
+
writeUpstreamContributionPlan,
|
|
379
|
+
};
|
package/scripts/vector-store.js
CHANGED
|
@@ -8,6 +8,12 @@ const {
|
|
|
8
8
|
writeModelFitReport,
|
|
9
9
|
resolveFeedbackDir,
|
|
10
10
|
} = require('./local-model-profile');
|
|
11
|
+
const {
|
|
12
|
+
prepareEmbeddingText,
|
|
13
|
+
resolveGeminiEmbeddingConfig,
|
|
14
|
+
resolveGeminiModelResource,
|
|
15
|
+
resolveGeminiTaskType,
|
|
16
|
+
} = require('./gemini-embedding-policy');
|
|
11
17
|
const { runStep } = require('./durability/step');
|
|
12
18
|
|
|
13
19
|
const DEFAULT_FEEDBACK_DIR = resolveFeedbackDir();
|
|
@@ -20,6 +26,7 @@ let _lancedbLoader = null;
|
|
|
20
26
|
const _pipelineCache = new Map();
|
|
21
27
|
let _lastEmbeddingProfile = null;
|
|
22
28
|
let _pipelineLoader = null;
|
|
29
|
+
let _geminiEmbedderForTests = null;
|
|
23
30
|
const TABLE_NAME = 'thumbgate_memories';
|
|
24
31
|
|
|
25
32
|
async function getLanceDB() {
|
|
@@ -97,13 +104,105 @@ async function getEmbeddingPipeline() {
|
|
|
97
104
|
// Set THUMBGATE_VECTOR_STUB_EMBED=true to get a deterministic 384-dim unit vector.
|
|
98
105
|
// The real embed() is used in production and integration tests
|
|
99
106
|
// (gated by absence of this env var).
|
|
100
|
-
async function
|
|
107
|
+
async function embedWithGemini(text, options = {}) {
|
|
108
|
+
const config = resolveGeminiEmbeddingConfig();
|
|
109
|
+
if (!config.apiKey && !_geminiEmbedderForTests) {
|
|
110
|
+
throw new Error('Gemini embeddings requested but no GEMINI_API_KEY, GOOGLE_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY is configured');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const preparedText = prepareEmbeddingText({
|
|
114
|
+
content: text,
|
|
115
|
+
kind: options.kind,
|
|
116
|
+
task: options.task || config.defaultTask,
|
|
117
|
+
title: options.title,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (_geminiEmbedderForTests) {
|
|
121
|
+
return _geminiEmbedderForTests(preparedText, config, options);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof fetch !== 'function') {
|
|
125
|
+
throw new Error('Gemini embeddings require global fetch. Use Node 18.18+ or the local embedding provider.');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const modelResource = resolveGeminiModelResource(config.model);
|
|
129
|
+
const requestBody = {
|
|
130
|
+
model: modelResource,
|
|
131
|
+
content: {
|
|
132
|
+
parts: [{ text: preparedText }],
|
|
133
|
+
},
|
|
134
|
+
outputDimensionality: config.outputDimensionality,
|
|
135
|
+
};
|
|
136
|
+
const taskType = resolveGeminiTaskType({
|
|
137
|
+
kind: options.kind,
|
|
138
|
+
task: options.task || config.defaultTask,
|
|
139
|
+
});
|
|
140
|
+
if (taskType) {
|
|
141
|
+
requestBody.taskType = taskType;
|
|
142
|
+
}
|
|
143
|
+
if (taskType === 'RETRIEVAL_DOCUMENT' && options.title) {
|
|
144
|
+
requestBody.title = String(options.title);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const endpoint = `${config.apiBaseUrl}/${modelResource}:embedContent`;
|
|
148
|
+
const response = await fetch(endpoint, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'x-goog-api-key': config.apiKey,
|
|
153
|
+
},
|
|
154
|
+
body: JSON.stringify(requestBody),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
const body = await response.text().catch(() => '');
|
|
159
|
+
throw new Error(`Gemini embedding request failed: ${response.status} ${response.statusText}${body ? ` — ${body.slice(0, 240)}` : ''}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const payload = await response.json();
|
|
163
|
+
const values = payload && (
|
|
164
|
+
(payload.embedding && payload.embedding.values)
|
|
165
|
+
|| (Array.isArray(payload.embeddings) && payload.embeddings[0] && payload.embeddings[0].values)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
169
|
+
throw new Error('Gemini embedding response did not include vector values');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return values.map(Number);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function embed(text, options = {}) {
|
|
101
176
|
if (process.env.THUMBGATE_VECTOR_STUB_EMBED === 'true') {
|
|
102
177
|
// Deterministic 384-dim unit vector: first element = 1.0, rest = 0.0
|
|
103
178
|
const stub = Array(384).fill(0);
|
|
104
179
|
stub[0] = 1.0;
|
|
105
180
|
return stub;
|
|
106
181
|
}
|
|
182
|
+
const geminiConfig = resolveGeminiEmbeddingConfig();
|
|
183
|
+
if (geminiConfig.enabled) {
|
|
184
|
+
try {
|
|
185
|
+
const vector = await embedWithGemini(text, options);
|
|
186
|
+
_lastEmbeddingProfile = {
|
|
187
|
+
generatedAt: new Date().toISOString(),
|
|
188
|
+
source: 'managed',
|
|
189
|
+
activeProfile: {
|
|
190
|
+
id: 'gemini',
|
|
191
|
+
model: geminiConfig.model,
|
|
192
|
+
outputDimensionality: geminiConfig.outputDimensionality,
|
|
193
|
+
task: options.task || geminiConfig.defaultTask,
|
|
194
|
+
rationale: 'Managed Gemini Embedding 2 path with task-specific query/document prefixes.',
|
|
195
|
+
},
|
|
196
|
+
fallbackUsed: false,
|
|
197
|
+
};
|
|
198
|
+
return vector;
|
|
199
|
+
} catch (geminiError) {
|
|
200
|
+
if (!geminiConfig.fallbackToLocal) {
|
|
201
|
+
throw geminiError;
|
|
202
|
+
}
|
|
203
|
+
console.warn(`Gemini embedding fallback: ${geminiError.message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
107
206
|
const { pipe, profile } = await getEmbeddingPipeline();
|
|
108
207
|
const output = await pipe(truncateForEmbedding(text, profile.activeProfile.maxChars), {
|
|
109
208
|
pooling: 'mean',
|
|
@@ -129,7 +228,11 @@ async function upsertFeedback(feedbackEvent) {
|
|
|
129
228
|
// Embed is pure CPU/model work (transformers.js or stub) — deterministic
|
|
130
229
|
// for a given input, so no retry is needed here. Retry wraps the table
|
|
131
230
|
// write below, which is the actual I/O failure surface.
|
|
132
|
-
const vector = await embed(textForEmbedding
|
|
231
|
+
const vector = await embed(textForEmbedding, {
|
|
232
|
+
kind: 'document',
|
|
233
|
+
task: 'code retrieval',
|
|
234
|
+
title: feedbackEvent.id || 'thumbgate feedback',
|
|
235
|
+
});
|
|
133
236
|
|
|
134
237
|
const record = {
|
|
135
238
|
id: feedbackEvent.id,
|
|
@@ -170,14 +273,20 @@ async function searchSimilar(queryText, limit = 5) {
|
|
|
170
273
|
const tableNames = await db.tableNames();
|
|
171
274
|
if (!tableNames.includes(TABLE_NAME)) return [];
|
|
172
275
|
|
|
173
|
-
const vector = await embed(queryText
|
|
276
|
+
const vector = await embed(queryText, {
|
|
277
|
+
kind: 'query',
|
|
278
|
+
task: 'code retrieval',
|
|
279
|
+
});
|
|
174
280
|
const table = await db.openTable(TABLE_NAME);
|
|
175
281
|
const results = await table.search(vector).limit(limit).toArray();
|
|
176
282
|
return results;
|
|
177
283
|
}
|
|
178
284
|
|
|
179
285
|
function getEmbeddingConfig() {
|
|
180
|
-
return
|
|
286
|
+
return {
|
|
287
|
+
...resolveEmbeddingProfile(),
|
|
288
|
+
managed: resolveGeminiEmbeddingConfig(),
|
|
289
|
+
};
|
|
181
290
|
}
|
|
182
291
|
|
|
183
292
|
function getLastEmbeddingProfile() {
|
|
@@ -195,6 +304,11 @@ function setLanceLoaderForTests(loader) {
|
|
|
195
304
|
_lancedb = null;
|
|
196
305
|
}
|
|
197
306
|
|
|
307
|
+
function setGeminiEmbedderForTests(loader) {
|
|
308
|
+
_geminiEmbedderForTests = loader;
|
|
309
|
+
_lastEmbeddingProfile = null;
|
|
310
|
+
}
|
|
311
|
+
|
|
198
312
|
module.exports = {
|
|
199
313
|
upsertFeedback,
|
|
200
314
|
searchSimilar,
|
|
@@ -203,5 +317,6 @@ module.exports = {
|
|
|
203
317
|
getLastEmbeddingProfile,
|
|
204
318
|
setPipelineLoaderForTests,
|
|
205
319
|
setLanceLoaderForTests,
|
|
320
|
+
setGeminiEmbedderForTests,
|
|
206
321
|
truncateForEmbedding,
|
|
207
322
|
};
|