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.
Files changed (62) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +3 -1
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +26 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/bin/cli.js +420 -1
  9. package/config/gate-templates.json +372 -0
  10. package/config/mcp-allowlists.json +25 -0
  11. package/config/model-candidates.json +59 -2
  12. package/config/model-tiers.json +4 -1
  13. package/package.json +79 -22
  14. package/public/compare.html +6 -0
  15. package/public/index.html +144 -11
  16. package/public/numbers.html +8 -8
  17. package/public/pro.html +22 -24
  18. package/scripts/agent-design-governance.js +211 -0
  19. package/scripts/agent-reasoning-traces.js +683 -0
  20. package/scripts/agent-reward-model.js +438 -0
  21. package/scripts/agent-stack-survival-audit.js +231 -0
  22. package/scripts/ai-engineering-stack-guardrails.js +256 -0
  23. package/scripts/billing.js +16 -4
  24. package/scripts/chatgpt-ads-readiness-pack.js +195 -0
  25. package/scripts/cli-schema.js +277 -0
  26. package/scripts/code-graph-guardrails.js +176 -0
  27. package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
  28. package/scripts/gemini-embedding-policy.js +198 -0
  29. package/scripts/inference-cache-policy.js +39 -0
  30. package/scripts/judge-reward-function.js +396 -0
  31. package/scripts/llm-behavior-monitor.js +251 -0
  32. package/scripts/long-running-agent-context-guardrails.js +176 -0
  33. package/scripts/multimodal-retrieval-plan.js +31 -11
  34. package/scripts/oss-pr-opportunity-scout.js +240 -0
  35. package/scripts/proactive-agent-eval-guardrails.js +230 -0
  36. package/scripts/profile-router.js +5 -4
  37. package/scripts/prompting-operating-system.js +273 -0
  38. package/scripts/proxy-pointer-rag-guardrails.js +189 -0
  39. package/scripts/rag-precision-guardrails.js +202 -0
  40. package/scripts/rate-limiter.js +1 -1
  41. package/scripts/reasoning-efficiency-guardrails.js +176 -0
  42. package/scripts/reward-hacking-guardrails.js +251 -0
  43. package/scripts/seo-gsd.js +1201 -11
  44. package/scripts/single-use-credential-gate.js +182 -0
  45. package/scripts/structured-prompt-driven.js +226 -0
  46. package/scripts/telemetry-analytics.js +31 -6
  47. package/scripts/tool-registry.js +92 -0
  48. package/scripts/upstream-contribution-engine.js +379 -0
  49. package/scripts/vector-store.js +119 -4
  50. package/src/api/server.js +333 -100
  51. package/scripts/agents-sdk-sandbox-plan.js +0 -57
  52. package/scripts/ai-org-governance.js +0 -98
  53. package/scripts/artifact-agent-plan.js +0 -81
  54. package/scripts/enterprise-agent-rollout.js +0 -34
  55. package/scripts/experience-replay-governance.js +0 -69
  56. package/scripts/inference-economics.js +0 -53
  57. package/scripts/knowledge-layer-plan.js +0 -108
  58. package/scripts/memory-store-governance.js +0 -60
  59. package/scripts/post-training-governance.js +0 -34
  60. package/scripts/production-agent-readiness.js +0 -40
  61. package/scripts/scaling-law-claims.js +0 -60
  62. package/scripts/student-consistent-training.js +0 -73
