thumbgate 1.14.1 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +60 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +217 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +211 -8
- package/config/enforcement.json +59 -7
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/default.json +33 -0
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +2 -1
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +70 -51
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +13 -7
- package/public/compare.html +29 -23
- package/public/dashboard.html +105 -12
- package/public/guide.html +28 -28
- package/public/index.html +233 -97
- package/public/learn.html +87 -20
- package/public/lessons.html +26 -2
- package/public/numbers.html +271 -0
- package/public/pro.html +89 -19
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-feedback.js +2 -1
- package/scripts/cli-schema.js +60 -5
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/commercial-offer.js +1 -1
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/contextfs.js +214 -32
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +141 -9
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +145 -1
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-canonical.js +181 -0
- package/scripts/lesson-db.js +71 -10
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/lesson-synthesis.js +23 -2
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/native-messaging-audit.js +514 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/pr-manager.js +47 -7
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/profile-router.js +16 -1
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/rule-validator.js +285 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +400 -43
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +75 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +434 -120
- package/.claude-plugin/README.md +0 -170
- package/adapters/README.md +0 -12
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- package/scripts/workflow-sprint-intake.js +0 -475
- package/skills/agent-memory/SKILL.md +0 -97
- package/skills/solve-architecture-autonomy/SKILL.md +0 -17
- package/skills/solve-architecture-autonomy/tool.js +0 -33
- package/skills/thumbgate-feedback/SKILL.md +0 -49
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { resolveHostedBillingConfig } = require('./hosted-config');
|
|
4
|
-
|
|
5
|
-
const DEFAULT_TEAM_SEAT_COUNT = 3;
|
|
6
|
-
const DEFAULT_TOP_CREATORS = 5;
|
|
7
|
-
const CREATOR_CHANNELS = [
|
|
8
|
-
'youtube',
|
|
9
|
-
'x',
|
|
10
|
-
'linkedin',
|
|
11
|
-
'instagram',
|
|
12
|
-
'threads',
|
|
13
|
-
'tiktok',
|
|
14
|
-
];
|
|
15
|
-
const CREATOR_CONTENT_SHAPES = [
|
|
16
|
-
'review',
|
|
17
|
-
'workflow_teardown',
|
|
18
|
-
'before_after_demo',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function normalizeText(value) {
|
|
22
|
-
if (value === undefined || value === null) return '';
|
|
23
|
-
return String(value).trim();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function normalizeCreatorHandle(value) {
|
|
27
|
-
return normalizeText(value)
|
|
28
|
-
.replace(/^@+/, '')
|
|
29
|
-
.toLowerCase();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function slugify(value) {
|
|
33
|
-
return normalizeText(value)
|
|
34
|
-
.toLowerCase()
|
|
35
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
36
|
-
.replace(/^-+|-+$/g, '');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function buildCreatorOfferCode(handle, motion = 'pro') {
|
|
40
|
-
const creatorCode = slugify(handle).toUpperCase() || 'CREATOR';
|
|
41
|
-
const motionCode = slugify(motion).toUpperCase() || 'PRO';
|
|
42
|
-
return `${creatorCode}-${motionCode}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function applyAttributionParams(url, attribution = {}) {
|
|
46
|
-
const params = {
|
|
47
|
-
utm_source: attribution.source,
|
|
48
|
-
utm_medium: attribution.utmMedium,
|
|
49
|
-
utm_campaign: attribution.utmCampaign,
|
|
50
|
-
utm_content: attribution.utmContent,
|
|
51
|
-
creator: attribution.creator,
|
|
52
|
-
community: attribution.community,
|
|
53
|
-
post_id: attribution.postId,
|
|
54
|
-
comment_id: attribution.commentId,
|
|
55
|
-
campaign_variant: attribution.campaignVariant,
|
|
56
|
-
offer_code: attribution.offerCode,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
for (const [key, value] of Object.entries(params)) {
|
|
60
|
-
const normalized = normalizeText(value);
|
|
61
|
-
if (normalized) {
|
|
62
|
-
url.searchParams.set(key, normalized);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return url;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function buildCreatorCampaignLinks(options = {}, runtimeConfig = resolveHostedBillingConfig({
|
|
69
|
-
requestOrigin: 'https://thumbgate-production.up.railway.app',
|
|
70
|
-
})) {
|
|
71
|
-
const creator = normalizeCreatorHandle(options.creator || options.handle);
|
|
72
|
-
if (!creator) {
|
|
73
|
-
throw new Error('buildCreatorCampaignLinks requires creator');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const source = slugify(options.source || options.platform || 'youtube');
|
|
77
|
-
if (!source) {
|
|
78
|
-
throw new Error('buildCreatorCampaignLinks requires source');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const campaign = slugify(options.campaign || `creator-${creator}-launch`);
|
|
82
|
-
const contentShape = slugify(options.contentShape || options.variant || 'review');
|
|
83
|
-
const community = normalizeText(options.community);
|
|
84
|
-
const postId = normalizeText(options.postId);
|
|
85
|
-
const commentId = normalizeText(options.commentId);
|
|
86
|
-
const offerCode = normalizeText(options.offerCode) || buildCreatorOfferCode(creator, options.motion || 'pro');
|
|
87
|
-
const teamSeatCount = Math.max(Number.parseInt(String(options.seatCount || DEFAULT_TEAM_SEAT_COUNT), 10) || DEFAULT_TEAM_SEAT_COUNT, DEFAULT_TEAM_SEAT_COUNT);
|
|
88
|
-
|
|
89
|
-
const attribution = {
|
|
90
|
-
creator,
|
|
91
|
-
source,
|
|
92
|
-
utmMedium: normalizeText(options.utmMedium) || 'creator_partnership',
|
|
93
|
-
utmCampaign: campaign,
|
|
94
|
-
utmContent: contentShape,
|
|
95
|
-
community,
|
|
96
|
-
postId,
|
|
97
|
-
commentId,
|
|
98
|
-
campaignVariant: contentShape,
|
|
99
|
-
offerCode,
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const landingUrl = applyAttributionParams(new URL('/', runtimeConfig.appOrigin), attribution).toString();
|
|
103
|
-
|
|
104
|
-
const proCheckoutUrl = applyAttributionParams(new URL('/checkout/pro', runtimeConfig.appOrigin), attribution);
|
|
105
|
-
proCheckoutUrl.searchParams.set('cta_id', 'pricing_pro');
|
|
106
|
-
proCheckoutUrl.searchParams.set('cta_placement', 'creator_partnership');
|
|
107
|
-
proCheckoutUrl.searchParams.set('plan_id', 'pro');
|
|
108
|
-
|
|
109
|
-
const teamCheckoutUrl = applyAttributionParams(new URL('/checkout/pro', runtimeConfig.appOrigin), attribution);
|
|
110
|
-
teamCheckoutUrl.searchParams.set('cta_id', 'pricing_team');
|
|
111
|
-
teamCheckoutUrl.searchParams.set('cta_placement', 'creator_partnership');
|
|
112
|
-
teamCheckoutUrl.searchParams.set('plan_id', 'team');
|
|
113
|
-
teamCheckoutUrl.searchParams.set('billing_cycle', 'monthly');
|
|
114
|
-
teamCheckoutUrl.searchParams.set('seat_count', String(teamSeatCount));
|
|
115
|
-
|
|
116
|
-
const sprintUrl = applyAttributionParams(new URL('/', runtimeConfig.appOrigin), attribution);
|
|
117
|
-
sprintUrl.hash = 'workflow-sprint-intake';
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
creator,
|
|
121
|
-
attribution,
|
|
122
|
-
links: {
|
|
123
|
-
landingUrl,
|
|
124
|
-
proCheckoutUrl: proCheckoutUrl.toString(),
|
|
125
|
-
teamCheckoutUrl: teamCheckoutUrl.toString(),
|
|
126
|
-
sprintUrl: sprintUrl.toString(),
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function getCounterValue(counter = {}, key) {
|
|
132
|
-
return Number(counter && counter[key]) || 0;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function summarizeCreatorPerformance(telemetry = null, billingSummary = null, options = {}) {
|
|
136
|
-
const topN = Math.max(Number.parseInt(String(options.topN || DEFAULT_TOP_CREATORS), 10) || DEFAULT_TOP_CREATORS, 1);
|
|
137
|
-
const creators = new Set();
|
|
138
|
-
const addKeys = (counter) => {
|
|
139
|
-
for (const key of Object.keys(counter || {})) {
|
|
140
|
-
const normalized = normalizeText(key);
|
|
141
|
-
if (normalized && normalized !== 'unknown') creators.add(normalized);
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
addKeys(telemetry && telemetry.visitors && telemetry.visitors.byCreator);
|
|
146
|
-
addKeys(telemetry && telemetry.ctas && telemetry.ctas.byCreator);
|
|
147
|
-
addKeys(telemetry && telemetry.ctas && telemetry.ctas.checkoutStartsByCreator);
|
|
148
|
-
addKeys(billingSummary && billingSummary.attribution && billingSummary.attribution.acquisitionByCreator);
|
|
149
|
-
addKeys(billingSummary && billingSummary.attribution && billingSummary.attribution.paidByCreator);
|
|
150
|
-
addKeys(billingSummary && billingSummary.attribution && billingSummary.attribution.bookedRevenueByCreatorCents);
|
|
151
|
-
addKeys(billingSummary && billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.byCreator);
|
|
152
|
-
addKeys(billingSummary && billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.byCreator);
|
|
153
|
-
|
|
154
|
-
return Array.from(creators)
|
|
155
|
-
.map((creator) => {
|
|
156
|
-
const visitors = getCounterValue(telemetry && telemetry.visitors && telemetry.visitors.byCreator, creator);
|
|
157
|
-
const ctaClicks = getCounterValue(telemetry && telemetry.ctas && telemetry.ctas.byCreator, creator);
|
|
158
|
-
const checkoutStarts = getCounterValue(telemetry && telemetry.ctas && telemetry.ctas.checkoutStartsByCreator, creator);
|
|
159
|
-
const acquisitions = getCounterValue(billingSummary && billingSummary.attribution && billingSummary.attribution.acquisitionByCreator, creator);
|
|
160
|
-
const paidOrders = getCounterValue(billingSummary && billingSummary.attribution && billingSummary.attribution.paidByCreator, creator);
|
|
161
|
-
const bookedRevenueCents = getCounterValue(billingSummary && billingSummary.attribution && billingSummary.attribution.bookedRevenueByCreatorCents, creator);
|
|
162
|
-
const sprintLeads = getCounterValue(
|
|
163
|
-
billingSummary && billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.byCreator,
|
|
164
|
-
creator
|
|
165
|
-
);
|
|
166
|
-
const qualifiedSprintLeads = getCounterValue(
|
|
167
|
-
billingSummary && billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.byCreator,
|
|
168
|
-
creator
|
|
169
|
-
);
|
|
170
|
-
return {
|
|
171
|
-
creator,
|
|
172
|
-
visitors,
|
|
173
|
-
ctaClicks,
|
|
174
|
-
checkoutStarts,
|
|
175
|
-
acquisitions,
|
|
176
|
-
paidOrders,
|
|
177
|
-
bookedRevenueCents,
|
|
178
|
-
sprintLeads,
|
|
179
|
-
qualifiedSprintLeads,
|
|
180
|
-
};
|
|
181
|
-
})
|
|
182
|
-
.sort((left, right) => (
|
|
183
|
-
right.bookedRevenueCents - left.bookedRevenueCents ||
|
|
184
|
-
right.paidOrders - left.paidOrders ||
|
|
185
|
-
right.qualifiedSprintLeads - left.qualifiedSprintLeads ||
|
|
186
|
-
right.checkoutStarts - left.checkoutStarts ||
|
|
187
|
-
right.visitors - left.visitors ||
|
|
188
|
-
left.creator.localeCompare(right.creator)
|
|
189
|
-
))
|
|
190
|
-
.slice(0, topN);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function parseArgs(argv = []) {
|
|
194
|
-
const options = {};
|
|
195
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
196
|
-
const token = argv[index];
|
|
197
|
-
if (!token.startsWith('--')) continue;
|
|
198
|
-
const [key, inlineValue] = token.slice(2).split('=');
|
|
199
|
-
if (inlineValue !== undefined) {
|
|
200
|
-
options[key] = inlineValue;
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
const next = argv[index + 1];
|
|
204
|
-
if (next && !next.startsWith('--')) {
|
|
205
|
-
options[key] = next;
|
|
206
|
-
index += 1;
|
|
207
|
-
} else {
|
|
208
|
-
options[key] = true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return options;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function runCli(argv = process.argv.slice(2), io = {}) {
|
|
215
|
-
const log = io.log || console.log;
|
|
216
|
-
const error = io.error || console.error;
|
|
217
|
-
const exit = io.exit || process.exit;
|
|
218
|
-
try {
|
|
219
|
-
const options = parseArgs(argv);
|
|
220
|
-
const result = buildCreatorCampaignLinks(options);
|
|
221
|
-
log(JSON.stringify(result, null, 2));
|
|
222
|
-
} catch (err) {
|
|
223
|
-
error(err.message);
|
|
224
|
-
exit(1);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
module.exports = {
|
|
229
|
-
CREATOR_CHANNELS,
|
|
230
|
-
CREATOR_CONTENT_SHAPES,
|
|
231
|
-
buildCreatorCampaignLinks,
|
|
232
|
-
buildCreatorOfferCode,
|
|
233
|
-
normalizeCreatorHandle,
|
|
234
|
-
summarizeCreatorPerformance,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
if (require.main === module) {
|
|
238
|
-
runCli();
|
|
239
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Cross-Encoder Reranker for ThumbGate lesson retrieval.
|
|
6
|
-
*
|
|
7
|
-
* Two-stage retrieval:
|
|
8
|
-
* Stage 1: Fast candidate retrieval (existing bigram Jaccard + keyword matching)
|
|
9
|
-
* Stage 2: Cross-encoder reranking scores query-document pairs jointly
|
|
10
|
-
*
|
|
11
|
-
* The cross-encoder evaluates the query AND each lesson together (not independently),
|
|
12
|
-
* catching false positives that keyword/vector search misses.
|
|
13
|
-
*
|
|
14
|
-
* Architecture reference: "Advanced RAG Retrieval: Cross-Encoders & Reranking"
|
|
15
|
-
* (Towards Data Science, April 2026)
|
|
16
|
-
*
|
|
17
|
-
* When LLM is available (ANTHROPIC_API_KEY), uses Claude as the cross-encoder.
|
|
18
|
-
* Falls back to enhanced heuristic scoring when LLM is unavailable.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const { retrieveRelevantLessons, scoreRelevance, buildActionSignature } = require('./lesson-retrieval');
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Heuristic cross-encoder: scores a (query, document) pair jointly.
|
|
25
|
-
* Unlike bi-encoder (independent embeddings), this examines the pair together
|
|
26
|
-
* to find semantic relationships that keyword matching misses.
|
|
27
|
-
*/
|
|
28
|
-
function heuristicCrossEncode(query, document) {
|
|
29
|
-
const queryLower = (query || '').toLowerCase();
|
|
30
|
-
const docLower = (document || '').toLowerCase();
|
|
31
|
-
|
|
32
|
-
let score = 0;
|
|
33
|
-
|
|
34
|
-
// 1. Exact substring containment (strongest signal)
|
|
35
|
-
if (queryLower.length > 3 && docLower.length > 3 &&
|
|
36
|
-
(docLower.includes(queryLower) || queryLower.includes(docLower))) {
|
|
37
|
-
score += 0.9;
|
|
38
|
-
return Math.min(score, 1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 2. Shared noun phrases (not just tokens — consecutive word pairs)
|
|
42
|
-
const queryPhrases = extractPhrases(queryLower);
|
|
43
|
-
const docPhrases = extractPhrases(docLower);
|
|
44
|
-
const phraseOverlap = queryPhrases.filter((p) => docPhrases.includes(p));
|
|
45
|
-
score += Math.min(phraseOverlap.length * 0.15, 0.5);
|
|
46
|
-
|
|
47
|
-
// 3. Semantic category matching
|
|
48
|
-
const categories = {
|
|
49
|
-
destructive: ['delete', 'remove', 'drop', 'destroy', 'wipe', 'truncate', 'rm -rf', 'force-push', 'reset --hard'],
|
|
50
|
-
git: ['git', 'push', 'pull', 'merge', 'rebase', 'branch', 'commit', 'checkout', 'stash'],
|
|
51
|
-
database: ['sql', 'query', 'table', 'migration', 'schema', 'database', 'insert', 'update', 'select'],
|
|
52
|
-
deploy: ['deploy', 'release', 'publish', 'railway', 'vercel', 'heroku', 'npm publish'],
|
|
53
|
-
security: ['secret', 'token', 'api key', 'password', 'credential', 'env', '.env', 'pem'],
|
|
54
|
-
file: ['edit', 'write', 'create', 'modify', 'config', 'package.json', 'readme'],
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
for (const [, terms] of Object.entries(categories)) {
|
|
58
|
-
const queryHit = terms.some((t) => queryLower.includes(t));
|
|
59
|
-
const docHit = terms.some((t) => docLower.includes(t));
|
|
60
|
-
if (queryHit && docHit) {
|
|
61
|
-
score += 0.25;
|
|
62
|
-
break; // Only count strongest category match
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 4. Action-target alignment (e.g., "git push" in query matches "push to main" in doc)
|
|
67
|
-
const queryVerbs = extractVerbs(queryLower);
|
|
68
|
-
const docVerbs = extractVerbs(docLower);
|
|
69
|
-
const verbOverlap = queryVerbs.filter((v) => docVerbs.includes(v));
|
|
70
|
-
score += Math.min(verbOverlap.length * 0.1, 0.3);
|
|
71
|
-
|
|
72
|
-
// 5. Negation alignment (both about what NOT to do)
|
|
73
|
-
const queryNegated = /\b(don'?t|never|avoid|block|prevent|stop)\b/.test(queryLower);
|
|
74
|
-
const docNegated = /\b(don'?t|never|avoid|block|prevent|stop)\b/.test(docLower);
|
|
75
|
-
if (queryNegated && docNegated) score += 0.1;
|
|
76
|
-
|
|
77
|
-
return Math.min(score, 1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* LLM cross-encoder: uses Claude to score relevance of query-document pairs.
|
|
82
|
-
* More accurate but requires API key and costs tokens.
|
|
83
|
-
*/
|
|
84
|
-
async function llmCrossEncode(query, documents) {
|
|
85
|
-
const { isAvailable, callClaude, MODELS } = require('./llm-client');
|
|
86
|
-
if (!isAvailable()) return null;
|
|
87
|
-
|
|
88
|
-
const docList = documents
|
|
89
|
-
.map((d, i) => `[${i}] ${(d.title || '').slice(0, 100)} | ${(d.content || '').slice(0, 200)}`)
|
|
90
|
-
.join('\n');
|
|
91
|
-
|
|
92
|
-
const prompt = `You are a relevance scoring engine. Given a query and a list of documents, score each document's relevance to the query from 0.0 (irrelevant) to 1.0 (highly relevant).
|
|
93
|
-
|
|
94
|
-
Query: "${query.slice(0, 300)}"
|
|
95
|
-
|
|
96
|
-
Documents:
|
|
97
|
-
${docList}
|
|
98
|
-
|
|
99
|
-
Return ONLY a JSON array of scores, one per document. Example: [0.9, 0.2, 0.7, 0.1, 0.5]
|
|
100
|
-
No other text.`;
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const raw = await callClaude({
|
|
104
|
-
systemPrompt: 'You are a relevance scoring engine. Return only JSON arrays of numbers.',
|
|
105
|
-
userPrompt: prompt,
|
|
106
|
-
model: MODELS.FAST,
|
|
107
|
-
maxTokens: 256,
|
|
108
|
-
});
|
|
109
|
-
const scores = JSON.parse(raw);
|
|
110
|
-
if (Array.isArray(scores) && scores.length === documents.length) {
|
|
111
|
-
return scores.map((s) => Math.max(0, Math.min(1, Number(s) || 0)));
|
|
112
|
-
}
|
|
113
|
-
} catch { /* fall back to heuristic */ }
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Two-stage retrieval with cross-encoder reranking.
|
|
119
|
-
*
|
|
120
|
-
* Stage 1: Retrieve top N candidates using existing keyword + bigram matching
|
|
121
|
-
* Stage 2: Rerank candidates using cross-encoder (LLM or heuristic)
|
|
122
|
-
* Return top K results by cross-encoder score
|
|
123
|
-
*/
|
|
124
|
-
async function retrieveWithReranking(toolName, actionContext, options = {}) {
|
|
125
|
-
const {
|
|
126
|
-
candidateCount = 20,
|
|
127
|
-
maxResults = 5,
|
|
128
|
-
useLLM = false,
|
|
129
|
-
feedbackDir,
|
|
130
|
-
} = options;
|
|
131
|
-
|
|
132
|
-
// Stage 1: Fast candidate retrieval (existing system)
|
|
133
|
-
const candidates = retrieveRelevantLessons(toolName, actionContext, {
|
|
134
|
-
maxResults: candidateCount,
|
|
135
|
-
feedbackDir,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (candidates.length === 0) return [];
|
|
139
|
-
if (candidates.length <= maxResults) return candidates;
|
|
140
|
-
|
|
141
|
-
const query = `${toolName || ''} ${actionContext || ''}`.trim();
|
|
142
|
-
|
|
143
|
-
// Stage 2: Cross-encoder reranking
|
|
144
|
-
let rerankedScores;
|
|
145
|
-
|
|
146
|
-
if (useLLM) {
|
|
147
|
-
rerankedScores = await llmCrossEncode(query, candidates);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Fall back to heuristic cross-encoder if LLM unavailable or failed
|
|
151
|
-
if (!rerankedScores) {
|
|
152
|
-
rerankedScores = candidates.map((c) => {
|
|
153
|
-
const docText = `${c.title || ''} ${c.content || ''}`;
|
|
154
|
-
return heuristicCrossEncode(query, docText);
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Combine original relevance score with cross-encoder score
|
|
159
|
-
// Weight: 40% original, 60% cross-encoder (cross-encoder is more precise)
|
|
160
|
-
const reranked = candidates.map((c, i) => ({
|
|
161
|
-
...c,
|
|
162
|
-
crossEncoderScore: rerankedScores[i],
|
|
163
|
-
combinedScore: c.relevanceScore * 0.4 + rerankedScores[i] * 0.6,
|
|
164
|
-
}));
|
|
165
|
-
|
|
166
|
-
return reranked
|
|
167
|
-
.sort((a, b) => b.combinedScore - a.combinedScore)
|
|
168
|
-
.slice(0, maxResults);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Synchronous version for use in PreToolUse hooks (cannot be async).
|
|
173
|
-
*/
|
|
174
|
-
function retrieveWithRerankingSync(toolName, actionContext, options = {}) {
|
|
175
|
-
const {
|
|
176
|
-
candidateCount = 20,
|
|
177
|
-
maxResults = 5,
|
|
178
|
-
feedbackDir,
|
|
179
|
-
} = options;
|
|
180
|
-
|
|
181
|
-
const candidates = retrieveRelevantLessons(toolName, actionContext, {
|
|
182
|
-
maxResults: candidateCount,
|
|
183
|
-
feedbackDir,
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
if (candidates.length === 0) return [];
|
|
187
|
-
if (candidates.length <= maxResults) return candidates;
|
|
188
|
-
|
|
189
|
-
const query = `${toolName || ''} ${actionContext || ''}`.trim();
|
|
190
|
-
|
|
191
|
-
const rerankedScores = candidates.map((c) => {
|
|
192
|
-
const docText = `${c.title || ''} ${c.content || ''}`;
|
|
193
|
-
return heuristicCrossEncode(query, docText);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const reranked = candidates.map((c, i) => ({
|
|
197
|
-
...c,
|
|
198
|
-
crossEncoderScore: rerankedScores[i],
|
|
199
|
-
combinedScore: c.relevanceScore * 0.4 + rerankedScores[i] * 0.6,
|
|
200
|
-
}));
|
|
201
|
-
|
|
202
|
-
return reranked
|
|
203
|
-
.sort((a, b) => b.combinedScore - a.combinedScore)
|
|
204
|
-
.slice(0, maxResults);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// --- Utility functions ---
|
|
208
|
-
|
|
209
|
-
function extractPhrases(text) {
|
|
210
|
-
const words = text.split(/\s+/).filter((w) => w.length > 2);
|
|
211
|
-
const phrases = [];
|
|
212
|
-
for (let i = 0; i < words.length - 1; i++) {
|
|
213
|
-
phrases.push(`${words[i]} ${words[i + 1]}`);
|
|
214
|
-
}
|
|
215
|
-
return phrases;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function extractVerbs(text) {
|
|
219
|
-
const verbPatterns = [
|
|
220
|
-
'push', 'pull', 'merge', 'delete', 'create', 'edit', 'write', 'read',
|
|
221
|
-
'deploy', 'install', 'remove', 'run', 'execute', 'build', 'test',
|
|
222
|
-
'commit', 'rebase', 'reset', 'drop', 'truncate', 'migrate', 'publish',
|
|
223
|
-
'block', 'allow', 'approve', 'deny', 'warn', 'log',
|
|
224
|
-
];
|
|
225
|
-
return verbPatterns.filter((v) => text.includes(v));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
module.exports = {
|
|
229
|
-
heuristicCrossEncode,
|
|
230
|
-
llmCrossEncode,
|
|
231
|
-
retrieveWithReranking,
|
|
232
|
-
retrieveWithRerankingSync,
|
|
233
|
-
extractPhrases,
|
|
234
|
-
extractVerbs,
|
|
235
|
-
};
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
|
|
9
|
-
const LABEL = 'com.thumbgate.daemon';
|
|
10
|
-
const PLIST_PATH = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
11
|
-
const NODE_PATH = process.execPath;
|
|
12
|
-
const GATEWAY_BIN = path.join(__dirname, '..', 'bin', 'cli.js');
|
|
13
|
-
const LOG_DIR = path.join(os.homedir(), '.thumbgate', 'logs');
|
|
14
|
-
|
|
15
|
-
function generatePlist() {
|
|
16
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
17
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
18
|
-
<plist version="1.0">
|
|
19
|
-
<dict>
|
|
20
|
-
<key>Label</key>
|
|
21
|
-
<string>${LABEL}</string>
|
|
22
|
-
<key>ProgramArguments</key>
|
|
23
|
-
<array>
|
|
24
|
-
<string>${NODE_PATH}</string>
|
|
25
|
-
<string>${GATEWAY_BIN}</string>
|
|
26
|
-
<string>serve</string>
|
|
27
|
-
</array>
|
|
28
|
-
<key>RunAtLoad</key>
|
|
29
|
-
<true/>
|
|
30
|
-
<key>KeepAlive</key>
|
|
31
|
-
<dict>
|
|
32
|
-
<key>SuccessfulExit</key>
|
|
33
|
-
<false/>
|
|
34
|
-
</dict>
|
|
35
|
-
<key>StandardOutPath</key>
|
|
36
|
-
<string>${LOG_DIR}/daemon-stdout.log</string>
|
|
37
|
-
<key>StandardErrorPath</key>
|
|
38
|
-
<string>${LOG_DIR}/daemon-stderr.log</string>
|
|
39
|
-
<key>EnvironmentVariables</key>
|
|
40
|
-
<dict>
|
|
41
|
-
<key>PATH</key>
|
|
42
|
-
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
43
|
-
<key>HOME</key>
|
|
44
|
-
<string>${os.homedir()}</string>
|
|
45
|
-
</dict>
|
|
46
|
-
<key>ThrottleInterval</key>
|
|
47
|
-
<integer>10</integer>
|
|
48
|
-
</dict>
|
|
49
|
-
</plist>`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function manageDaemon(subCommand) {
|
|
53
|
-
switch (subCommand) {
|
|
54
|
-
case 'install': {
|
|
55
|
-
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
56
|
-
fs.writeFileSync(PLIST_PATH, generatePlist(), 'utf8');
|
|
57
|
-
try {
|
|
58
|
-
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'pipe' });
|
|
59
|
-
} catch { /* not loaded */ }
|
|
60
|
-
execSync(`launchctl load "${PLIST_PATH}"`, { stdio: 'inherit' });
|
|
61
|
-
console.log(`✅ Daemon installed and started: ${LABEL}`);
|
|
62
|
-
console.log(` Plist: ${PLIST_PATH}`);
|
|
63
|
-
console.log(` Logs: ${LOG_DIR}/daemon-*.log`);
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case 'uninstall': {
|
|
68
|
-
try {
|
|
69
|
-
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'pipe' });
|
|
70
|
-
} catch { /* not loaded */ }
|
|
71
|
-
if (fs.existsSync(PLIST_PATH)) {
|
|
72
|
-
fs.unlinkSync(PLIST_PATH);
|
|
73
|
-
console.log(`✅ Daemon uninstalled: ${LABEL}`);
|
|
74
|
-
} else {
|
|
75
|
-
console.log('ℹ️ Daemon not installed');
|
|
76
|
-
}
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
case 'restart': {
|
|
81
|
-
try {
|
|
82
|
-
execSync(`launchctl unload "${PLIST_PATH}" 2>/dev/null`, { stdio: 'pipe' });
|
|
83
|
-
execSync(`launchctl load "${PLIST_PATH}"`, { stdio: 'inherit' });
|
|
84
|
-
console.log(`✅ Daemon restarted: ${LABEL}`);
|
|
85
|
-
} catch (e) {
|
|
86
|
-
console.error(`❌ Restart failed: ${e.message}`);
|
|
87
|
-
}
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
case 'status':
|
|
92
|
-
default: {
|
|
93
|
-
try {
|
|
94
|
-
const output = execSync(`launchctl list 2>/dev/null | grep "${LABEL}"`, { encoding: 'utf8' });
|
|
95
|
-
const parts = output.trim().split(/\s+/);
|
|
96
|
-
const pid = parts[0] === '-' ? 'idle' : `PID ${parts[0]}`;
|
|
97
|
-
const status = parts[1] === '0' ? 'OK' : `exit ${parts[1]}`;
|
|
98
|
-
console.log(`🔧 ThumbGate Daemon: ${pid} (${status})`);
|
|
99
|
-
console.log(` Plist: ${PLIST_PATH}`);
|
|
100
|
-
} catch {
|
|
101
|
-
console.log('ℹ️ Daemon not installed. Run: thumbgate daemon install');
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
module.exports = { manageDaemon, LABEL, PLIST_PATH };
|