thumbgate 1.27.17 → 1.27.18

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -221
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +99 -229
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -145
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. package/scripts/visitor-journey.js +0 -172
@@ -1,12 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- const path = require('node:path');
5
- const os = require('node:os');
6
- const {
7
- getAggregateStatuslineCachePath,
8
- shouldAggregateFeedback,
9
- } = require('./feedback-aggregate');
4
+ const path = require('path');
10
5
  const {
11
6
  listFeedbackArtifactPaths,
12
7
  resolveFeedbackDir,
@@ -17,28 +12,18 @@ function unique(values = []) {
17
12
  return [...new Set(values.filter(Boolean).map((value) => path.resolve(value)))];
18
13
  }
19
14
 
20
- function hasIsolatedTempFeedbackDir(env = process.env) {
21
- if (!env.THUMBGATE_FEEDBACK_DIR) return false;
22
- try {
23
- return path.resolve(env.THUMBGATE_FEEDBACK_DIR).startsWith(path.resolve(os.tmpdir()) + path.sep);
24
- } catch {
25
- return false;
26
- }
27
- }
28
-
29
15
  function getStatuslineCacheCandidates(options = {}) {
30
16
  const env = options.env || process.env;
31
17
  const projectDir = resolveProjectDir({ cwd: options.cwd, env });
32
18
  const feedbackDir = resolveFeedbackDir({ projectDir, env });
33
19
 
34
20
  return unique([
35
- shouldAggregateFeedback({ env }) && !hasIsolatedTempFeedbackDir(env) && getAggregateStatuslineCachePath({ env }),
36
21
  ...listFeedbackArtifactPaths('statusline_cache.json', { projectDir, env }),
37
22
  path.join(feedbackDir, 'statusline_cache.json'),
38
23
  ]);
39
24
  }
40
25
 
41
- if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename)) {
26
+ if (require.main === module) {
42
27
  process.stdout.write(JSON.stringify({ candidates: getStatuslineCacheCandidates() }));
43
28
  }
44
29
 
@@ -2,10 +2,6 @@
2
2
  'use strict';
3
3
 
4
4
  const { analyzeFeedback } = require('./feedback-loop');
5
- const {
6
- computeAggregateFeedbackStats,
7
- shouldAggregateFeedback,
8
- } = require('./feedback-aggregate');
9
5
  const { normalizeStatsPayload } = require('./hook-thumbgate-cache-updater');
10
6
  const { syncClaudeHistoryFeedback } = require('./claude-feedback-sync');
11
7
  const { resolveProjectDir } = require('./feedback-paths');
@@ -13,13 +9,9 @@ const { resolveProjectDir } = require('./feedback-paths');
13
9
  try {
14
10
  const projectDir = resolveProjectDir({ cwd: process.cwd(), env: process.env });
15
11
  syncClaudeHistoryFeedback({ projectDir });
16
- const scope = String(process.env.THUMBGATE_STATUSLINE_SCOPE || 'global').toLowerCase();
17
- const stats = scope !== 'project' && shouldAggregateFeedback({ env: process.env })
18
- ? computeAggregateFeedbackStats({ projectDir, env: process.env })
19
- : analyzeFeedback();
12
+ const stats = analyzeFeedback();
20
13
  const payload = {
21
14
  ...normalizeStatsPayload(stats),
22
- aggregate: stats.aggregate || { enabled: false },
23
15
  updated_at: String(Math.floor(Date.now() / 1000)),
24
16
  };
25
17
  process.stdout.write(JSON.stringify(payload));
@@ -2,42 +2,16 @@
2
2
  'use strict';
3
3
 
4
4
  const path = require('path');
5
- const { isProLicensed, isValidKey } = require('./license');
5
+ const { isProLicensed } = require('./license');
6
6
 
7
7
  function getStatuslineMeta(options = {}) {
8
8
  const pkg = require(path.join(__dirname, '..', 'package.json'));
9
9
  const env = options.env || process.env;
10
10
  const homeDir = options.homeDir || env.HOME || env.USERPROFILE || '.';
11
- const fs = require('fs');
12
-
13
- // Enterprise detection based on key prefix
14
- let apiKey = env.THUMBGATE_OPERATOR_KEY || env.THUMBGATE_API_KEY || '';
15
- if (apiKey && !isValidKey(apiKey)) {
16
- apiKey = '';
17
- }
18
-
19
- // Fallback to reading from disk if not in env
20
- if (!apiKey) {
21
- try {
22
- const configPath = path.join(homeDir, '.config', 'thumbgate', 'operator.json');
23
- if (fs.existsSync(configPath)) {
24
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
25
- apiKey = config.operatorKey || config.apiKey || '';
26
- }
27
- } catch (_) { /* ignore disk read errors */ }
28
- }
29
-
30
- let activeTier = 'Free';
31
-
32
- if (apiKey.startsWith('tg_op_') || apiKey.startsWith('tg_creator_')) {
33
- activeTier = 'Enterprise';
34
- } else if (isProLicensed({ homeDir }) || apiKey.startsWith('tg_pro_')) {
35
- activeTier = 'Pro';
36
- }
37
11
 
38
12
  return {
39
13
  version: String(pkg.version || '').trim() || 'unknown',
40
- tier: activeTier,
14
+ tier: isProLicensed({ homeDir }) ? 'Pro' : 'Free',
41
15
  };
42
16
  }
43
17
 
@@ -7,7 +7,6 @@
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
8
8
  case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
9
9
  LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
10
- STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
11
10
 
12
11
  # ── Parse Claude Code session JSON from stdin ─────────────────────
13
12
  eval "$(cat | jq -r '
@@ -45,19 +44,7 @@ if [ -z "$THUMBGATE_CACHE" ]; then
45
44
  fi
46
45
 
47
46
  UP="0"; DOWN="0"; LESSONS="0"; TREND="?"; CACHE_TS="0"
48
- # Display reads aggregate across every per-folder cache so the statusline
49
- # reflects true totals, not whichever folder happens to be cwd. Writes still
50
- # target $THUMBGATE_CACHE (per-folder), so attribution is preserved.
51
- _RESOLVED_CACHE_JSON=$(node "${SCRIPT_DIR}/statusline-cache-read.js" 2>/dev/null)
52
- if [[ -n "$_RESOLVED_CACHE_JSON" ]]; then
53
- eval "$(echo "$_RESOLVED_CACHE_JSON" | jq -r '
54
- @sh "UP=\(.thumbs_up // "0")",
55
- @sh "DOWN=\(.thumbs_down // "0")",
56
- @sh "LESSONS=\(.lessons // "0")",
57
- @sh "TREND=\(.trend // "?")",
58
- @sh "CACHE_TS=\(.updated_at // "0")"
59
- ' 2>/dev/null)"
60
- elif [[ -f "$THUMBGATE_CACHE" ]]; then
47
+ if [ -f "$THUMBGATE_CACHE" ]; then
61
48
  eval "$(jq -r '
62
49
  @sh "UP=\(.thumbs_up // "0")",
63
50
  @sh "DOWN=\(.thumbs_down // "0")",
@@ -212,10 +199,8 @@ LINE="${LINE:+${LINE} · }ThumbGate v${TG_VERSION} · ${TG_TIER}"
212
199
  if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
213
200
  LINE="${D}${LINE}${RST} · no feedback yet"
214
201
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
215
-
216
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
202
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
217
203
  [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
218
-
219
204
  printf '%b\n' "$LINE"
220
205
  else
221
206
  LINE="${LINE} · ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
@@ -225,9 +210,8 @@ else
225
210
  [[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}⚠${RST}"
226
211
  [[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}☠${RST}"
227
212
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} · ${D}${PR_LABEL}${RST}"
228
-
229
- LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST}"
213
+ LINE="${LINE} · ${C}${DASHBOARD_LINK}${RST} · ${M}${LESSONS_LINK}${RST}"
230
214
  [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} · ${D}${LATEST_LESSON_LINK}${RST}"
231
-
215
+
232
216
  printf '%b\n' "$LINE"
233
217
  fi
@@ -75,13 +75,6 @@ function normalizeInteger(value) {
75
75
  return Number.isFinite(parsed) ? Math.trunc(parsed) : null;
76
76
  }
77
77
 
78
- function normalizeRate(value) {
79
- if (value === undefined || value === null || value === '') return null;
80
- const parsed = Number(value);
81
- if (!Number.isFinite(parsed)) return null;
82
- return Math.max(0, Math.min(parsed, 1));
83
- }
84
-
85
78
  function safeRate(num, den) {
86
79
  if (!den) return 0;
87
80
  return Number((num / den).toFixed(4));
@@ -262,149 +255,6 @@ function inferTrafficChannel(raw = {}, referrerHost = null) {
262
255
  return 'referral';
263
256
  }
264
257
 
265
- function normalizeBoolean(value) {
266
- if (value === true || value === false) return value;
267
- const text = normalizeText(value, 32);
268
- if (!text) return false;
269
- return ['1', 'true', 'yes', 'y', 'bot'].includes(text.toLowerCase());
270
- }
271
-
272
- function includesAnyToken(values, tokens) {
273
- const haystack = values
274
- .map((value) => normalizeText(value, 512))
275
- .filter(Boolean)
276
- .join(' ')
277
- .toLowerCase();
278
- if (!haystack) return false;
279
- return tokens.some((token) => haystack.includes(token));
280
- }
281
-
282
- function addAudienceReason(reasons, code) {
283
- if (!reasons.includes(code)) reasons.push(code);
284
- }
285
-
286
- function classifyTelemetryAudience(entry = {}, raw = {}) {
287
- const reasons = [];
288
- const identityValues = [
289
- raw.email,
290
- raw.customerEmail,
291
- raw.buyerEmail,
292
- raw.userEmail,
293
- entry.visitorId,
294
- entry.sessionId,
295
- entry.traceId,
296
- entry.acquisitionId,
297
- entry.installId,
298
- ];
299
- const attributionValues = [
300
- entry.source,
301
- entry.utmSource,
302
- entry.utmMedium,
303
- entry.utmCampaign,
304
- entry.utmContent,
305
- entry.creator,
306
- entry.offerCode,
307
- entry.campaignVariant,
308
- entry.referrerHost,
309
- ];
310
- const eventValues = [
311
- entry.eventType,
312
- entry.event,
313
- entry.page,
314
- entry.landingPath,
315
- entry.ctaId,
316
- entry.reasonCode,
317
- entry.reasonDetail,
318
- ];
319
- const userAgent = normalizeText(entry.userAgent || raw.userAgent, 512) || '';
320
- const hostValues = [raw.host, raw.hostname, raw.origin, raw.url, raw.pageUrl, entry.referrerHost];
321
-
322
- if (normalizeBoolean(entry.isBot || raw.isBot) || includesAnyToken([userAgent], [
323
- 'bot',
324
- 'crawler',
325
- 'spider',
326
- 'headless',
327
- 'playwright',
328
- 'puppeteer',
329
- 'curl/',
330
- 'wget/',
331
- 'lighthouse',
332
- ])) {
333
- addAudienceReason(reasons, 'bot_or_automation_user_agent');
334
- }
335
-
336
- if (includesAnyToken(identityValues, [
337
- '@example.com',
338
- 'buyer@example.com',
339
- 'test@example.com',
340
- 'codex-verification',
341
- 'audit@thumbgate.ai',
342
- ])) {
343
- addAudienceReason(reasons, 'test_identity');
344
- }
345
-
346
- if (includesAnyToken([...attributionValues, ...eventValues], [
347
- 'codex',
348
- 'audit',
349
- 'verification',
350
- 'synthetic',
351
- 'smoke',
352
- 'probe',
353
- 'test',
354
- ])) {
355
- addAudienceReason(reasons, 'test_or_audit_attribution');
356
- }
357
-
358
- if (includesAnyToken(hostValues, [
359
- 'localhost',
360
- '127.0.0.1',
361
- '::1',
362
- 'thumbgate-production.up.railway.app',
363
- ])) {
364
- addAudienceReason(reasons, 'internal_host');
365
- }
366
-
367
- if (includesAnyToken([userAgent, ...identityValues], [
368
- 'codex',
369
- 'github-actions',
370
- 'node-fetch',
371
- 'thumbgate-analytics',
372
- 'thumbgate-verification',
373
- ])) {
374
- addAudienceReason(reasons, 'internal_operator_or_ci');
375
- }
376
-
377
- if (
378
- (entry.clientType || entry.client) === 'unknown' &&
379
- !pickFirstText(entry.visitorId, entry.sessionId, entry.acquisitionId, entry.installId, entry.traceId) &&
380
- !userAgent &&
381
- includesAnyToken([entry.source, entry.utmSource], ['direct', 'website'])
382
- ) {
383
- addAudienceReason(reasons, 'unknown_direct_no_identity');
384
- }
385
-
386
- const audience = reasons.includes('test_identity') || reasons.includes('test_or_audit_attribution')
387
- ? 'test'
388
- : (reasons.includes('bot_or_automation_user_agent')
389
- ? 'bot'
390
- : (
391
- reasons.includes('internal_host') ||
392
- reasons.includes('internal_operator_or_ci') ||
393
- reasons.includes('unknown_direct_no_identity')
394
- ? 'internal'
395
- : 'external'
396
- ));
397
-
398
- return {
399
- audience,
400
- reasons,
401
- isExternal: audience === 'external',
402
- isInternal: audience === 'internal',
403
- isTest: audience === 'test',
404
- isBot: audience === 'bot',
405
- };
406
- }
407
-
408
258
  function sanitizeTelemetryPayload(payload = {}, headers = {}) {
409
259
  const raw = payload && typeof payload === 'object' ? payload : {};
410
260
  const clientType = inferClientType(raw);
@@ -463,12 +313,6 @@ function sanitizeTelemetryPayload(payload = {}, headers = {}) {
463
313
  pipelineStatus: pickFirstText(raw.pipelineStatus, raw.workflowSprintStatus, raw.status),
464
314
  reasonCode,
465
315
  reasonDetail: pickFirstText(raw.reasonDetail, raw.reasonText, raw.otherReason, raw.notes),
466
- integration: pickFirstText(raw.integration, raw.actionIntegration),
467
- actionOperation: pickFirstText(raw.actionOperation, raw.operationId),
468
- endpoint: pickFirstText(raw.endpoint, raw.apiPath),
469
- decisionMode: pickFirstText(raw.decisionMode, raw.executionMode),
470
- actionStatus: pickFirstText(raw.actionStatus, raw.status),
471
- accepted: raw.accepted === undefined || raw.accepted === null ? null : Boolean(raw.accepted),
472
316
  pricingInterest: pickFirstText(raw.pricingInterest, raw.interestLevel),
473
317
  seoQuery: pickFirstText(raw.seoQuery, raw.query),
474
318
  seoSurface: pickFirstText(raw.seoSurface, raw.searchSurface, raw.surface),
@@ -487,22 +331,11 @@ function sanitizeTelemetryPayload(payload = {}, headers = {}) {
487
331
  httpStatus: normalizeInteger(raw.httpStatus),
488
332
  userAgent: pickFirstText(raw.userAgent, headers['user-agent']),
489
333
  isBot: pickFirstText(raw.isBot),
490
- interstitialSampled: pickFirstText(raw.interstitialSampled),
491
- interstitialSampleRate: normalizeRate(raw.interstitialSampleRate),
492
334
  attributionTagged: Boolean(
493
335
  pickFirstText(raw.utmSource, raw.utmMedium, raw.utmCampaign, raw.utmContent, raw.utmTerm)
494
336
  ),
495
337
  };
496
338
 
497
- const audience = classifyTelemetryAudience(entry, raw);
498
- entry.audience = audience.audience;
499
- entry.trafficAudience = audience.audience;
500
- entry.audienceReasons = audience.reasons;
501
- entry.isExternal = audience.isExternal;
502
- entry.isInternal = audience.isInternal;
503
- entry.isTest = audience.isTest;
504
- entry.isBotTraffic = audience.isBot;
505
-
506
339
  return entry;
507
340
  }
508
341
 
@@ -593,162 +426,9 @@ function summarizeRecentEvents(events) {
593
426
  community: entry.community || null,
594
427
  offerCode: entry.offerCode || null,
595
428
  campaignVariant: entry.campaignVariant || null,
596
- audience: entry.audience || 'external',
597
- audienceReasons: entry.audienceReasons || [],
598
429
  }));
599
430
  }
600
431
 
601
- function summarizeExternalTrafficQuality(events) {
602
- const byAudience = {};
603
- const byExclusionReason = {};
604
- const externalVisitors = new Set();
605
- const externalSessions = new Set();
606
- const externalCheckoutStarters = new Set();
607
- const pageViewsBySource = {};
608
- const pageViewsByPath = {};
609
- const pageViewsByTrafficChannel = {};
610
- const checkoutStartsBySource = {};
611
- const checkoutStartsByTrafficChannel = {};
612
- const buyerLossReasons = {};
613
- const visitorPaths = new Map();
614
- let externalEvents = 0;
615
- let excludedEvents = 0;
616
- let externalWebEvents = 0;
617
- let externalPageViews = 0;
618
- let externalCtaClicks = 0;
619
- let externalCheckoutStarts = 0;
620
- let externalBuyerLossSignals = 0;
621
-
622
- for (const entry of events) {
623
- const audience = entry.audience || 'external';
624
- incrementCounter(byAudience, audience);
625
- if (audience !== 'external') {
626
- excludedEvents += 1;
627
- const reasons = Array.isArray(entry.audienceReasons) && entry.audienceReasons.length > 0
628
- ? entry.audienceReasons
629
- : [`${audience}_traffic`];
630
- for (const reason of reasons) incrementCounter(byExclusionReason, reason);
631
- continue;
632
- }
633
-
634
- externalEvents += 1;
635
- if ((entry.clientType || entry.client) !== 'web') continue;
636
-
637
- externalWebEvents += 1;
638
- const visitorKey = pickFirstText(entry.visitorId, entry.installId, entry.sessionId);
639
- if (visitorKey) externalVisitors.add(visitorKey);
640
- if (entry.sessionId) externalSessions.add(entry.sessionId);
641
-
642
- const eventType = entry.eventType || entry.event;
643
- if (visitorKey) {
644
- const pathRow = visitorPaths.get(visitorKey) || {
645
- visitorKey,
646
- firstSeenAt: entry.receivedAt || null,
647
- lastSeenAt: entry.receivedAt || null,
648
- source: entry.source || null,
649
- trafficChannel: entry.trafficChannel || null,
650
- events: [],
651
- pages: [],
652
- checkoutStarts: 0,
653
- };
654
- if (!pathRow.firstSeenAt || String(entry.receivedAt || '') < pathRow.firstSeenAt) {
655
- pathRow.firstSeenAt = entry.receivedAt || null;
656
- }
657
- if (!pathRow.lastSeenAt || String(entry.receivedAt || '') > pathRow.lastSeenAt) {
658
- pathRow.lastSeenAt = entry.receivedAt || null;
659
- }
660
- pathRow.events.push({
661
- at: entry.receivedAt || null,
662
- eventType,
663
- page: entry.page || entry.landingPath || null,
664
- ctaId: entry.ctaId || null,
665
- });
666
- if ((entry.page || entry.landingPath) && !pathRow.pages.includes(entry.page || entry.landingPath)) {
667
- pathRow.pages.push(entry.page || entry.landingPath);
668
- }
669
- if (eventType === 'checkout_start' || eventType === 'checkout_bootstrap') {
670
- pathRow.checkoutStarts += 1;
671
- }
672
- visitorPaths.set(visitorKey, pathRow);
673
- }
674
-
675
- if (eventType === 'landing_page_view') {
676
- externalPageViews += 1;
677
- incrementCounter(pageViewsBySource, entry.source);
678
- incrementCounter(pageViewsByPath, entry.page);
679
- incrementCounter(pageViewsByTrafficChannel, entry.trafficChannel);
680
- }
681
-
682
- if (isMarketingClickEvent(eventType)) {
683
- externalCtaClicks += 1;
684
- }
685
-
686
- if (eventType === 'checkout_start' || eventType === 'checkout_bootstrap') {
687
- externalCheckoutStarts += 1;
688
- incrementCounter(checkoutStartsBySource, entry.source);
689
- incrementCounter(checkoutStartsByTrafficChannel, entry.trafficChannel);
690
- const starterKey = pickFirstText(
691
- entry.acquisitionId,
692
- entry.visitorId,
693
- entry.sessionId,
694
- entry.installId,
695
- entry.traceId
696
- );
697
- if (starterKey) externalCheckoutStarters.add(starterKey);
698
- }
699
-
700
- if (eventType === 'checkout_cancelled' || eventType === 'checkout_abandoned' || eventType === 'reason_not_buying') {
701
- externalBuyerLossSignals += 1;
702
- incrementCounter(buyerLossReasons, entry.reasonCode);
703
- }
704
- }
705
-
706
- const topPath = getTopCounterEntry(pageViewsByPath);
707
- const topSource = getTopCounterEntry(pageViewsBySource);
708
- const topTrafficChannel = getTopCounterEntry(pageViewsByTrafficChannel);
709
- const topExclusionReason = getTopCounterEntry(byExclusionReason);
710
-
711
- return {
712
- rawEvents: events.length,
713
- externalEvents,
714
- excludedEvents,
715
- internalEvents: byAudience.internal || 0,
716
- testEvents: byAudience.test || 0,
717
- botEvents: byAudience.bot || 0,
718
- exclusionRate: safeRate(excludedEvents, events.length),
719
- byAudience,
720
- byExclusionReason,
721
- topExclusionReason: topExclusionReason ? { key: topExclusionReason[0], count: topExclusionReason[1] } : null,
722
- external: {
723
- totalEvents: externalWebEvents,
724
- uniqueVisitors: externalVisitors.size,
725
- uniqueSessions: externalSessions.size,
726
- uniqueCheckoutStarters: externalCheckoutStarters.size,
727
- pageViews: externalPageViews,
728
- ctaClicks: externalCtaClicks,
729
- checkoutStarts: externalCheckoutStarts,
730
- buyerLossSignals: externalBuyerLossSignals,
731
- pageViewToCheckoutRate: safeRate(externalCheckoutStarts, externalPageViews),
732
- visitorToCheckoutRate: safeRate(externalCheckoutStarts, externalVisitors.size),
733
- bySource: pageViewsBySource,
734
- byPath: pageViewsByPath,
735
- byTrafficChannel: pageViewsByTrafficChannel,
736
- checkoutStartsBySource,
737
- checkoutStartsByTrafficChannel,
738
- buyerLossReasons,
739
- topSource: topSource ? { key: topSource[0], count: topSource[1] } : null,
740
- topPath: topPath ? { key: topPath[0], count: topPath[1] } : null,
741
- topTrafficChannel: topTrafficChannel ? { key: topTrafficChannel[0], count: topTrafficChannel[1] } : null,
742
- visitorPaths: Array.from(visitorPaths.values())
743
- .sort((a, b) => String(a.firstSeenAt || '').localeCompare(String(b.firstSeenAt || '')))
744
- .slice(0, 50),
745
- },
746
- verdict: externalEvents > 0
747
- ? (excludedEvents > externalEvents ? 'polluted_but_usable' : 'usable')
748
- : (events.length > 0 ? 'polluted_no_external_signal' : 'missing'),
749
- };
750
- }
751
-
752
432
  function getTelemetrySummary(feedbackDir, options = {}) {
753
433
  const analyticsWindow = resolveAnalyticsWindow(options);
754
434
  const telemetryLoadOptions = analyticsWindow.bounded
@@ -759,7 +439,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
759
439
  analyticsWindow,
760
440
  (entry) => entry && (entry.receivedAt || entry.timestamp)
761
441
  );
762
- const trafficQuality = summarizeExternalTrafficQuality(events);
763
442
  const byClientType = {};
764
443
  const byEventType = {};
765
444
  const webVisitors = new Set();
@@ -847,12 +526,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
847
526
  let exitEngagementMsCount = 0;
848
527
  let exitScrollPercentTotal = 0;
849
528
  let exitScrollPercentCount = 0;
850
- let chatgptActionCalls = 0;
851
- let chatgptActionAccepted = 0;
852
- const chatgptActionsByOperation = {};
853
- const chatgptActionsByEndpoint = {};
854
- const chatgptActionsByStatus = {};
855
- const chatgptActionsByDecisionMode = {};
856
529
 
857
530
  for (const entry of events) {
858
531
  incrementCounter(byClientType, entry.clientType || entry.client || 'unknown');
@@ -1041,18 +714,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
1041
714
  }
1042
715
  }
1043
716
 
1044
- if (
1045
- String(entry.eventType || entry.event || '').startsWith('chatgpt_action_') ||
1046
- entry.integration === 'chatgpt_gpt'
1047
- ) {
1048
- chatgptActionCalls += 1;
1049
- if (entry.accepted === true) chatgptActionAccepted += 1;
1050
- incrementCounter(chatgptActionsByOperation, entry.actionOperation);
1051
- incrementCounter(chatgptActionsByEndpoint, entry.endpoint);
1052
- incrementCounter(chatgptActionsByStatus, entry.actionStatus);
1053
- incrementCounter(chatgptActionsByDecisionMode, entry.decisionMode);
1054
- }
1055
-
1056
717
  if ((entry.clientType || entry.client) === 'cli') {
1057
718
  if (entry.installId) cliInstalls.add(entry.installId);
1058
719
  incrementCounter(cliByPlatform, entry.platform);
@@ -1103,14 +764,12 @@ function getTelemetrySummary(feedbackDir, options = {}) {
1103
764
  window: serializeAnalyticsWindow(analyticsWindow),
1104
765
  totalEvents: events.length,
1105
766
  latestSeenAt,
1106
- trafficQuality,
1107
767
  byClientType,
1108
768
  byEventType,
1109
769
  conversionFunnel: {
1110
770
  landingViews: pageViews,
1111
771
  installCopies,
1112
772
  gptOpens,
1113
- gptActionCalls: chatgptActionCalls,
1114
773
  checkoutStarts,
1115
774
  checkoutInterstitialViews,
1116
775
  checkoutInterstitialClicks,
@@ -1118,7 +777,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
1118
777
  proConversions,
1119
778
  landingToInstallCopyRate: safeRate(installCopies, pageViews),
1120
779
  landingToGptOpenRate: safeRate(gptOpens, pageViews),
1121
- gptOpenToActionRate: safeRate(chatgptActionCalls, gptOpens),
1122
780
  landingToCheckoutRate: safeRate(checkoutStarts, pageViews),
1123
781
  checkoutInterstitialClickRate: safeRate(checkoutInterstitialClicks, checkoutInterstitialViews),
1124
782
  checkoutInterstitialProConfirmRate: safeRate(checkoutInterstitialProConfirms, checkoutInterstitialViews),
@@ -1177,17 +835,6 @@ function getTelemetrySummary(feedbackDir, options = {}) {
1177
835
  byPlatform: cliByPlatform,
1178
836
  byVersion: cliByVersion,
1179
837
  },
1180
- chatgpt: {
1181
- gptOpens,
1182
- actionCalls: chatgptActionCalls,
1183
- acceptedActionCalls: chatgptActionAccepted,
1184
- openToActionRate: safeRate(chatgptActionCalls, gptOpens),
1185
- acceptedActionRate: safeRate(chatgptActionAccepted, chatgptActionCalls),
1186
- byOperation: chatgptActionsByOperation,
1187
- byEndpoint: chatgptActionsByEndpoint,
1188
- byStatus: chatgptActionsByStatus,
1189
- byDecisionMode: chatgptActionsByDecisionMode,
1190
- },
1191
838
  marketing: {
1192
839
  pageViewsBySource,
1193
840
  pageViewsByCampaign,
@@ -1279,12 +926,9 @@ function getTelemetryAnalytics(feedbackDir, options = {}) {
1279
926
  window: summary.window,
1280
927
  totalEvents: summary.totalEvents,
1281
928
  latestSeenAt: summary.latestSeenAt,
1282
- trafficQuality: summary.trafficQuality,
1283
- qualified: summary.trafficQuality.external,
1284
929
  byClientType: summary.byClientType,
1285
930
  byEventType: summary.byEventType,
1286
931
  conversionFunnel: summary.conversionFunnel,
1287
- chatgpt: summary.chatgpt,
1288
932
  visitors: {
1289
933
  totalEvents: summary.web.totalEvents,
1290
934
  uniqueVisitors: summary.web.uniqueVisitors,
@@ -1460,7 +1104,6 @@ const appendTelemetryPing = appendTelemetryEvent;
1460
1104
  module.exports = {
1461
1105
  TELEMETRY_FILE_NAME,
1462
1106
  sanitizeTelemetryPayload,
1463
- classifyTelemetryAudience,
1464
1107
  appendTelemetryPing,
1465
1108
  appendTelemetryEvent,
1466
1109
  getTelemetrySourceDiagnostics,
@@ -302,44 +302,24 @@ function getCalibration(model) {
302
302
  // ---------------------------------------------------------------------------
303
303
 
304
304
  /**
305
- * Return the Beta posterior parameters after applying Thompson temperature
306
- * scaling. The posterior mean is preserved while precision changes:
307
- * lower temperatures sharpen the posterior, higher temperatures flatten it.
305
+ * Draw one sample from the Beta posterior for each category via the
306
+ * Marsaglia-Tsang (2000) gamma ratio method. No external library needed.
308
307
  *
309
- * @param {Object} params - Category posterior parameters
310
- * @param {number} temperature - Scaling factor (default 1.0)
311
- * @returns {{ alpha: number, beta: number }}
312
- */
313
- function getTemperatureScaledPosteriorParams(params, temperature = 1.0) {
314
- const T = Math.max(0.01, Number(temperature) || 1.0);
315
- const invT = 1.0 / T;
316
- return {
317
- alpha: Math.max(params.alpha * invT, 0.01),
318
- beta: Math.max(params.beta * invT, 0.01),
319
- };
320
- }
321
-
322
- /**
323
- * Draw one sample from the Beta posterior for each category.
324
- * Supports temperature scaling to adjust exploitation vs exploration.
308
+ * betaSample(alpha, beta) = gammaSample(alpha) / (gammaSample(alpha) + gammaSample(beta))
325
309
  *
326
- * Temperature (T):
327
- * T = 1.0 (default) Standard Thompson Sampling.
328
- * T < 1.0 — Sharper distribution, favors high-reliability categories (exploit).
329
- * T > 1.0 — Flatter distribution, increases uncertainty and exploration.
330
- *
331
- * Implementation: Scales alpha and beta by 1/T.
310
+ * This is the JS equivalent of Python's random.betavariate(alpha, beta).
311
+ * Used for Thompson Sampling action selection (explore via uncertainty).
332
312
  *
333
313
  * @param {Object} model - Model object containing categories
334
- * @param {number} temperature - Scaling factor (default 1.0)
335
314
  * @returns {Object} Map of category → float sample in [0, 1]
336
315
  */
337
- function samplePosteriors(model, temperature = 1.0) {
316
+ function samplePosteriors(model) {
338
317
  const samples = {};
339
-
340
318
  for (const [cat, params] of Object.entries(model.categories || {})) {
341
- const { alpha, beta } = getTemperatureScaledPosteriorParams(params, temperature);
342
- samples[cat] = betaSample(alpha, beta);
319
+ samples[cat] = betaSample(
320
+ Math.max(params.alpha, 0.01),
321
+ Math.max(params.beta, 0.01),
322
+ );
343
323
  }
344
324
  return samples;
345
325
  }
@@ -471,7 +451,6 @@ module.exports = {
471
451
  getReliability,
472
452
  isCalibrated,
473
453
  getCalibration,
474
- getTemperatureScaledPosteriorParams,
475
454
  samplePosteriors,
476
455
  argmaxPosteriors,
477
456
  pickBestCategory,