thumbgate 0.9.10

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 (364) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/thumbgate-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1484 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +283 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1014 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +97 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +263 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -0
@@ -0,0 +1,196 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const SIMILARITY_THRESHOLD = 0.6;
6
+ const AUTO_PROMOTE_THRESHOLD = 3;
7
+
8
+ /**
9
+ * Read JSONL file and return parsed records.
10
+ * Self-contained to avoid circular dependency with feedback-loop.
11
+ */
12
+ function readJSONLLocal(filePath, { maxLines = 500 } = {}) {
13
+ if (!fs.existsSync(filePath)) return [];
14
+ const content = fs.readFileSync(filePath, 'utf8');
15
+ const lines = content.split('\n').filter(Boolean);
16
+ const tail = maxLines > 0 ? lines.slice(-maxLines) : lines;
17
+ const results = [];
18
+ for (const line of tail) {
19
+ try { results.push(JSON.parse(line)); } catch { /* skip malformed */ }
20
+ }
21
+ return results;
22
+ }
23
+
24
+ /**
25
+ * Append a record to a JSONL file.
26
+ */
27
+ function appendJSONLLocal(filePath, record) {
28
+ const dir = path.dirname(filePath);
29
+ if (!fs.existsSync(dir)) {
30
+ fs.mkdirSync(dir, { recursive: true });
31
+ }
32
+ fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`);
33
+ }
34
+
35
+ /**
36
+ * Find a similar existing lesson by comparing titles and context.
37
+ * Uses token overlap (Jaccard similarity) — fast, no embeddings needed.
38
+ */
39
+ function findSimilarLesson(memoryLogPath, newRecord) {
40
+ const existing = readJSONLLocal(memoryLogPath, { maxLines: 200 });
41
+ const newTokens = tokenize(newRecord.title + ' ' + (newRecord.content || ''));
42
+
43
+ let bestMatch = null;
44
+ let bestScore = 0;
45
+
46
+ for (const mem of existing) {
47
+ const memTokens = tokenize((mem.title || '') + ' ' + (mem.content || ''));
48
+ const score = jaccardSimilarity(newTokens, memTokens);
49
+ if (score > bestScore && score >= SIMILARITY_THRESHOLD) {
50
+ bestScore = score;
51
+ bestMatch = mem;
52
+ }
53
+ }
54
+
55
+ return bestMatch ? { match: bestMatch, similarity: bestScore } : null;
56
+ }
57
+
58
+ /**
59
+ * Merge a new feedback event into an existing lesson.
60
+ * Increments occurrence count, updates timestamp, enriches context.
61
+ */
62
+ function mergeIntoExisting(memoryLogPath, existingLesson, newRecord, newFeedbackEvent) {
63
+ const merged = {
64
+ ...existingLesson,
65
+ occurrences: (existingLesson.occurrences || 1) + 1,
66
+ lastUpdated: new Date().toISOString(),
67
+ mergedFeedbackIds: [
68
+ ...(existingLesson.mergedFeedbackIds || []),
69
+ newFeedbackEvent.id
70
+ ].slice(-20), // Keep last 20 IDs
71
+ };
72
+
73
+ // Enrich context if the new one adds information
74
+ if (newRecord.content && newRecord.content.length > (existingLesson.content || '').length) {
75
+ merged.content = newRecord.content;
76
+ }
77
+
78
+ // Update the record in-place by rewriting the JSONL
79
+ updateRecordInJsonl(memoryLogPath, existingLesson.id, merged);
80
+
81
+ return merged;
82
+ }
83
+
84
+ /**
85
+ * Check if a lesson should be auto-promoted to a prevention rule.
86
+ * Threshold: 3+ occurrences.
87
+ */
88
+ function shouldAutoPromote(lesson) {
89
+ return (lesson.occurrences || 1) >= AUTO_PROMOTE_THRESHOLD;
90
+ }
91
+
92
+ /**
93
+ * Generate a structured prevention rule from a high-frequency lesson.
94
+ */
95
+ function synthesizePreventionRule(lesson) {
96
+ const title = lesson.title || '';
97
+ const content = lesson.content || '';
98
+
99
+ // Extract the core mistake pattern
100
+ const mistakeMatch = title.match(/^MISTAKE:\s*(.+)/i);
101
+ const mistake = mistakeMatch ? mistakeMatch[1].trim() : title;
102
+
103
+ // Build IF/THEN rule
104
+ return {
105
+ id: 'synth_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
106
+ type: 'auto-promoted',
107
+ source: 'lesson-synthesis',
108
+ sourceLessonId: lesson.id,
109
+ occurrences: lesson.occurrences || 1,
110
+ rule: {
111
+ format: 'if-then-v1',
112
+ trigger: { condition: mistake, type: 'recurring-mistake' },
113
+ action: { type: 'avoid', description: 'NEVER: ' + mistake },
114
+ confidence: Math.min(0.5 + (lesson.occurrences || 1) * 0.1, 0.95),
115
+ scope: inferScopeFromTags(lesson.tags || []),
116
+ },
117
+ humanReadable: 'After ' + (lesson.occurrences || 1) + ' occurrences: NEVER ' + mistake,
118
+ tags: [...(lesson.tags || []), 'auto-promoted', 'synthesized'],
119
+ createdAt: new Date().toISOString(),
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Update a single record in a JSONL file by ID.
125
+ */
126
+ function updateRecordInJsonl(filePath, recordId, updatedRecord) {
127
+ if (!fs.existsSync(filePath)) return false;
128
+ const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
129
+ let found = false;
130
+ const updated = lines.map(function(line) {
131
+ try {
132
+ const obj = JSON.parse(line);
133
+ if (obj.id === recordId) {
134
+ found = true;
135
+ return JSON.stringify(updatedRecord);
136
+ }
137
+ return line;
138
+ } catch { return line; }
139
+ });
140
+ if (found) {
141
+ fs.writeFileSync(filePath, updated.join('\n') + '\n');
142
+ }
143
+ return found;
144
+ }
145
+
146
+ /**
147
+ * Delete a single record from a JSONL file by ID.
148
+ */
149
+ function deleteRecordFromJsonl(filePath, recordId) {
150
+ if (!fs.existsSync(filePath)) return false;
151
+ const lines = fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
152
+ const filtered = lines.filter(function(line) {
153
+ try {
154
+ const obj = JSON.parse(line);
155
+ return obj.id !== recordId;
156
+ } catch { return true; }
157
+ });
158
+ if (filtered.length === lines.length) return false;
159
+ fs.writeFileSync(filePath, filtered.length ? filtered.join('\n') + '\n' : '');
160
+ return true;
161
+ }
162
+
163
+ function tokenize(text) {
164
+ return (text || '').toLowerCase().split(/[\s.,;:!?()\[\]{}"'`]+/).filter(function(t) { return t.length > 3; });
165
+ }
166
+
167
+ function jaccardSimilarity(setA, setB) {
168
+ var a = new Set(setA);
169
+ var b = new Set(setB);
170
+ var intersection = 0;
171
+ a.forEach(function(item) { if (b.has(item)) intersection++; });
172
+ var union = a.size + b.size - intersection;
173
+ return union === 0 ? 0 : intersection / union;
174
+ }
175
+
176
+ function inferScopeFromTags(tags) {
177
+ if (tags.some(function(t) { return t.includes('file') || t.includes('src/'); })) return 'file-level';
178
+ if (tags.some(function(t) { return t.includes('project') || t.includes('repo'); })) return 'project-level';
179
+ return 'global';
180
+ }
181
+
182
+ module.exports = {
183
+ findSimilarLesson,
184
+ mergeIntoExisting,
185
+ shouldAutoPromote,
186
+ synthesizePreventionRule,
187
+ updateRecordInJsonl,
188
+ deleteRecordFromJsonl,
189
+ readJSONLLocal,
190
+ appendJSONLLocal,
191
+ jaccardSimilarity,
192
+ tokenize,
193
+ inferScopeFromTags,
194
+ SIMILARITY_THRESHOLD,
195
+ AUTO_PROMOTE_THRESHOLD,
196
+ };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const crypto = require('crypto');
5
+
6
+ const LICENSE_PATH = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.thumbgate', 'license.json');
7
+ const VALID_PREFIXES = ['tg_pro_', 'tg_'];
8
+
9
+ function isValidKey(key) {
10
+ return key && VALID_PREFIXES.some((p) => key.startsWith(p));
11
+ }
12
+
13
+ function verifyLicense() {
14
+ const envKey = process.env.THUMBGATE_API_KEY || process.env.THUMBGATE_PRO_KEY;
15
+ if (isValidKey(envKey)) {
16
+ return { valid: true, source: 'env', key: envKey };
17
+ }
18
+ try {
19
+ if (fs.existsSync(LICENSE_PATH)) {
20
+ const data = JSON.parse(fs.readFileSync(LICENSE_PATH, 'utf8'));
21
+ if (isValidKey(data.key)) {
22
+ return { valid: true, source: 'file', key: data.key, activatedAt: data.activatedAt };
23
+ }
24
+ }
25
+ } catch (_) {}
26
+ return { valid: false, source: null };
27
+ }
28
+
29
+ function isProLicensed() {
30
+ return verifyLicense().valid;
31
+ }
32
+
33
+ function activateLicense(key) {
34
+ if (!isValidKey(key)) {
35
+ return { success: false, error: 'Invalid key format. Expected tg_... or tg_pro_...' };
36
+ }
37
+ const dir = path.dirname(LICENSE_PATH);
38
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
39
+ const data = { key, activatedAt: new Date().toISOString(), version: require('../package.json').version };
40
+ fs.writeFileSync(LICENSE_PATH, JSON.stringify(data, null, 2));
41
+ return { success: true, path: LICENSE_PATH };
42
+ }
43
+
44
+ function generateLicenseKey(email) {
45
+ const payload = `${email}:${Date.now()}`;
46
+ const hash = crypto.createHash('sha256').update(payload).digest('hex').slice(0, 24);
47
+ return `tg_pro_${hash}`;
48
+ }
49
+
50
+ module.exports = { verifyLicense, isProLicensed, activateLicense, generateLicenseKey, isValidKey, VALID_PREFIXES, LICENSE_PATH };
@@ -0,0 +1,383 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const path = require('path');
7
+
8
+ const PROJECT_ROOT = path.join(__dirname, '..');
9
+ const DEFAULT_FEEDBACK_DIR = path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
10
+ const DEFAULT_EMBED_MODEL = 'Xenova/all-MiniLM-L6-v2';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Model Role Router (OpenDev workload-specialized model routing)
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const MODEL_ROLES = {
17
+ normal: 'gemini-2.5-flash',
18
+ thinking: 'gemini-2.5-pro',
19
+ critique: 'gemini-2.5-flash',
20
+ compaction: 'gemini-2.5-flash-lite',
21
+ vlm: 'gemini-2.5-flash',
22
+ };
23
+
24
+ const VALID_MODEL_ROLES = Object.keys(MODEL_ROLES);
25
+
26
+ const EMBEDDING_PROFILES = {
27
+ compact: {
28
+ id: 'compact',
29
+ model: DEFAULT_EMBED_MODEL,
30
+ quantized: true,
31
+ maxChars: 1024,
32
+ rationale: 'Conservative fit for low-memory or CI environments.',
33
+ },
34
+ balanced: {
35
+ id: 'balanced',
36
+ model: DEFAULT_EMBED_MODEL,
37
+ quantized: true,
38
+ maxChars: 2048,
39
+ rationale: 'Default local profile for reliable quantized embedding.',
40
+ },
41
+ quality: {
42
+ id: 'quality',
43
+ model: DEFAULT_EMBED_MODEL,
44
+ quantized: false,
45
+ maxChars: 4096,
46
+ rationale: 'Higher-quality local embedding when memory headroom is available.',
47
+ },
48
+ };
49
+
50
+ const INDEXCACHE_SERVER_ENGINES = new Set([
51
+ 'sglang',
52
+ 'vllm',
53
+ 'trtllm',
54
+ 'tensorrt-llm',
55
+ ]);
56
+
57
+ const LONG_CONTEXT_TASK_TYPES = new Set([
58
+ 'architecture',
59
+ 'cross-file',
60
+ 'large-context',
61
+ ]);
62
+
63
+ const LONG_CONTEXT_TAGS = new Set([
64
+ 'codegraph',
65
+ 'contextfs',
66
+ 'long-context',
67
+ 'multi-hop',
68
+ 'retrieval-heavy',
69
+ 'xmemory',
70
+ ]);
71
+
72
+ function parseNumber(value, fallback) {
73
+ const parsed = Number(value);
74
+ return Number.isFinite(parsed) ? parsed : fallback;
75
+ }
76
+
77
+ function parseBoolean(value, fallback) {
78
+ if (value == null || value === '') return fallback;
79
+ if (typeof value === 'boolean') return value;
80
+ const normalized = String(value).trim().toLowerCase();
81
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
82
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
83
+ return fallback;
84
+ }
85
+
86
+ function normalizeSlug(value, fallback = '') {
87
+ if (value === undefined || value === null || value === '') return fallback;
88
+ const normalized = String(value)
89
+ .trim()
90
+ .toLowerCase()
91
+ .replace(/[^a-z0-9]+/g, '-')
92
+ .replace(/^-+|-+$/g, '');
93
+ return normalized || fallback;
94
+ }
95
+
96
+ function isSparseAttentionFamily(modelFamily) {
97
+ return modelFamily.startsWith('deepseek') || modelFamily.startsWith('glm');
98
+ }
99
+
100
+ function resolveProviderMode(env = process.env) {
101
+ const explicit = normalizeSlug(env.THUMBGATE_PROVIDER_MODE || env.THUMBGATE_MODEL_PROVIDER_MODE);
102
+ if (explicit === 'local' || explicit === 'managed') return explicit;
103
+ if (env.THUMBGATE_LOCAL_MODEL_FAMILY || env.THUMBGATE_LOCAL_MODEL_SERVER) return 'local';
104
+ return 'managed';
105
+ }
106
+
107
+ function resolveServerEngine(env = process.env, providerMode = resolveProviderMode(env)) {
108
+ const explicit = normalizeSlug(env.THUMBGATE_LOCAL_MODEL_SERVER || env.THUMBGATE_MODEL_SERVER);
109
+ if (explicit) return explicit;
110
+ return providerMode === 'local' ? 'generic' : 'api';
111
+ }
112
+
113
+ function resolveModelFamily(env = process.env) {
114
+ return normalizeSlug(
115
+ env.THUMBGATE_LOCAL_MODEL_FAMILY
116
+ || env.THUMBGATE_MODEL_FAMILY
117
+ || env.THUMBGATE_LOCAL_MODEL
118
+ || env.THUMBGATE_MODEL_ID,
119
+ 'unknown',
120
+ );
121
+ }
122
+
123
+ function buildBackendLabel(providerMode, modelFamily) {
124
+ if (providerMode === 'managed') return 'Managed API backend';
125
+ if (modelFamily.startsWith('deepseek')) return 'Local DeepSeek sparse backend';
126
+ if (modelFamily.startsWith('glm')) return 'Local GLM sparse backend';
127
+ return 'Local dense backend';
128
+ }
129
+
130
+ function detectInferenceBackend(env = process.env) {
131
+ const providerMode = resolveProviderMode(env);
132
+ const modelFamily = resolveModelFamily(env);
133
+ const serverEngine = resolveServerEngine(env, providerMode);
134
+ const supportsSparseAttention = isSparseAttentionFamily(modelFamily);
135
+ const indexCacheEligible = providerMode === 'local'
136
+ && supportsSparseAttention
137
+ && INDEXCACHE_SERVER_ENGINES.has(serverEngine);
138
+ const indexCacheEnabled = indexCacheEligible && parseBoolean(env.THUMBGATE_INDEXCACHE_ENABLED, false);
139
+ const id = providerMode === 'managed'
140
+ ? 'managed-api'
141
+ : supportsSparseAttention
142
+ ? `local-${modelFamily}-sparse`
143
+ : 'local-dense';
144
+
145
+ let rationale = 'Baseline backend with no sparse-attention acceleration.';
146
+ if (providerMode === 'managed') {
147
+ rationale = 'Managed API path does not expose sparse-attention kernel controls like IndexCache.';
148
+ } else if (indexCacheEnabled) {
149
+ rationale = `Local ${modelFamily} backend is sparse-attention capable and IndexCache-ready on ${serverEngine}.`;
150
+ } else if (indexCacheEligible) {
151
+ rationale = `Local ${modelFamily} backend is sparse-attention capable and can use IndexCache on ${serverEngine}.`;
152
+ } else if (supportsSparseAttention) {
153
+ rationale = `Local ${modelFamily} backend is sparse-attention capable, but current server engine "${serverEngine}" is not marked IndexCache-ready.`;
154
+ }
155
+
156
+ return {
157
+ id,
158
+ label: buildBackendLabel(providerMode, modelFamily),
159
+ providerMode,
160
+ modelFamily,
161
+ serverEngine,
162
+ supportsSparseAttention,
163
+ indexCacheEligible,
164
+ indexCacheEnabled,
165
+ longContextOptimized: indexCacheEnabled,
166
+ rationale,
167
+ };
168
+ }
169
+
170
+ function isLongContextTask(task = {}) {
171
+ const contextTokens = Number(task.contextTokens || 0);
172
+ const tags = Array.isArray(task.tags) ? task.tags.map((tag) => normalizeSlug(tag)) : [];
173
+ return contextTokens >= 120000
174
+ || LONG_CONTEXT_TASK_TYPES.has(normalizeSlug(task.type))
175
+ || tags.some((tag) => LONG_CONTEXT_TAGS.has(tag));
176
+ }
177
+
178
+ function recommendInferenceBackend(task = {}, env = process.env) {
179
+ const backend = detectInferenceBackend(env);
180
+ const privacyRoute = task.privacyRoute || 'frontier';
181
+ const workloadClass = isLongContextTask(task) ? 'long_context' : 'baseline';
182
+
183
+ if (privacyRoute === 'local' && backend.providerMode !== 'local') {
184
+ return {
185
+ backend,
186
+ workloadClass,
187
+ recommendationClass: 'privacy_local_required',
188
+ route: 'local',
189
+ reason: 'privacy-sensitive workload should stay on a local backend before any long-context optimization.',
190
+ };
191
+ }
192
+
193
+ if (workloadClass === 'long_context' && backend.indexCacheEnabled) {
194
+ return {
195
+ backend,
196
+ workloadClass,
197
+ recommendationClass: 'indexcache_active',
198
+ route: backend.providerMode,
199
+ reason: `current backend ${backend.id} is IndexCache-ready for long-context sparse-attention workloads.`,
200
+ };
201
+ }
202
+
203
+ if (workloadClass === 'long_context' && backend.indexCacheEligible) {
204
+ return {
205
+ backend,
206
+ workloadClass,
207
+ recommendationClass: 'indexcache_eligible',
208
+ route: backend.providerMode,
209
+ reason: `current backend ${backend.id} is sparse-attention capable; enabling IndexCache is the highest-ROI latency/cost improvement.`,
210
+ };
211
+ }
212
+
213
+ if (workloadClass === 'long_context') {
214
+ return {
215
+ backend,
216
+ workloadClass,
217
+ recommendationClass: 'baseline_long_context',
218
+ route: backend.providerMode,
219
+ reason: backend.providerMode === 'managed'
220
+ ? 'managed API path hides sparse-attention kernel controls, so IndexCache-style gains are unavailable here.'
221
+ : `current local backend ${backend.id} is not yet IndexCache-eligible.`,
222
+ };
223
+ }
224
+
225
+ return {
226
+ backend,
227
+ workloadClass,
228
+ recommendationClass: 'baseline',
229
+ route: privacyRoute === 'local' ? 'local' : backend.providerMode,
230
+ reason: 'baseline workload does not need sparse-attention optimization.',
231
+ };
232
+ }
233
+
234
+ function resolveFeedbackDir(explicitDir) {
235
+ return explicitDir || process.env.THUMBGATE_FEEDBACK_DIR || DEFAULT_FEEDBACK_DIR;
236
+ }
237
+
238
+ function detectHardware(env = process.env) {
239
+ const totalMemBytes = parseNumber(env.THUMBGATE_RAM_BYTES_OVERRIDE, os.totalmem());
240
+ const ramGb = Math.round((totalMemBytes / (1024 ** 3)) * 10) / 10;
241
+ const cpuCount = Math.max(1, Math.floor(parseNumber(env.THUMBGATE_CPU_COUNT_OVERRIDE, os.cpus().length || 1)));
242
+ const platform = env.THUMBGATE_PLATFORM_OVERRIDE || process.platform;
243
+ const arch = env.THUMBGATE_ARCH_OVERRIDE || process.arch;
244
+ const ci = parseBoolean(env.CI, false);
245
+ const accelerator = env.THUMBGATE_ACCELERATOR
246
+ || (platform === 'darwin' && arch === 'arm64' ? 'metal' : 'cpu');
247
+
248
+ return {
249
+ ramGb,
250
+ cpuCount,
251
+ platform,
252
+ arch,
253
+ accelerator,
254
+ ci,
255
+ };
256
+ }
257
+
258
+ function pickAutoProfile(hardware) {
259
+ if (hardware.ci || hardware.ramGb < 8 || hardware.cpuCount <= 4) {
260
+ return EMBEDDING_PROFILES.compact;
261
+ }
262
+ if (hardware.ramGb >= 24 && hardware.cpuCount >= 8 && !hardware.ci) {
263
+ return EMBEDDING_PROFILES.quality;
264
+ }
265
+ return EMBEDDING_PROFILES.balanced;
266
+ }
267
+
268
+ function cloneProfile(profile) {
269
+ return {
270
+ id: profile.id,
271
+ model: profile.model,
272
+ quantized: profile.quantized,
273
+ maxChars: profile.maxChars,
274
+ rationale: profile.rationale,
275
+ };
276
+ }
277
+
278
+ function resolveEmbeddingProfile(env = process.env) {
279
+ const hardware = detectHardware(env);
280
+ const requestedProfile = String(env.THUMBGATE_MODEL_FIT_PROFILE || 'auto').trim().toLowerCase();
281
+
282
+ const baseProfile = requestedProfile !== 'auto' && EMBEDDING_PROFILES[requestedProfile]
283
+ ? EMBEDDING_PROFILES[requestedProfile]
284
+ : pickAutoProfile(hardware);
285
+
286
+ const profile = cloneProfile(baseProfile);
287
+ const source = requestedProfile !== 'auto' && EMBEDDING_PROFILES[requestedProfile]
288
+ ? 'profile_override'
289
+ : 'auto';
290
+
291
+ if (env.THUMBGATE_EMBED_MODEL) {
292
+ profile.model = String(env.THUMBGATE_EMBED_MODEL).trim();
293
+ }
294
+ profile.quantized = parseBoolean(env.THUMBGATE_EMBED_QUANTIZED, profile.quantized);
295
+ profile.maxChars = Math.max(256, Math.floor(parseNumber(env.THUMBGATE_EMBED_MAX_CHARS, profile.maxChars)));
296
+
297
+ const fallback = cloneProfile(EMBEDDING_PROFILES.balanced);
298
+ fallback.id = 'fallback';
299
+
300
+ return {
301
+ source,
302
+ hardware,
303
+ selectedProfile: profile,
304
+ fallbackProfile: fallback,
305
+ };
306
+ }
307
+
308
+ /**
309
+ * Resolve the LLM model ID for a given workload role.
310
+ *
311
+ * Roles: normal, thinking, critique, compaction, vlm
312
+ * Each role can be overridden via THUMBGATE_MODEL_ROLE_<ROLE> env var.
313
+ *
314
+ * @param {string} role - One of the valid model roles
315
+ * @param {object} [env=process.env]
316
+ * @returns {{ role: string, model: string, provider: string, envKey: string }}
317
+ */
318
+ function resolveModelRole(role, env) {
319
+ const e = env || process.env;
320
+ const normalized = String(role || '').toLowerCase().trim();
321
+ if (!MODEL_ROLES[normalized]) {
322
+ throw new Error(`Unknown model role: '${normalized}'. Valid roles: ${VALID_MODEL_ROLES.join(', ')}`);
323
+ }
324
+ const envKey = `THUMBGATE_MODEL_ROLE_${normalized.toUpperCase()}`;
325
+ const model = (e[envKey] && String(e[envKey]).trim()) || MODEL_ROLES[normalized];
326
+ return { role: normalized, model, provider: 'gemini', envKey };
327
+ }
328
+
329
+ function buildModelFitReport(options = {}) {
330
+ const resolved = options.resolved || resolveEmbeddingProfile(options.env);
331
+ const selected = resolved.selectedProfile;
332
+ const fallback = resolved.fallbackProfile;
333
+ const summary = selected.quantized
334
+ ? `${selected.id} profile selected with quantized ${selected.model}`
335
+ : `${selected.id} profile selected with full-precision ${selected.model}`;
336
+
337
+ return {
338
+ generatedAt: new Date().toISOString(),
339
+ source: resolved.source,
340
+ hardware: resolved.hardware,
341
+ selectedProfile: selected,
342
+ fallbackProfile: fallback,
343
+ summary,
344
+ };
345
+ }
346
+
347
+ function getModelFitReportPath(feedbackDir) {
348
+ return path.join(resolveFeedbackDir(feedbackDir), 'model-fit-report.json');
349
+ }
350
+
351
+ function writeModelFitReport(feedbackDir, options = {}) {
352
+ const report = buildModelFitReport(options);
353
+ const reportPath = getModelFitReportPath(feedbackDir);
354
+ fs.mkdirSync(path.dirname(reportPath), { recursive: true });
355
+ fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
356
+ return { reportPath, report };
357
+ }
358
+
359
+ module.exports = {
360
+ DEFAULT_EMBED_MODEL,
361
+ DEFAULT_FEEDBACK_DIR,
362
+ EMBEDDING_PROFILES,
363
+ INDEXCACHE_SERVER_ENGINES,
364
+ LONG_CONTEXT_TAGS,
365
+ LONG_CONTEXT_TASK_TYPES,
366
+ MODEL_ROLES,
367
+ VALID_MODEL_ROLES,
368
+ detectHardware,
369
+ detectInferenceBackend,
370
+ resolveEmbeddingProfile,
371
+ resolveModelRole,
372
+ buildModelFitReport,
373
+ writeModelFitReport,
374
+ getModelFitReportPath,
375
+ isLongContextTask,
376
+ recommendInferenceBackend,
377
+ resolveFeedbackDir,
378
+ };
379
+
380
+ if (require.main === module) {
381
+ const report = buildModelFitReport();
382
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
383
+ }
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ function escapeMarkdownTableCell(value) {
4
+ return String(value || '')
5
+ .replace(/\\/g, '\\\\')
6
+ .replace(/\|/g, '\\|')
7
+ .replace(/\r?\n/g, ' ');
8
+ }
9
+
10
+ module.exports = {
11
+ escapeMarkdownTableCell,
12
+ };