@@ -0,0 +1,176 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { listGateTemplates } = require('./gate-templates');
5
+
6
+ const CATEGORY = 'Long-Running Agent Context';
7
+
8
+ function normalizeBoolean(value) {
9
+ if (value === true) return true;
10
+ if (value === false || value === undefined || value === null) return false;
11
+ return /^(1|true|yes|on)$/i.test(String(value).trim());
12
+ }
13
+
14
+ function toNumber(value) {
15
+ if (value === undefined || value === null || value === '') return null;
16
+ const num = Number(value);
17
+ return Number.isFinite(num) ? num : null;
18
+ }
19
+
20
+ function normalizeOptions(options = {}) {
21
+ return {
22
+ workflow: String(options.workflow || options.name || 'long-running-agent').trim() || 'long-running-agent',
23
+ requestCount: toNumber(options['request-count'] || options.requests),
24
+ outputMb: toNumber(options['output-mb'] || options['output-megabytes']),
25
+ directorJournal: normalizeBoolean(options['director-journal'] || options.journal),
26
+ criticReview: normalizeBoolean(options['critic-review'] || options.critic),
27
+ criticTimeline: normalizeBoolean(options['critic-timeline'] || options.timeline),
28
+ credibilityScores: normalizeBoolean(options['credibility-scores'] || options.credibility),
29
+ conflicts: normalizeBoolean(options.conflicts || options['conflict-resolution']),
30
+ rawChatOnly: normalizeBoolean(options['raw-chat-only'] || options['chat-log-only']),
31
+ };
32
+ }
33
+
34
+ function templateApplicability(template, options) {
35
+ if (template.id === 'require-director-journal-for-long-running-agent') {
36
+ return options.rawChatOnly ||
37
+ !options.directorJournal ||
38
+ (options.requestCount !== null && options.requestCount >= 25) ||
39
+ (options.outputMb !== null && options.outputMb >= 1);
40
+ }
41
+ if (template.id === 'require-critic-review-for-agent-findings') {
42
+ return !options.criticReview || !options.credibilityScores;
43
+ }
44
+ if (template.id === 'checkpoint-critic-timeline-conflict-resolution') {
45
+ return options.conflicts || !options.criticTimeline;
46
+ }
47
+ return false;
48
+ }
49
+
50
+ function buildSignals(options) {
51
+ return [
52
+ contextWindowSignal(options),
53
+ truthFilterSignal(options),
54
+ timelineConflictSignal(options),
55
+ ].filter(Boolean);
56
+ }
57
+
58
+ function contextWindowSignal(options) {
59
+ const bloated = options.rawChatOnly ||
60
+ (options.requestCount !== null && options.requestCount >= 25) ||
61
+ (options.outputMb !== null && options.outputMb >= 1);
62
+ if (!bloated) return null;
63
+ return {
64
+ id: 'context_window_bloat',
65
+ label: 'Context-window bloat risk',
66
+ values: [
67
+ options.rawChatOnly ? 'raw chat history only' : null,
68
+ options.requestCount !== null ? `${options.requestCount} requests` : null,
69
+ options.outputMb !== null ? `${options.outputMb}MB output` : null,
70
+ ].filter(Boolean),
71
+ risk: 'raw message accumulation degrades coherence and wastes context budget',
72
+ };
73
+ }
74
+
75
+ function truthFilterSignal(options) {
76
+ if (options.directorJournal && options.criticReview && options.credibilityScores) return null;
77
+ return {
78
+ id: 'missing_truth_filter',
79
+ label: 'Missing structured truth filter',
80
+ values: [
81
+ options.directorJournal ? null : 'no director journal',
82
+ options.criticReview ? null : 'no critic review',
83
+ options.credibilityScores ? null : 'no credibility scores',
84
+ ].filter(Boolean),
85
+ risk: 'agent summaries can become shared truth without evidence inspection',
86
+ };
87
+ }
88
+
89
+ function timelineConflictSignal(options) {
90
+ if (!options.conflicts && options.criticTimeline) return null;
91
+ return {
92
+ id: 'timeline_conflict',
93
+ label: 'Timeline conflict risk',
94
+ values: [
95
+ options.conflicts ? 'known conflicts' : null,
96
+ options.criticTimeline ? null : 'no critic timeline',
97
+ ].filter(Boolean),
98
+ risk: 'long-lived memory can retain duplicates, stale claims, or contradictory findings',
99
+ };
100
+ }
101
+
102
+ function buildLongRunningAgentContextGuardrailsPlan(rawOptions = {}, templatesPath) {
103
+ const options = normalizeOptions(rawOptions);
104
+ const templates = listGateTemplates(templatesPath)
105
+ .filter((template) => template.category === CATEGORY)
106
+ .map((template) => ({
107
+ ...template,
108
+ recommended: templateApplicability(template, options),
109
+ }));
110
+ const signals = buildSignals(options);
111
+ const recommendedTemplates = templates.filter((template) => template.recommended);
112
+
113
+ return {
114
+ name: 'thumbgate-long-running-agent-context-guardrails',
115
+ status: recommendedTemplates.length > 0 ? 'actionable' : 'ready',
116
+ workflow: options.workflow,
117
+ summary: {
118
+ signalCount: signals.length,
119
+ templateCount: templates.length,
120
+ recommendedTemplateCount: recommendedTemplates.length,
121
+ requestCount: options.requestCount,
122
+ outputMb: options.outputMb,
123
+ },
124
+ signals,
125
+ templates,
126
+ nextActions: [
127
+ 'Persist a director journal with observations, decisions, questions, hypotheses, and open risks.',
128
+ 'Run critic review over expert findings and attach credibility scores before promoting them to shared memory.',
129
+ 'Maintain a critic timeline that deduplicates findings and resolves conflicts by strongest evidence.',
130
+ 'Block long-running agent handoffs that rely only on accumulated chat logs.',
131
+ ],
132
+ exampleCommand: 'npx thumbgate long-running-agent-context-guardrails --request-count=80 --output-mb=3 --raw-chat-only --json',
133
+ };
134
+ }
135
+
136
+ function formatLongRunningAgentContextGuardrailsPlan(report) {
137
+ const lines = [
138
+ '',
139
+ 'ThumbGate Long-Running Agent Context Guardrails',
140
+ '-'.repeat(50),
141
+ `Status : ${report.status}`,
142
+ `Workflow: ${report.workflow}`,
143
+ `Signals : ${report.summary.signalCount}`,
144
+ `Templates: ${report.summary.recommendedTemplateCount}/${report.summary.templateCount} recommended`,
145
+ ];
146
+
147
+ if (report.signals.length > 0) {
148
+ lines.push('', 'Detected context risk signals:');
149
+ for (const signal of report.signals) {
150
+ lines.push(` - ${signal.label}: ${signal.values.join(', ')}`);
151
+ lines.push(` Risk: ${signal.risk}`);
152
+ }
153
+ }
154
+
155
+ lines.push('', 'Recommended templates:');
156
+ const recommended = report.templates.filter((template) => template.recommended);
157
+ if (recommended.length === 0) {
158
+ lines.push(' - No long-running context risks were passed. Start with --request-count, --raw-chat-only, or critic/journal flags.');
159
+ } else {
160
+ for (const template of recommended) {
161
+ lines.push(` - ${template.id} [${template.defaultAction}]`);
162
+ lines.push(` ${template.roi}`);
163
+ }
164
+ }
165
+
166
+ lines.push('', 'Next actions:');
167
+ for (const action of report.nextActions) lines.push(` - ${action}`);
168
+ lines.push('', `Example: ${report.exampleCommand}`, '');
169
+ return `${lines.join('\n')}\n`;
170
+ }
171
+
172
+ module.exports = {
173
+ buildLongRunningAgentContextGuardrailsPlan,
174
+ formatLongRunningAgentContextGuardrailsPlan,
175
+ normalizeOptions,
176
+ };
@@ -1,7 +1,14 @@
1
1
  'use strict';
