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.
Files changed (150) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +60 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +217 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +211 -8
  12. package/config/enforcement.json +59 -7
  13. package/config/evals/agent-safety-eval.json +338 -22
  14. package/config/gates/default.json +33 -0
  15. package/config/gates/routine.json +43 -0
  16. package/config/github-about.json +3 -3
  17. package/config/mcp-allowlists.json +4 -0
  18. package/config/merge-quality-checks.json +2 -1
  19. package/config/model-candidates.json +131 -0
  20. package/openapi/openapi.yaml +118 -2
  21. package/package.json +70 -51
  22. package/public/blog.html +7 -7
  23. package/public/codex-plugin.html +13 -7
  24. package/public/compare.html +29 -23
  25. package/public/dashboard.html +105 -12
  26. package/public/guide.html +28 -28
  27. package/public/index.html +233 -97
  28. package/public/learn.html +87 -20
  29. package/public/lessons.html +26 -2
  30. package/public/numbers.html +271 -0
  31. package/public/pro.html +89 -19
  32. package/scripts/agent-audit-trace.js +55 -0
  33. package/scripts/agent-memory-lifecycle.js +96 -0
  34. package/scripts/agent-readiness-plan.js +118 -0
  35. package/scripts/agentic-data-pipeline.js +21 -1
  36. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  37. package/scripts/ai-org-governance.js +98 -0
  38. package/scripts/ai-search-distribution.js +43 -0
  39. package/scripts/artifact-agent-plan.js +81 -0
  40. package/scripts/billing.js +27 -8
  41. package/scripts/cli-feedback.js +2 -1
  42. package/scripts/cli-schema.js +60 -5
  43. package/scripts/code-mode-mcp-plan.js +71 -0
  44. package/scripts/commercial-offer.js +1 -1
  45. package/scripts/context-engine.js +1 -2
  46. package/scripts/context-manager.js +4 -1
  47. package/scripts/contextfs.js +214 -32
  48. package/scripts/dashboard-render-spec.js +1 -1
  49. package/scripts/dashboard.js +275 -9
  50. package/scripts/decision-journal.js +13 -3
  51. package/scripts/document-workflow-governance.js +62 -0
  52. package/scripts/enterprise-agent-rollout.js +34 -0
  53. package/scripts/experience-replay-governance.js +69 -0
  54. package/scripts/export-hf-dataset.js +1 -1
  55. package/scripts/feedback-loop.js +141 -9
  56. package/scripts/feedback-to-rules.js +17 -23
  57. package/scripts/gates-engine.js +4 -6
  58. package/scripts/growth-campaigns.js +49 -0
  59. package/scripts/harness-selector.js +145 -1
  60. package/scripts/hybrid-supervisor-agent.js +64 -0
  61. package/scripts/inference-cache-policy.js +72 -0
  62. package/scripts/inference-economics.js +53 -0
  63. package/scripts/internal-agent-bootstrap.js +12 -2
  64. package/scripts/knowledge-layer-plan.js +108 -0
  65. package/scripts/lesson-canonical.js +181 -0
  66. package/scripts/lesson-db.js +71 -10
  67. package/scripts/lesson-inference.js +183 -44
  68. package/scripts/lesson-search.js +4 -1
  69. package/scripts/lesson-synthesis.js +23 -2
  70. package/scripts/llm-client.js +157 -26
  71. package/scripts/mailer/resend-mailer.js +112 -1
  72. package/scripts/mcp-transport-strategy.js +66 -0
  73. package/scripts/memory-store-governance.js +60 -0
  74. package/scripts/meta-agent-loop.js +7 -13
  75. package/scripts/model-access-eligibility.js +38 -0
  76. package/scripts/model-migration-readiness.js +55 -0
  77. package/scripts/native-messaging-audit.js +514 -0
  78. package/scripts/operational-integrity.js +96 -3
  79. package/scripts/otel-declarative-config.js +56 -0
  80. package/scripts/perplexity-client.js +1 -1
  81. package/scripts/post-training-governance.js +34 -0
  82. package/scripts/pr-manager.js +47 -7
  83. package/scripts/private-core-boundary.js +72 -0
  84. package/scripts/production-agent-readiness.js +40 -0
  85. package/scripts/profile-router.js +16 -1
  86. package/scripts/prompt-eval.js +564 -32
  87. package/scripts/prompt-programs.js +93 -0
  88. package/scripts/provider-action-normalizer.js +585 -0
  89. package/scripts/rule-validator.js +285 -0
  90. package/scripts/scaling-law-claims.js +60 -0
  91. package/scripts/security-scanner.js +1 -1
  92. package/scripts/self-distill-agent.js +7 -32
  93. package/scripts/seo-gsd.js +400 -43
  94. package/scripts/skill-rag-router.js +53 -0
  95. package/scripts/spec-gate.js +1 -1
  96. package/scripts/student-consistent-training.js +73 -0
  97. package/scripts/synthetic-data-provenance.js +98 -0
  98. package/scripts/task-context-result.js +81 -0
  99. package/scripts/telemetry-analytics.js +149 -0
  100. package/scripts/thompson-sampling.js +2 -2
  101. package/scripts/token-savings.js +7 -6
  102. package/scripts/token-tco.js +46 -0
  103. package/scripts/tool-registry.js +75 -3
  104. package/scripts/verification-loop.js +10 -1
  105. package/scripts/verifier-scoring.js +71 -0
  106. package/scripts/workflow-sentinel.js +284 -28
  107. package/scripts/workspace-agent-routines.js +118 -0
  108. package/skills/thumbgate/SKILL.md +1 -1
  109. package/src/api/server.js +434 -120
  110. package/.claude-plugin/README.md +0 -170
  111. package/adapters/README.md +0 -12
  112. package/scripts/analytics-report.js +0 -328
  113. package/scripts/autonomous-workflow.js +0 -377
  114. package/scripts/billing-setup.js +0 -109
  115. package/scripts/creator-campaigns.js +0 -239
  116. package/scripts/cross-encoder-reranker.js +0 -235
  117. package/scripts/daemon-manager.js +0 -108
  118. package/scripts/decision-trace.js +0 -354
  119. package/scripts/delegation-runtime.js +0 -896
  120. package/scripts/dispatch-brief.js +0 -159
  121. package/scripts/distribution-surfaces.js +0 -110
  122. package/scripts/feedback-history-distiller.js +0 -382
  123. package/scripts/funnel-analytics.js +0 -35
  124. package/scripts/history-distiller.js +0 -200
  125. package/scripts/hosted-job-launcher.js +0 -256
  126. package/scripts/intent-router.js +0 -392
  127. package/scripts/lesson-reranker.js +0 -263
  128. package/scripts/lesson-retrieval.js +0 -148
  129. package/scripts/managed-lesson-agent.js +0 -183
  130. package/scripts/operational-dashboard.js +0 -103
  131. package/scripts/operational-summary.js +0 -129
  132. package/scripts/operator-artifacts.js +0 -608
  133. package/scripts/optimize-context.js +0 -17
  134. package/scripts/org-dashboard.js +0 -206
  135. package/scripts/partner-orchestration.js +0 -146
  136. package/scripts/predictive-insights.js +0 -356
  137. package/scripts/pulse.js +0 -80
  138. package/scripts/reflector-agent.js +0 -221
  139. package/scripts/sales-pipeline.js +0 -681
  140. package/scripts/session-episode-store.js +0 -329
  141. package/scripts/session-health-sensor.js +0 -242
  142. package/scripts/session-report.js +0 -120
  143. package/scripts/swarm-coordinator.js +0 -81
  144. package/scripts/tool-kpi-tracker.js +0 -12
  145. package/scripts/webhook-delivery.js +0 -62
  146. package/scripts/workflow-sprint-intake.js +0 -475
  147. package/skills/agent-memory/SKILL.md +0 -97
  148. package/skills/solve-architecture-autonomy/SKILL.md +0 -17
  149. package/skills/solve-architecture-autonomy/tool.js +0 -33
  150. 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 };