thumbgate 0.9.9

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 (369) 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/rlhf-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 +1483 -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 +286 -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 +1195 -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-314.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 +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -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 +93 -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 +231 -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 +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ function toNumber(value) {
5
+ const numeric = Number(value || 0);
6
+ return Number.isFinite(numeric) ? numeric : 0;
7
+ }
8
+
9
+ function safeRate(numerator, denominator, precision = 4) {
10
+ if (!denominator) return 0;
11
+ return Number((toNumber(numerator) / toNumber(denominator)).toFixed(precision));
12
+ }
13
+
14
+ function clamp01(value) {
15
+ return Math.max(0, Math.min(1, toNumber(value)));
16
+ }
17
+
18
+ function normalizeCapped(value, cap) {
19
+ const capped = toNumber(cap);
20
+ if (!capped || capped <= 0) return 0;
21
+ return clamp01(toNumber(value) / capped);
22
+ }
23
+
24
+ function sumCounter(counter = {}) {
25
+ return Object.values(counter).reduce((sum, value) => sum + toNumber(value), 0);
26
+ }
27
+
28
+ function summarizeSeverity(anomalies = []) {
29
+ if (anomalies.some((entry) => entry.severity === 'critical')) return 'critical';
30
+ if (anomalies.some((entry) => entry.severity === 'warning')) return 'warning';
31
+ return 'healthy';
32
+ }
33
+
34
+ function toBand(score) {
35
+ if (score >= 0.8) return 'very_high';
36
+ if (score >= 0.6) return 'high';
37
+ if (score >= 0.4) return 'medium';
38
+ if (score >= 0.2) return 'low';
39
+ return 'very_low';
40
+ }
41
+
42
+ function weightedAverage(entries = []) {
43
+ const active = entries.filter((entry) => entry && Number.isFinite(entry.value) && entry.weight > 0);
44
+ const totalWeight = active.reduce((sum, entry) => sum + entry.weight, 0);
45
+ if (!totalWeight) return 0;
46
+ return active.reduce((sum, entry) => sum + (entry.value * entry.weight), 0) / totalWeight;
47
+ }
48
+
49
+ function smoothedRate(successes, trials, priorRate = 0, priorWeight = 8) {
50
+ const trialCount = Math.max(0, toNumber(trials));
51
+ const successCount = Math.max(0, toNumber(successes));
52
+ const boundedPrior = clamp01(priorRate);
53
+ return safeRate(successCount + (boundedPrior * priorWeight), trialCount + priorWeight, 6);
54
+ }
55
+
56
+ function countQualifiedCreators(rows = []) {
57
+ return rows.filter((row) => {
58
+ return toNumber(row.workflowSprintLeads) > 0
59
+ || toNumber(row.qualifiedWorkflowSprintLeads) > 0
60
+ || toNumber(row.bookedRevenueCents) > 0
61
+ || toNumber(row.checkoutStarts) > 0;
62
+ }).length;
63
+ }
64
+
65
+ function buildBenchmarks({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {} } = {}) {
66
+ const uniqueVisitors = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.uniqueVisitors);
67
+ const checkoutStarts = toNumber(telemetryAnalytics.ctas && telemetryAnalytics.ctas.checkoutStarts);
68
+ const acquisitionLeads = toNumber(billingSummary.signups && billingSummary.signups.uniqueLeads);
69
+ const paidCustomers = toNumber(billingSummary.revenue && billingSummary.revenue.paidCustomers);
70
+ const bookedRevenueCents = toNumber(billingSummary.revenue && billingSummary.revenue.bookedRevenueCents);
71
+ const revenuePerPaidCents = paidCustomers > 0
72
+ ? Math.max(1, Math.round(bookedRevenueCents / paidCustomers))
73
+ : 1900;
74
+ const creatorRows = Array.isArray(stagingModel.dims && stagingModel.dims.creators)
75
+ ? stagingModel.dims.creators
76
+ : [];
77
+
78
+ return {
79
+ uniqueVisitors,
80
+ checkoutStarts,
81
+ acquisitionLeads,
82
+ paidCustomers,
83
+ bookedRevenueCents,
84
+ revenuePerPaidCents,
85
+ visitorToCheckoutRate: safeRate(checkoutStarts, uniqueVisitors, 6),
86
+ visitorToLeadRate: safeRate(acquisitionLeads, uniqueVisitors, 6),
87
+ visitorToPaidRate: safeRate(paidCustomers, uniqueVisitors, 6),
88
+ checkoutToLeadRate: safeRate(acquisitionLeads, checkoutStarts, 6),
89
+ checkoutToPaidRate: safeRate(paidCustomers, checkoutStarts, 6),
90
+ leadToPaidRate: safeRate(paidCustomers, acquisitionLeads, 6),
91
+ creatorCount: creatorRows.length,
92
+ qualifiedCreatorCount: countQualifiedCreators(creatorRows),
93
+ };
94
+ }
95
+
96
+ function buildOpportunityForecast(row, benchmarks, labelKey) {
97
+ const pageViews = toNumber(row.pageViews);
98
+ const checkoutStarts = toNumber(row.checkoutStarts);
99
+ const acquisitionLeads = toNumber(row.acquisitionLeads);
100
+ const paidCustomers = toNumber(row.paidCustomers);
101
+ const bookedRevenueCents = toNumber(row.bookedRevenueCents);
102
+ const workflowSprintLeads = toNumber(row.workflowSprintLeads);
103
+ const qualifiedWorkflowSprintLeads = toNumber(row.qualifiedWorkflowSprintLeads);
104
+
105
+ const visitorToPaid = smoothedRate(
106
+ paidCustomers,
107
+ pageViews,
108
+ benchmarks.visitorToPaidRate,
109
+ 12
110
+ );
111
+ const checkoutToPaid = smoothedRate(
112
+ paidCustomers,
113
+ checkoutStarts,
114
+ benchmarks.checkoutToPaidRate,
115
+ 8
116
+ );
117
+ const leadToPaid = smoothedRate(
118
+ paidCustomers,
119
+ acquisitionLeads,
120
+ benchmarks.leadToPaidRate,
121
+ 6
122
+ );
123
+
124
+ const predictedPaidCustomers = Math.max(
125
+ paidCustomers,
126
+ weightedAverage([
127
+ { value: pageViews * visitorToPaid, weight: pageViews > 0 ? 0.2 : 0 },
128
+ { value: checkoutStarts * checkoutToPaid, weight: checkoutStarts > 0 ? 0.45 : 0 },
129
+ { value: acquisitionLeads * leadToPaid, weight: acquisitionLeads > 0 ? 0.35 : 0 },
130
+ ])
131
+ );
132
+ const predictedBookedRevenueCents = Math.round(predictedPaidCustomers * benchmarks.revenuePerPaidCents);
133
+ const opportunityRevenueCents = Math.max(0, predictedBookedRevenueCents - bookedRevenueCents);
134
+ const sampleVolume = pageViews + checkoutStarts + (acquisitionLeads * 2) + (workflowSprintLeads * 3);
135
+ const confidence = clamp01(Math.log1p(sampleVolume) / Math.log1p(40));
136
+ const momentumScore = clamp01(weightedAverage([
137
+ { value: normalizeCapped(pageViews, 50), weight: 0.2 },
138
+ { value: normalizeCapped(checkoutStarts, 12), weight: 0.35 },
139
+ { value: normalizeCapped(acquisitionLeads, 6), weight: 0.25 },
140
+ { value: normalizeCapped(qualifiedWorkflowSprintLeads, 3), weight: 0.2 },
141
+ ]));
142
+
143
+ return {
144
+ key: row.key || 'unknown',
145
+ label: row[labelKey] || row.key || 'unknown',
146
+ pageViews,
147
+ checkoutStarts,
148
+ acquisitionLeads,
149
+ paidCustomers,
150
+ bookedRevenueCents,
151
+ workflowSprintLeads,
152
+ qualifiedWorkflowSprintLeads,
153
+ predictedPaidCustomers: Number(predictedPaidCustomers.toFixed(2)),
154
+ predictedBookedRevenueCents,
155
+ opportunityRevenueCents,
156
+ confidence: Number(confidence.toFixed(4)),
157
+ momentumScore: Number(momentumScore.toFixed(4)),
158
+ band: toBand(clamp01((confidence * 0.45) + (momentumScore * 0.55))),
159
+ };
160
+ }
161
+
162
+ function scoreDimensionForecasts(rows = [], benchmarks = {}, labelKey = 'key') {
163
+ return rows
164
+ .map((row) => buildOpportunityForecast(row, benchmarks, labelKey))
165
+ .filter((row) => row.pageViews > 0 || row.checkoutStarts > 0 || row.acquisitionLeads > 0 || row.bookedRevenueCents > 0)
166
+ .sort((left, right) => {
167
+ if (right.opportunityRevenueCents !== left.opportunityRevenueCents) {
168
+ return right.opportunityRevenueCents - left.opportunityRevenueCents;
169
+ }
170
+ if (right.predictedBookedRevenueCents !== left.predictedBookedRevenueCents) {
171
+ return right.predictedBookedRevenueCents - left.predictedBookedRevenueCents;
172
+ }
173
+ return String(left.key).localeCompare(String(right.key));
174
+ });
175
+ }
176
+
177
+ function summarizeDrivers(drivers = []) {
178
+ return drivers
179
+ .sort((left, right) => right.impact - left.impact)
180
+ .filter((driver) => driver.impact > 0)
181
+ .slice(0, 3)
182
+ .map((driver) => ({
183
+ key: driver.key,
184
+ label: driver.label,
185
+ impact: Number(driver.impact.toFixed(4)),
186
+ rawValue: driver.rawValue,
187
+ }));
188
+ }
189
+
190
+ function scoreUpgradePropensity({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {}, gateStats = {}, team = {} } = {}) {
191
+ const benchmarks = buildBenchmarks({ telemetryAnalytics, billingSummary, stagingModel });
192
+ const pricingSignals = toNumber(telemetryAnalytics.pricing && telemetryAnalytics.pricing.pricingInterestEvents);
193
+ const tooExpensiveSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.reasonsByCode && telemetryAnalytics.buyerLoss.reasonsByCode.too_expensive);
194
+ const workflowSprintLeads = toNumber(billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.total);
195
+ const qualifiedWorkflowSprintLeads = toNumber(billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.total);
196
+ const activeProKeys = toNumber(billingSummary.keys && billingSummary.keys.active);
197
+ const totalUsage = toNumber(billingSummary.keys && billingSummary.keys.totalUsage);
198
+ const blockedActions = toNumber(gateStats.blocked);
199
+ const activeAgents = toNumber(team.activeAgents);
200
+
201
+ const proDrivers = [
202
+ { key: 'checkoutStarts', label: 'checkout starts', impact: normalizeCapped(benchmarks.checkoutStarts, 12) * 0.28, rawValue: benchmarks.checkoutStarts },
203
+ { key: 'acquisitionLeads', label: 'captured leads', impact: normalizeCapped(benchmarks.acquisitionLeads, 6) * 0.2, rawValue: benchmarks.acquisitionLeads },
204
+ { key: 'visitorToCheckoutRate', label: 'visitor → checkout rate', impact: normalizeCapped(benchmarks.visitorToCheckoutRate, 0.08) * 0.18, rawValue: benchmarks.visitorToCheckoutRate },
205
+ { key: 'pricingInterest', label: 'pricing intent', impact: normalizeCapped(pricingSignals, 8) * 0.14, rawValue: pricingSignals },
206
+ { key: 'totalUsage', label: 'usage depth', impact: normalizeCapped(totalUsage, 300) * 0.12, rawValue: totalUsage },
207
+ { key: 'blockedActions', label: 'blocked mistakes', impact: normalizeCapped(blockedActions, 30) * 0.08, rawValue: blockedActions },
208
+ ];
209
+ const proPenalty = normalizeCapped(tooExpensiveSignals, 6) * 0.12;
210
+ const proScore = clamp01(proDrivers.reduce((sum, driver) => sum + driver.impact, 0) - proPenalty);
211
+
212
+ const teamDrivers = [
213
+ { key: 'qualifiedWorkflowSprintLeads', label: 'qualified workflow sprint leads', impact: normalizeCapped(qualifiedWorkflowSprintLeads, 3) * 0.3, rawValue: qualifiedWorkflowSprintLeads },
214
+ { key: 'workflowSprintLeads', label: 'workflow sprint leads', impact: normalizeCapped(workflowSprintLeads, 5) * 0.22, rawValue: workflowSprintLeads },
215
+ { key: 'activeProKeys', label: 'active Pro keys', impact: normalizeCapped(activeProKeys, 5) * 0.16, rawValue: activeProKeys },
216
+ { key: 'qualifiedCreators', label: 'qualified creators/channels', impact: normalizeCapped(benchmarks.qualifiedCreatorCount, 4) * 0.12, rawValue: benchmarks.qualifiedCreatorCount },
217
+ { key: 'activeAgents', label: 'active agents', impact: normalizeCapped(activeAgents, 6) * 0.12, rawValue: activeAgents },
218
+ { key: 'leadToPaidRate', label: 'lead → paid rate', impact: normalizeCapped(benchmarks.leadToPaidRate, 0.5) * 0.08, rawValue: benchmarks.leadToPaidRate },
219
+ ];
220
+ const teamScore = clamp01(teamDrivers.reduce((sum, driver) => sum + driver.impact, 0));
221
+
222
+ return {
223
+ pro: {
224
+ score: Number(proScore.toFixed(4)),
225
+ band: toBand(proScore),
226
+ confidence: Number(clamp01(Math.log1p(benchmarks.uniqueVisitors + benchmarks.checkoutStarts + pricingSignals) / Math.log1p(80)).toFixed(4)),
227
+ drivers: summarizeDrivers(proDrivers),
228
+ pricingResistanceSignals: tooExpensiveSignals,
229
+ },
230
+ team: {
231
+ score: Number(teamScore.toFixed(4)),
232
+ band: toBand(teamScore),
233
+ confidence: Number(clamp01(Math.log1p(workflowSprintLeads + qualifiedWorkflowSprintLeads + activeProKeys + activeAgents) / Math.log1p(24)).toFixed(4)),
234
+ drivers: summarizeDrivers(teamDrivers),
235
+ },
236
+ };
237
+ }
238
+
239
+ function detectPredictiveAnomalies({ telemetryAnalytics = {}, billingSummary = {}, creatorForecasts = [], sourceForecasts = [] } = {}) {
240
+ const anomalies = [];
241
+ const uniqueVisitors = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.uniqueVisitors);
242
+ const checkoutStarts = toNumber(telemetryAnalytics.ctas && telemetryAnalytics.ctas.checkoutStarts);
243
+ const paidCustomers = toNumber(billingSummary.revenue && billingSummary.revenue.paidCustomers);
244
+ const attributionCoverageRate = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.attributionCoverageRate);
245
+ const unreconciledPaidEvents = toNumber(billingSummary.dataQuality && billingSummary.dataQuality.unreconciledPaidEvents);
246
+ const tooExpensiveSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.reasonsByCode && telemetryAnalytics.buyerLoss.reasonsByCode.too_expensive);
247
+ const buyerLossSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.totalSignals);
248
+
249
+ if (checkoutStarts >= 3 && paidCustomers === 0) {
250
+ anomalies.push({
251
+ type: 'conversion_stall',
252
+ severity: checkoutStarts >= 8 ? 'critical' : 'warning',
253
+ message: 'Checkout starts are arriving without paid conversions.',
254
+ evidence: `checkoutStarts=${checkoutStarts}, paidCustomers=${paidCustomers}`,
255
+ });
256
+ }
257
+
258
+ if (uniqueVisitors >= 10 && attributionCoverageRate < 0.6) {
259
+ anomalies.push({
260
+ type: 'attribution_blindspot',
261
+ severity: attributionCoverageRate < 0.35 ? 'critical' : 'warning',
262
+ message: 'Attribution coverage is too low for reliable predictive routing.',
263
+ evidence: `uniqueVisitors=${uniqueVisitors}, attributionCoverageRate=${attributionCoverageRate}`,
264
+ });
265
+ }
266
+
267
+ if (unreconciledPaidEvents > 0) {
268
+ anomalies.push({
269
+ type: 'billing_reconciliation',
270
+ severity: unreconciledPaidEvents >= 3 ? 'critical' : 'warning',
271
+ message: 'Paid events are waiting on reconciliation, which weakens revenue forecasts.',
272
+ evidence: `unreconciledPaidEvents=${unreconciledPaidEvents}`,
273
+ });
274
+ }
275
+
276
+ if (buyerLossSignals >= 3 && safeRate(tooExpensiveSignals, buyerLossSignals, 4) >= 0.5) {
277
+ anomalies.push({
278
+ type: 'pricing_resistance',
279
+ severity: 'warning',
280
+ message: 'Price sensitivity dominates current loss reasons.',
281
+ evidence: `tooExpensiveSignals=${tooExpensiveSignals}, buyerLossSignals=${buyerLossSignals}`,
282
+ });
283
+ }
284
+
285
+ const underperformingCreator = creatorForecasts.find((row) => row.checkoutStarts >= 2 && row.bookedRevenueCents === 0 && row.opportunityRevenueCents >= 1500);
286
+ if (underperformingCreator) {
287
+ anomalies.push({
288
+ type: 'creator_underperformance',
289
+ severity: 'warning',
290
+ message: `Creator ${underperformingCreator.key} is generating intent without revenue conversion.`,
291
+ evidence: `checkouts=${underperformingCreator.checkoutStarts}, opportunityRevenueCents=${underperformingCreator.opportunityRevenueCents}`,
292
+ });
293
+ }
294
+
295
+ const underperformingSource = sourceForecasts.find((row) => row.checkoutStarts >= 3 && row.bookedRevenueCents === 0 && row.opportunityRevenueCents >= 1900);
296
+ if (underperformingSource) {
297
+ anomalies.push({
298
+ type: 'channel_underperformance',
299
+ severity: 'warning',
300
+ message: `Channel ${underperformingSource.key} is leaking revenue between checkout and paid.`,
301
+ evidence: `checkouts=${underperformingSource.checkoutStarts}, opportunityRevenueCents=${underperformingSource.opportunityRevenueCents}`,
302
+ });
303
+ }
304
+
305
+ return anomalies;
306
+ }
307
+
308
+ function buildPredictiveInsights({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {}, gateStats = {}, team = {} } = {}) {
309
+ const benchmarks = buildBenchmarks({ telemetryAnalytics, billingSummary, stagingModel });
310
+ const creators = scoreDimensionForecasts(stagingModel.dims && stagingModel.dims.creators ? stagingModel.dims.creators : [], benchmarks);
311
+ const sources = scoreDimensionForecasts(stagingModel.dims && stagingModel.dims.sources ? stagingModel.dims.sources : [], benchmarks);
312
+ const upgradePropensity = scoreUpgradePropensity({ telemetryAnalytics, billingSummary, stagingModel, gateStats, team });
313
+ const anomalies = detectPredictiveAnomalies({
314
+ telemetryAnalytics,
315
+ billingSummary,
316
+ creatorForecasts: creators,
317
+ sourceForecasts: sources,
318
+ });
319
+
320
+ const aggregatePredictedBookedRevenueCents = Math.max(
321
+ benchmarks.bookedRevenueCents,
322
+ Math.round(weightedAverage([
323
+ { value: benchmarks.uniqueVisitors * smoothedRate(benchmarks.paidCustomers, benchmarks.uniqueVisitors, benchmarks.visitorToPaidRate, 12) * benchmarks.revenuePerPaidCents, weight: benchmarks.uniqueVisitors > 0 ? 0.2 : 0 },
324
+ { value: benchmarks.checkoutStarts * smoothedRate(benchmarks.paidCustomers, benchmarks.checkoutStarts, benchmarks.checkoutToPaidRate, 8) * benchmarks.revenuePerPaidCents, weight: benchmarks.checkoutStarts > 0 ? 0.45 : 0 },
325
+ { value: benchmarks.acquisitionLeads * smoothedRate(benchmarks.paidCustomers, benchmarks.acquisitionLeads, benchmarks.leadToPaidRate, 6) * benchmarks.revenuePerPaidCents, weight: benchmarks.acquisitionLeads > 0 ? 0.35 : 0 },
326
+ ]))
327
+ );
328
+
329
+ return {
330
+ generatedAt: new Date().toISOString(),
331
+ modelVersion: 'predictive-insights-v1',
332
+ benchmarks,
333
+ upgradePropensity,
334
+ revenueForecast: {
335
+ predictedBookedRevenueCents: aggregatePredictedBookedRevenueCents,
336
+ incrementalOpportunityCents: Math.max(0, aggregatePredictedBookedRevenueCents - benchmarks.bookedRevenueCents),
337
+ confidence: Number(clamp01((upgradePropensity.pro.confidence + upgradePropensity.team.confidence) / 2).toFixed(4)),
338
+ band: toBand(clamp01((upgradePropensity.pro.score * 0.55) + (upgradePropensity.team.score * 0.45))),
339
+ },
340
+ topCreators: creators.slice(0, 5),
341
+ topSources: sources.slice(0, 5),
342
+ anomalies,
343
+ anomalySummary: {
344
+ count: anomalies.length,
345
+ severity: summarizeSeverity(anomalies),
346
+ },
347
+ };
348
+ }
349
+
350
+ module.exports = {
351
+ buildBenchmarks,
352
+ buildPredictiveInsights,
353
+ detectPredictiveAnomalies,
354
+ scoreDimensionForecasts,
355
+ scoreUpgradePropensity,
356
+ };
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * MemAlign-style principle extraction from NL feedback.
5
+ *
6
+ * Distills whatWentWrong / whatToChange / whatWorked into reusable
7
+ * NEVER/ALWAYS semantic principles stored in principles.jsonl.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { getFeedbackPaths, readJSONL, inferDomain } = require('./feedback-loop');
13
+
14
+ const PRINCIPLES_FILENAME = 'principles.jsonl';
15
+
16
+ /**
17
+ * Extract a semantic principle from a single feedback entry.
18
+ *
19
+ * @param {object} entry - A feedback log entry.
20
+ * @returns {object|null} A principle object or null if nothing extractable.
21
+ */
22
+ function extractPrinciple(entry) {
23
+ if (!entry) return null;
24
+
25
+ const signal = String(entry.signal || '').toLowerCase();
26
+ const isNegative = ['negative', 'down', 'thumbs-down', 'thumbsdown'].includes(signal);
27
+ const isPositive = ['positive', 'up', 'thumbs-up', 'thumbsup'].includes(signal);
28
+
29
+ if (!isNegative && !isPositive) return null;
30
+
31
+ const tags = Array.isArray(entry.tags) ? entry.tags : [];
32
+ const domain = inferDomain(tags, entry.context || '');
33
+
34
+ if (isNegative) {
35
+ const wrong = (entry.whatWentWrong || '').trim();
36
+ const change = (entry.whatToChange || '').trim();
37
+ if (!wrong && !change) return null;
38
+
39
+ const text = change
40
+ ? `NEVER: ${wrong || change}. INSTEAD: ${change}`
41
+ : `NEVER: ${wrong}`;
42
+
43
+ return {
44
+ id: `prin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
45
+ type: 'constraint',
46
+ polarity: 'negative',
47
+ text,
48
+ source: wrong,
49
+ correction: change || null,
50
+ tags,
51
+ domain,
52
+ sourceCount: 1,
53
+ createdAt: new Date().toISOString(),
54
+ };
55
+ }
56
+
57
+ // Positive
58
+ const worked = (entry.whatWorked || '').trim();
59
+ if (!worked) return null;
60
+
61
+ return {
62
+ id: `prin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
63
+ type: 'heuristic',
64
+ polarity: 'positive',
65
+ text: `ALWAYS: ${worked}`,
66
+ source: worked,
67
+ correction: null,
68
+ tags,
69
+ domain,
70
+ sourceCount: 1,
71
+ createdAt: new Date().toISOString(),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Batch-extract principles from a feedback log, deduplicating by text.
77
+ *
78
+ * @param {string} [logPath] - Path to feedback-log.jsonl.
79
+ * @param {string} [principlesPath] - Path to write principles.jsonl.
80
+ * @returns {{ created: number, updated: number, total: number }}
81
+ */
82
+ function extractAllPrinciples(logPath, principlesPath) {
83
+ const paths = getFeedbackPaths();
84
+ const feedbackLog = logPath || paths.FEEDBACK_LOG_PATH;
85
+ const outPath = principlesPath || path.join(path.dirname(feedbackLog), PRINCIPLES_FILENAME);
86
+
87
+ const entries = readJSONL(feedbackLog);
88
+ const existing = readJSONL(outPath);
89
+
90
+ // Index existing by normalized text for dedup
91
+ const byText = new Map();
92
+ for (const p of existing) {
93
+ if (p && p.text) byText.set(p.text, p);
94
+ }
95
+
96
+ let created = 0;
97
+ let updated = 0;
98
+
99
+ for (const entry of entries) {
100
+ const principle = extractPrinciple(entry);
101
+ if (!principle) continue;
102
+
103
+ const match = byText.get(principle.text);
104
+ if (match) {
105
+ match.sourceCount = (match.sourceCount || 1) + 1;
106
+ updated++;
107
+ } else {
108
+ byText.set(principle.text, principle);
109
+ created++;
110
+ }
111
+ }
112
+
113
+ // Write all principles
114
+ const dir = path.dirname(outPath);
115
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
116
+ const lines = [...byText.values()].map((p) => JSON.stringify(p)).join('\n');
117
+ fs.writeFileSync(outPath, lines ? `${lines}\n` : '');
118
+
119
+ return { created, updated, total: byText.size };
120
+ }
121
+
122
+ /**
123
+ * Retrieve principles with optional filtering.
124
+ *
125
+ * @param {object} opts
126
+ * @param {string[]} [opts.tags] - Filter by tags (any match).
127
+ * @param {string} [opts.domain] - Filter by domain.
128
+ * @param {number} [opts.limit] - Max results.
129
+ * @param {string} [opts.principlesPath] - Path to principles.jsonl.
130
+ * @returns {object[]} Matching principles.
131
+ */
132
+ function getPrinciples({ tags, domain, limit, principlesPath } = {}) {
133
+ const paths = getFeedbackPaths();
134
+ const filePath = principlesPath || path.join(paths.FEEDBACK_DIR, PRINCIPLES_FILENAME);
135
+ const all = readJSONL(filePath);
136
+
137
+ let results = all;
138
+
139
+ if (tags && tags.length > 0) {
140
+ const tagSet = new Set(tags.map((t) => t.toLowerCase()));
141
+ results = results.filter((p) =>
142
+ Array.isArray(p.tags) && p.tags.some((t) => tagSet.has(t.toLowerCase()))
143
+ );
144
+ }
145
+
146
+ if (domain) {
147
+ results = results.filter((p) => p.domain === domain);
148
+ }
149
+
150
+ if (typeof limit === 'number' && limit > 0) {
151
+ results = results.slice(0, limit);
152
+ }
153
+
154
+ return results;
155
+ }
156
+
157
+ module.exports = {
158
+ PRINCIPLES_FILENAME,
159
+ extractPrinciple,
160
+ extractAllPrinciples,
161
+ getPrinciples,
162
+ };
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+ const { isProLicensed } = require('./license');
3
+ const {
4
+ PRO_MONTHLY_PAYMENT_LINK,
5
+ PRO_PRICE_LABEL,
6
+ } = require('./commercial-offer');
7
+
8
+ const PRO_URL = PRO_MONTHLY_PAYMENT_LINK;
9
+
10
+ function requirePro(
11
+ featureName,
12
+ {
13
+ isProLicensedFn = isProLicensed,
14
+ write = (message) => process.stderr.write(message),
15
+ } = {}
16
+ ) {
17
+ if (isProLicensedFn()) return true;
18
+ const descriptions = {
19
+ 'dpo-export': 'Export feedback as DPO training pairs for model fine-tuning',
20
+ 'dpo-synthesis': 'Generate synthetic DPO pairs from existing feedback patterns',
21
+ 'multi-hop-recall': 'Multi-hop recall — chain related lessons for deeper context',
22
+ 'databricks-export': 'Export to Databricks ML pipeline format',
23
+ 'dashboard-search': 'Search, filter, and edit lessons across all repos',
24
+ 'multi-repo-sync': 'Sync prevention rules across multiple repositories',
25
+ 'custom-gates': 'Create custom pre-action gates beyond the defaults',
26
+ 'advanced-thompson': 'Advanced Thompson Sampling with custom priors and decay',
27
+ 'rule-analytics': 'Analytics on which rules fire most and their block rates',
28
+ 'team-sharing': 'Share lesson databases across team members',
29
+ };
30
+ const desc = descriptions[featureName] || featureName;
31
+ write(
32
+ `\n 🔒 Pro Feature Required: ${desc}\n` +
33
+ ` Upgrade to ThumbGate Pro — ${PRO_PRICE_LABEL}:\n` +
34
+ ` ${PRO_URL}\n` +
35
+ ` Or run: npx thumbgate pro\n\n`
36
+ );
37
+ return false;
38
+ }
39
+
40
+ module.exports = { requirePro, PRO_URL };