2
2
 
3
+ const {
4
+ buildGeminiEmbeddingRolloutPlan,
5
+ GEMINI_EMBEDDING_2_MODEL,
6
+ MULTIMODAL_LIMITS,
7
+ RECOMMENDED_OUTPUT_DIMENSIONS,
8
+ } = require('./gemini-embedding-policy');
9
+
3
10
  const DEFAULT_EVIDENCE_TYPES = ['screenshots', 'pdf_pages', 'proof_artifacts'];
4
- const DEFAULT_DIMS = [1024, 512, 256, 128, 64];
11
+ const DEFAULT_DIMS = [...RECOMMENDED_OUTPUT_DIMENSIONS, 512, 256, 128, 64];
5
12
 
6
13
  function clampInteger(value, { min, max, fallback }) {
7
14
  const parsed = Number(value);
@@ -24,8 +31,8 @@ function dimensionPlan({ corpusItems, maxEmbeddingDim }) {
24
31
  dim,
25
32
  estimatedFloat32Mb: Number(((corpusItems * dim * 4) / (1024 * 1024)).toFixed(2)),
26
33
  useWhen: dim >= 1024
27
- ? 'default quality pass for launch-critical retrieval'
28
- : 'cost-down pass when storage or latency dominates',
34
+ ? 'quality pass for launch-critical retrieval and holdout benchmarking'
35
+ : 'default cost-efficient Matryoshka pass for online agent recall',
29
36
  }));
30
37
  }
31
38
 
@@ -38,8 +45,8 @@ function buildMultimodalRetrievalPlan(args = {}) {
38
45
  });
39
46
  const maxEmbeddingDim = clampInteger(args.maxEmbeddingDim, {
40
47
  min: 64,
41
- max: 2048,
42
- fallback: 1024,
48
+ max: 3072,
49
+ fallback: 768,
43
50
  });
44
51
  const latencyBudgetMs = clampInteger(args.latencyBudgetMs, {
45
52
  min: 50,
@@ -49,20 +56,32 @@ function buildMultimodalRetrievalPlan(args = {}) {
49
56
  const useReranker = args.useReranker !== false;
50
57
  const goal = String(args.goal || 'retrieve visual proof for agent-governance decisions').trim();
51
58
  const dims = dimensionPlan({ corpusItems, maxEmbeddingDim });
52
- const defaultDim = dims.some((entry) => entry.dim === 1024) ? 1024 : dims[0].dim;
59
+ const defaultDim = dims.some((entry) => entry.dim === 768) ? 768 : dims[0].dim;
60
+ const gemini = buildGeminiEmbeddingRolloutPlan({
61
+ corpusItems,
62
+ outputDimensionality: defaultDim,
63
+ task: args.task || 'search result',
64
+ useBatchApi: args.useBatchApi,
65
+ });
53
66
 
54
67
  return {
55
- planVersion: '2026-04-20',
56
- sourcePattern: 'multimodal Sentence Transformers visual document retrieval',
68
+ planVersion: '2026-05-04',
69
+ sourcePattern: `${GEMINI_EMBEDDING_2_MODEL} agentic multimodal RAG`,
57
70
  goal,
58
71
  evidenceTypes,
59
72
  architecture: {
60
- stage1: 'Index screenshots, PDF pages, dashboard captures, and proof artifacts with a multimodal embedding model.',
73
+ stage1: 'Index screenshots, PDF pages, dashboard captures, and proof artifacts with Gemini Embedding 2 in one shared semantic space.',
61
74
  stage2: useReranker
62
- ? 'Rerank the top candidates with a multimodal cross-encoder before using evidence in a gate, PR, or sales proof claim.'
75
+ ? 'Rerank the top candidates with query/document similarity and hard-negative checks before using evidence in a gate, PR, or sales proof claim.'
63
76
  : 'Skip reranking for low-latency agent recall; require stronger holdout evaluation before shipping.',
64
77
  fallback: 'Keep text-only search as a fallback for code, logs, markdown, and plain policy docs.',
65
78
  },
79
+ geminiEmbedding2: {
80
+ model: GEMINI_EMBEDDING_2_MODEL,
81
+ modalityLimits: MULTIMODAL_LIMITS,
82
+ taskPrefixes: gemini.taskPrefixes,
83
+ batchApi: gemini.economics.batchApi,
84
+ },
66
85
  trainingData: {
67
86
  pilotSchema: ['query', 'image', 'negative_0'],
68
87
  hardNegativeStrategy: 'Pair each proof query with visually similar but wrong screenshots or PDF pages.',
@@ -82,7 +101,7 @@ function buildMultimodalRetrievalPlan(args = {}) {
82
101
  latencyBudgetMs,
83
102
  defaultEmbeddingDim: defaultDim,
84
103
  matryoshkaDimensions: dims,
85
- compressionPath: 'Use Matryoshka truncation first, then quantization only after holdout quality is stable.',
104
+ compressionPath: 'Use Gemini Embedding 2 Matryoshka truncation first; start at 768 dimensions and benchmark 1536 only when recall misses justify the storage.',
86
105
  },
87
106
  thumbgateUseCases: [
88
107
  'Find the exact screenshot or proof artifact behind a completion claim.',
@@ -93,6 +112,7 @@ function buildMultimodalRetrievalPlan(args = {}) {
93
112
  guardrails: [
94
113
  'Never promote visual retrieval results into claims without a linked artifact URL or local path.',
95
114
  'Keep the multimodal index read-only for agent recall; gate training and index rebuilds behind explicit workflow checks.',
115
+ 'Use task prefixes at both index time and query time so short agent questions retrieve long proof artifacts correctly.',
96
116
  'Evaluate retrieval on holdout screenshots/PDF pages before replacing text-only recall.',
97
117
  ],
98
118
  nextActions: [
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const DEFAULT_PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
8
+ const DEFAULT_OUTPUT_DIR = path.join(__dirname, '..', 'docs', 'marketing');
9
+ const KNOWN_REPOS = Object.freeze({
10
+ '@anthropic-ai/sdk': 'anthropics/anthropic-sdk-typescript',
11
+ '@google/genai': 'googleapis/js-genai',
12
+ '@huggingface/transformers': 'huggingface/transformers.js',
13
+ '@lancedb/lancedb': 'lancedb/lancedb',
14
+ 'apache-arrow': 'apache/arrow-js',
15
+ 'better-sqlite3': 'WiseLibs/better-sqlite3',
16
+ dotenv: 'motdotla/dotenv',
17
+ 'playwright-core': 'microsoft/playwright',
18
+ protobufjs: 'protobufjs/protobuf.js',
19
+ stripe: 'stripe/stripe-node',
20
+ '@changesets/changelog-github': 'changesets/changesets',
21
+ '@changesets/cli': 'changesets/changesets',
22
+ c8: 'bcoe/c8',
23
+ undici: 'nodejs/undici',
24
+ });
25
+
26
+ const BOUNTY_KEYWORDS = [
27
+ 'bug bounty',
28
+ 'bounty',
29
+ 'good first issue',
30
+ 'help wanted',
31
+ 'security',
32
+ 'repro',
33
+ 'regression',
34
+ 'docs',
35
+ 'typescript',
36
+ 'test failure',
37
+ ];
38
+
39
+ function normalizeText(value) {
40
+ if (value === undefined || value === null) return '';
41
+ return String(value).trim();
42
+ }
43
+
44
+ function splitList(value) {
45
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
46
+ return String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
47
+ }
48
+
49
+ function slugify(value) {
50
+ return normalizeText(value)
51
+ .toLowerCase()
52
+ .split('')
53
+ .map((char) => (/[a-z0-9]/.test(char) ? char : '-'))
54
+ .join('')
55
+ .split('-')
56
+ .filter(Boolean)
57
+ .join('-');
58
+ }
59
+
60
+ function loadPackage(packagePath = DEFAULT_PACKAGE_PATH) {
61
+ return JSON.parse(fs.readFileSync(packagePath, 'utf8'));
62
+ }
63
+
64
+ function dependencyNames(pkg = {}) {
65
+ return [
66
+ ...Object.keys(pkg.dependencies || {}),
67
+ ...Object.keys(pkg.devDependencies || {}),
68
+ ...Object.keys(pkg.optionalDependencies || {}),
69
+ ].sort((a, b) => a.localeCompare(b));
70
+ }
71
+
72
+ function repoFromDependency(name) {
73
+ return KNOWN_REPOS[name] || '';
74
+ }
75
+
76
+ function buildIssueSearchQueries(repo) {
77
+ return [
78
+ `repo:${repo} is:issue is:open label:"good first issue"`,
79
+ `repo:${repo} is:issue is:open label:"help wanted"`,
80
+ `repo:${repo} is:issue is:open bounty OR "bug bounty"`,
81
+ `repo:${repo} is:issue is:open regression test failure`,
82
+ ];
83
+ }
84
+
85
+ function scoreOpportunity(depName, repo, options = {}) {
86
+ let score = 0;
87
+ const reasons = [];
88
+ if (repo) {
89
+ score += 20;
90
+ reasons.push('known upstream repository');
91
+ }
92
+ if (/sdk|genai|stripe|playwright|lancedb|transformers|sqlite|undici/i.test(depName)) {
93
+ score += 20;
94
+ reasons.push('high product adjacency for agent tooling');
95
+ }
96
+ if (/anthropic|google|huggingface|stripe|microsoft|nodejs/i.test(repo)) {
97
+ score += 15;
98
+ reasons.push('large ecosystem visibility');
99
+ }
100
+ if (options.includeBounties) {
101
+ score += 10;
102
+ reasons.push('bounty search enabled');
103
+ }
104
+ if (/docs|changelog|dotenv|c8/i.test(depName)) {
105
+ score += 8;
106
+ reasons.push('lower-risk contribution surface');
107
+ }
108
+ return { score, reasons };
109
+ }
110
+
111
+ function buildOpportunity(depName, options = {}) {
112
+ const repo = repoFromDependency(depName);
113
+ const scoring = scoreOpportunity(depName, repo, options);
114
+ const repoUrl = repo ? `https://github.com/${repo}` : '';
115
+ return {
116
+ id: slugify(`${depName}-${repo || 'unknown'}`),
117
+ dependency: depName,
118
+ repo,
119
+ repoUrl,
120
+ score: scoring.score,
121
+ reasons: scoring.reasons,
122
+ issueSearchQueries: repo ? buildIssueSearchQueries(repo) : [],
123
+ bountyQueries: repo ? [
124
+ `repo:${repo} is:issue is:open "bug bounty"`,
125
+ `repo:${repo} is:issue is:open bounty security`,
126
+ ] : [],
127
+ safeFixLanes: [
128
+ 'reproduce issue locally before claiming it is fixed',
129
+ 'prefer docs, tests, typed edge cases, and minimal bug fixes',
130
+ 'open one focused PR per issue after reading contribution guidelines',
131
+ 'include ThumbGate only as a transparent proof note when relevant, never as hidden promotion',
132
+ ],
133
+ prReadinessGates: [
134
+ 'issue linked or maintainer pain clearly documented',
135
+ 'local reproduction or failing test captured',
136
+ 'fix is minimal and scoped to the issue',
137
+ 'tests or verification output attached',
138
+ 'no bounty, security, or maintainer-policy claim without source link',
139
+ ],
140
+ 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.`
142
+ : '',
143
+ };
144
+ }
145
+
146
+ function buildOssPrOpportunityScoutPlan(rawOptions = {}) {
147
+ const packagePath = normalizeText(rawOptions.packagePath || rawOptions['package-path']) || DEFAULT_PACKAGE_PATH;
148
+ const pkg = loadPackage(packagePath);
149
+ const explicitDeps = splitList(rawOptions.dependencies || rawOptions.deps);
150
+ const includeBounties = rawOptions.includeBounties !== false && rawOptions['include-bounties'] !== false;
151
+ const maxRepos = Math.max(1, Number.parseInt(String(rawOptions.maxRepos || rawOptions['max-repos'] || 12), 10) || 12);
152
+ const deps = explicitDeps.length ? explicitDeps : dependencyNames(pkg);
153
+ const opportunities = deps
154
+ .map((dep) => buildOpportunity(dep, { includeBounties }))
155
+ .filter((opportunity) => opportunity.repo)
156
+ .sort((left, right) => right.score - left.score || left.dependency.localeCompare(right.dependency))
157
+ .slice(0, maxRepos);
158
+
159
+ return {
160
+ name: 'thumbgate-oss-pr-opportunity-scout',
161
+ packagePath,
162
+ generatedAt: new Date().toISOString(),
163
+ status: opportunities.length ? 'ready_to_scout' : 'needs_repo_mapping',
164
+ summary: {
165
+ dependencyCount: deps.length,
166
+ mappedRepos: opportunities.length,
167
+ includeBounties,
168
+ topRepos: opportunities.slice(0, 5).map((item) => item.repo),
169
+ },
170
+ searchProtocol: {
171
+ issueLabels: ['good first issue', 'help wanted', 'bug', 'regression', 'documentation', 'security'],
172
+ bountyKeywords: BOUNTY_KEYWORDS,
173
+ antiSpamRule: 'Do not open a PR unless the issue is reproduced, the fix is minimal, and verification output is attached.',
174
+ promotionRule: 'Mention ThumbGate only as the toolchain context or proof discipline, not as unrelated advertising.',
175
+ },
176
+ opportunities,
177
+ automationCommands: [
178
+ 'gh issue list --repo <owner/repo> --label "good first issue" --state open',
179
+ 'gh issue list --repo <owner/repo> --search "bounty OR bug bounty OR regression"',
180
+ 'gh repo fork <owner/repo> --clone',
181
+ 'npx thumbgate require-evidence-for-claim --claim "fix is ready" before opening the PR',
182
+ ],
183
+ marketingAngle: {
184
+ headline: 'ThumbGate promotes itself by shipping proof-backed fixes, not drive-by ads.',
185
+ subhead: 'Find upstream repos we actually use, fix real issues with tests, and let maintainers see the agent-governance workflow in the PR evidence.',
186
+ replyDraft: 'This is a strong promotion loop as long as it is gated: use repos ThumbGate really depends on, fix issues maintainers already care about, attach reproduction plus tests, and make ThumbGate visible through the quality of the PR rather than a pitch.',
187
+ },
188
+ };
189
+ }
190
+
191
+ function formatOssPrOpportunityScoutPlan(report) {
192
+ const lines = [
193
+ '',
194
+ 'ThumbGate OSS PR Opportunity Scout',
195
+ '-'.repeat(35),
196
+ `Status : ${report.status}`,
197
+ `Package : ${report.packagePath}`,
198
+ `Mapped repos: ${report.summary.mappedRepos}`,
199
+ '',
200
+ 'Top opportunities:',
201
+ ];
202
+ for (const opportunity of report.opportunities.slice(0, 10)) {
203
+ lines.push(` - ${opportunity.repo} (${opportunity.dependency}) score=${opportunity.score}`);
204
+ lines.push(` Search: ${opportunity.issueSearchQueries[0]}`);
205
+ }
206
+ lines.push('', 'PR gates:');
207
+ for (const gate of report.searchProtocol.antiSpamRule ? [report.searchProtocol.antiSpamRule] : []) {
208
+ lines.push(` - ${gate}`);
209
+ }
210
+ lines.push('', `Reply draft: ${report.marketingAngle.replyDraft}`, '');
211
+ return `${lines.join('\n')}\n`;
212
+ }
213
+
214
+ function writeOssPrOpportunityScoutPack(outputDir = DEFAULT_OUTPUT_DIR, options = {}) {
215
+ const report = buildOssPrOpportunityScoutPlan(options);
216
+ fs.mkdirSync(outputDir, { recursive: true });
217
+ const jsonPath = path.join(outputDir, 'oss-pr-opportunity-scout.json');
218
+ const markdownPath = path.join(outputDir, 'oss-pr-opportunity-scout.md');
219
+ fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
220
+ fs.writeFileSync(markdownPath, formatOssPrOpportunityScoutPlan(report));
221
+ return { report, jsonPath, markdownPath };
222
+ }
223
+
224
+ module.exports = {
225
+ KNOWN_REPOS,
226
+ buildIssueSearchQueries,
227
+ buildOpportunity,
228
+ buildOssPrOpportunityScoutPlan,
229
+ formatOssPrOpportunityScoutPlan,
230
+ writeOssPrOpportunityScoutPack,
231
+ };
232
+
233
+ function isCliInvocation(argv = process.argv) {
234
+ return Boolean(argv[1] && path.resolve(argv[1]) === __filename);
235
+ }
236
+
237
+ if (isCliInvocation()) {
238
+ const { jsonPath, markdownPath } = writeOssPrOpportunityScoutPack();
239
+ console.log(JSON.stringify({ jsonPath, markdownPath }, null, 2));
240
+ }