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,447 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * Version Sync — Single Source of Truth
5
+ *
6
+ * Reads the version from package.json and propagates it to all
7
+ * manifests and public docs. Eliminates version drift permanently.
8
+ *
9
+ * Inspired by the "Pipeline Doctor" pattern (Optimum Partners, 2026)
10
+ * and OneUptime's automated version bumping approach.
11
+ *
12
+ * Usage:
13
+ * node scripts/sync-version.js # Sync all files
14
+ * node scripts/sync-version.js --check # Dry-run: report drift without fixing
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const PROJECT_ROOT = path.join(__dirname, '..');
21
+
22
+ function readJson(relPath) {
23
+ return JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, relPath), 'utf-8'));
24
+ }
25
+
26
+ function writeJson(relPath, data) {
27
+ fs.writeFileSync(path.join(PROJECT_ROOT, relPath), JSON.stringify(data, null, 2) + '\n');
28
+ }
29
+
30
+ function replaceInFile(relPath, search, replace) {
31
+ const filePath = path.join(PROJECT_ROOT, relPath);
32
+ if (!fs.existsSync(filePath)) return false;
33
+ const content = fs.readFileSync(filePath, 'utf-8');
34
+ if (!content.includes(search)) return false;
35
+ fs.writeFileSync(filePath, content.split(search).join(replace));
36
+ return true;
37
+ }
38
+
39
+ function syncJsonField(target, field, expectedValue, drifted, checkOnly) {
40
+ if (field !== 'version' && field !== 'homepage' && field !== 'repository') return false;
41
+ if (target[field] === expectedValue) return false;
42
+ drifted.push({ file: target.__file, field, current: target[field] });
43
+ if (!checkOnly) target[field] = expectedValue;
44
+ return true;
45
+ }
46
+
47
+ function syncVersion(opts) {
48
+ const options = opts || {};
49
+ const checkOnly = options.check || false;
50
+ const pkg = readJson('package.json');
51
+ const version = pkg.version;
52
+ const homepageUrl = pkg.homepage;
53
+ const repositoryUrl = pkg.repository && pkg.repository.url ? String(pkg.repository.url).replace(/\.git$/, '') : '';
54
+
55
+ const targets = [];
56
+ const drifted = [];
57
+
58
+ // 1. package-lock.json — top-level version metadata
59
+ const packageLockPath = 'package-lock.json';
60
+ if (fs.existsSync(path.join(PROJECT_ROOT, packageLockPath))) {
61
+ const packageLock = readJson(packageLockPath);
62
+ if (packageLock.version !== version) {
63
+ drifted.push({ file: packageLockPath, field: 'version', current: packageLock.version });
64
+ if (!checkOnly) {
65
+ packageLock.version = version;
66
+ }
67
+ }
68
+ if (packageLock.packages && packageLock.packages[''] && packageLock.packages[''].version !== version) {
69
+ drifted.push({ file: packageLockPath, field: 'packages[""].version', current: packageLock.packages[''].version });
70
+ if (!checkOnly) {
71
+ packageLock.packages[''].version = version;
72
+ }
73
+ }
74
+ if (!checkOnly && drifted.some((entry) => entry.file === packageLockPath)) {
75
+ writeJson(packageLockPath, packageLock);
76
+ }
77
+ targets.push(packageLockPath);
78
+ }
79
+
80
+ // 2. server.json — top-level version + packages[0].version
81
+ const serverJson = readJson('server.json');
82
+ if (serverJson.version !== version) {
83
+ drifted.push({ file: 'server.json', field: 'version', current: serverJson.version });
84
+ if (!checkOnly) {
85
+ serverJson.version = version;
86
+ if (serverJson.packages && serverJson.packages[0]) {
87
+ serverJson.packages[0].version = version;
88
+ }
89
+ writeJson('server.json', serverJson);
90
+ }
91
+ } else if (serverJson.packages && serverJson.packages[0] && serverJson.packages[0].version !== version) {
92
+ drifted.push({ file: 'server.json', field: 'packages[0].version', current: serverJson.packages[0].version });
93
+ if (!checkOnly) {
94
+ serverJson.packages[0].version = version;
95
+ writeJson('server.json', serverJson);
96
+ }
97
+ }
98
+ targets.push('server.json');
99
+
100
+ // 3. .well-known/mcp/server-card.json
101
+ const cardPath = '.well-known/mcp/server-card.json';
102
+ if (fs.existsSync(path.join(PROJECT_ROOT, cardPath))) {
103
+ const card = readJson(cardPath);
104
+ if (card.version !== version) {
105
+ drifted.push({ file: cardPath, field: 'version', current: card.version });
106
+ if (!checkOnly) {
107
+ card.version = version;
108
+ writeJson(cardPath, card);
109
+ }
110
+ }
111
+ targets.push(cardPath);
112
+ }
113
+
114
+ // 4. .claude-plugin/plugin.json
115
+ const claudePluginPath = '.claude-plugin/plugin.json';
116
+ if (fs.existsSync(path.join(PROJECT_ROOT, claudePluginPath))) {
117
+ const claudePlugin = readJson(claudePluginPath);
118
+ claudePlugin.__file = claudePluginPath;
119
+ const changed = [
120
+ syncJsonField(claudePlugin, 'version', version, drifted, checkOnly),
121
+ syncJsonField(claudePlugin, 'homepage', homepageUrl, drifted, checkOnly),
122
+ syncJsonField(claudePlugin, 'repository', repositoryUrl, drifted, checkOnly),
123
+ ].some(Boolean);
124
+ delete claudePlugin.__file;
125
+ if (!checkOnly && changed) {
126
+ writeJson(claudePluginPath, claudePlugin);
127
+ }
128
+ targets.push(claudePluginPath);
129
+ }
130
+
131
+ // 5. .claude-plugin/marketplace.json
132
+ const claudeMarketplacePath = '.claude-plugin/marketplace.json';
133
+ if (fs.existsSync(path.join(PROJECT_ROOT, claudeMarketplacePath))) {
134
+ const claudeMarketplace = readJson(claudeMarketplacePath);
135
+ let changed = false;
136
+ if (claudeMarketplace.version !== version) {
137
+ drifted.push({ file: claudeMarketplacePath, field: 'version', current: claudeMarketplace.version });
138
+ if (!checkOnly) {
139
+ claudeMarketplace.version = version;
140
+ changed = true;
141
+ }
142
+ }
143
+ const currentHomepage = claudeMarketplace.plugins && claudeMarketplace.plugins[0] && claudeMarketplace.plugins[0].metadata
144
+ ? claudeMarketplace.plugins[0].metadata.homepage
145
+ : undefined;
146
+ if (currentHomepage !== homepageUrl) {
147
+ drifted.push({ file: claudeMarketplacePath, field: 'plugins[0].metadata.homepage', current: currentHomepage });
148
+ if (!checkOnly && claudeMarketplace.plugins && claudeMarketplace.plugins[0]) {
149
+ claudeMarketplace.plugins[0].metadata = claudeMarketplace.plugins[0].metadata || {};
150
+ claudeMarketplace.plugins[0].metadata.homepage = homepageUrl;
151
+ changed = true;
152
+ }
153
+ }
154
+ if (!checkOnly && changed) {
155
+ writeJson(claudeMarketplacePath, claudeMarketplace);
156
+ }
157
+ targets.push(claudeMarketplacePath);
158
+ }
159
+
160
+ // 6. root Cursor marketplace manifest
161
+ const cursorMarketplacePath = '.cursor-plugin/marketplace.json';
162
+ if (fs.existsSync(path.join(PROJECT_ROOT, cursorMarketplacePath))) {
163
+ const cursorMarketplace = readJson(cursorMarketplacePath);
164
+ const current = cursorMarketplace.metadata && cursorMarketplace.metadata.version;
165
+ if (current !== version) {
166
+ drifted.push({ file: cursorMarketplacePath, field: 'metadata.version', current });
167
+ if (!checkOnly) {
168
+ cursorMarketplace.metadata = cursorMarketplace.metadata || {};
169
+ cursorMarketplace.metadata.version = version;
170
+ writeJson(cursorMarketplacePath, cursorMarketplace);
171
+ }
172
+ }
173
+ targets.push(cursorMarketplacePath);
174
+ }
175
+
176
+ // 7. plugin Cursor manifest
177
+ const cursorPluginManifestPath = 'plugins/cursor-marketplace/.cursor-plugin/plugin.json';
178
+ if (fs.existsSync(path.join(PROJECT_ROOT, cursorPluginManifestPath))) {
179
+ const cursorPlugin = readJson(cursorPluginManifestPath);
180
+ cursorPlugin.__file = cursorPluginManifestPath;
181
+ const changed = [
182
+ syncJsonField(cursorPlugin, 'version', version, drifted, checkOnly),
183
+ syncJsonField(cursorPlugin, 'homepage', homepageUrl, drifted, checkOnly),
184
+ syncJsonField(cursorPlugin, 'repository', repositoryUrl, drifted, checkOnly),
185
+ ].some(Boolean);
186
+ delete cursorPlugin.__file;
187
+ if (!checkOnly && changed) {
188
+ writeJson(cursorPluginManifestPath, cursorPlugin);
189
+ }
190
+ targets.push(cursorPluginManifestPath);
191
+ }
192
+
193
+ // 8. Codex plugin manifest + MCP config
194
+ const codexPluginManifestPath = 'plugins/codex-profile/.codex-plugin/plugin.json';
195
+ if (fs.existsSync(path.join(PROJECT_ROOT, codexPluginManifestPath))) {
196
+ const codexPlugin = readJson(codexPluginManifestPath);
197
+ codexPlugin.__file = codexPluginManifestPath;
198
+ const changed = [
199
+ syncJsonField(codexPlugin, 'version', version, drifted, checkOnly),
200
+ syncJsonField(codexPlugin, 'homepage', homepageUrl, drifted, checkOnly),
201
+ syncJsonField(codexPlugin, 'repository', repositoryUrl, drifted, checkOnly),
202
+ ].some(Boolean);
203
+ delete codexPlugin.__file;
204
+ if (!checkOnly && changed) {
205
+ writeJson(codexPluginManifestPath, codexPlugin);
206
+ }
207
+ targets.push(codexPluginManifestPath);
208
+ }
209
+
210
+ const codexPluginConfigPath = 'plugins/codex-profile/.mcp.json';
211
+ if (fs.existsSync(path.join(PROJECT_ROOT, codexPluginConfigPath))) {
212
+ const codexPluginConfig = readJson(codexPluginConfigPath);
213
+ const server = codexPluginConfig.mcpServers && codexPluginConfig.mcpServers.rlhf;
214
+ const expectedArgs = ['-y', `thumbgate@${version}`, 'serve'];
215
+ const currentArgs = server && Array.isArray(server.args) ? server.args : [];
216
+ if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
217
+ drifted.push({ file: codexPluginConfigPath, field: 'mcpServers.rlhf.args', current: JSON.stringify(currentArgs) });
218
+ if (!checkOnly) {
219
+ server.args = expectedArgs.slice();
220
+ writeJson(codexPluginConfigPath, codexPluginConfig);
221
+ }
222
+ }
223
+ targets.push(codexPluginConfigPath);
224
+ }
225
+
226
+ const claudeCodexBridgeManifestPath = 'plugins/claude-codex-bridge/.claude-plugin/plugin.json';
227
+ if (fs.existsSync(path.join(PROJECT_ROOT, claudeCodexBridgeManifestPath))) {
228
+ const bridgePlugin = readJson(claudeCodexBridgeManifestPath);
229
+ bridgePlugin.__file = claudeCodexBridgeManifestPath;
230
+ const changed = [
231
+ syncJsonField(bridgePlugin, 'version', version, drifted, checkOnly),
232
+ syncJsonField(bridgePlugin, 'homepage', homepageUrl, drifted, checkOnly),
233
+ syncJsonField(bridgePlugin, 'repository', repositoryUrl, drifted, checkOnly),
234
+ ].some(Boolean);
235
+ delete bridgePlugin.__file;
236
+ if (!checkOnly && changed) {
237
+ writeJson(claudeCodexBridgeManifestPath, bridgePlugin);
238
+ }
239
+ targets.push(claudeCodexBridgeManifestPath);
240
+ }
241
+
242
+ const claudeCodexBridgeConfigPath = 'plugins/claude-codex-bridge/.mcp.json';
243
+ if (fs.existsSync(path.join(PROJECT_ROOT, claudeCodexBridgeConfigPath))) {
244
+ const bridgeConfig = readJson(claudeCodexBridgeConfigPath);
245
+ const server = bridgeConfig.mcpServers && bridgeConfig.mcpServers.rlhf;
246
+ const expectedArgs = ['-y', `thumbgate@${version}`, 'serve'];
247
+ const currentArgs = server && Array.isArray(server.args) ? server.args : [];
248
+ if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
249
+ drifted.push({ file: claudeCodexBridgeConfigPath, field: 'mcpServers.rlhf.args', current: JSON.stringify(currentArgs) });
250
+ if (!checkOnly) {
251
+ server.args = expectedArgs.slice();
252
+ writeJson(claudeCodexBridgeConfigPath, bridgeConfig);
253
+ }
254
+ }
255
+ targets.push(claudeCodexBridgeConfigPath);
256
+ }
257
+
258
+ // 9. plugin Cursor MCP config
259
+ const cursorPluginConfigPath = 'plugins/cursor-marketplace/mcp.json';
260
+ if (fs.existsSync(path.join(PROJECT_ROOT, cursorPluginConfigPath))) {
261
+ const cursorPluginConfig = readJson(cursorPluginConfigPath);
262
+ const server = cursorPluginConfig.mcpServers && cursorPluginConfig.mcpServers.rlhf;
263
+ const expectedArgs = ['-y', 'thumbgate@latest', 'serve'];
264
+ const currentArgs = server && Array.isArray(server.args) ? server.args : [];
265
+ if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
266
+ drifted.push({ file: cursorPluginConfigPath, field: 'mcpServers.rlhf.args', current: JSON.stringify(currentArgs) });
267
+ if (!checkOnly) {
268
+ server.args = expectedArgs.slice();
269
+ writeJson(cursorPluginConfigPath, cursorPluginConfig);
270
+ }
271
+ }
272
+ targets.push(cursorPluginConfigPath);
273
+ }
274
+
275
+ // 10. docs/install files that pin the npm package version
276
+ const pinnedPackageTargets = [
277
+ 'adapters/claude/.mcp.json',
278
+ 'docs/PLUGIN_DISTRIBUTION.md',
279
+ 'adapters/README.md',
280
+ 'adapters/opencode/opencode.json',
281
+ 'docs/guides/opencode-integration.md',
282
+ 'docs/mcp-hub-submission.md',
283
+ 'docs/VERIFICATION_EVIDENCE.md',
284
+ 'plugins/claude-codex-bridge/README.md',
285
+ 'plugins/claude-codex-bridge/INSTALL.md',
286
+ 'plugins/codex-profile/README.md',
287
+ 'plugins/codex-profile/INSTALL.md',
288
+ 'plugins/opencode-profile/INSTALL.md',
289
+ ];
290
+ const pinnedPackagePattern = /thumbgate@\d+\.\d+\.\d+/g;
291
+ for (const relPath of pinnedPackageTargets) {
292
+ const filePath = path.join(PROJECT_ROOT, relPath);
293
+ if (!fs.existsSync(filePath)) continue;
294
+ const content = fs.readFileSync(filePath, 'utf-8');
295
+ const matches = content.match(pinnedPackagePattern) || [];
296
+ const hasDrift = matches.some((match) => match !== `thumbgate@${version}`);
297
+ if (hasDrift) {
298
+ drifted.push({ file: relPath, field: 'package-version-string', current: matches.join(', ') });
299
+ if (!checkOnly) {
300
+ fs.writeFileSync(filePath, content.replace(pinnedPackagePattern, `thumbgate@${version}`));
301
+ }
302
+ }
303
+ targets.push(relPath);
304
+ }
305
+
306
+ // 11. docs/landing-page.html — hero badge + JSON snippet
307
+ const landingPath = 'docs/landing-page.html';
308
+ if (fs.existsSync(path.join(PROJECT_ROOT, landingPath))) {
309
+ const landingContent = fs.readFileSync(path.join(PROJECT_ROOT, landingPath), 'utf-8');
310
+ // Match any version pattern in the hero badge
311
+ const badgeMatch = landingContent.match(/v(\d+\.\d+\.\d+) — Hosted API/);
312
+ if (badgeMatch && badgeMatch[1] !== version) {
313
+ drifted.push({ file: landingPath, field: 'hero-badge', current: badgeMatch[1] });
314
+ if (!checkOnly) {
315
+ replaceInFile(landingPath, `v${badgeMatch[1]} — Hosted API`, `v${version} — Hosted API`);
316
+ }
317
+ }
318
+ // JSON snippet version
319
+ const jsonMatch = landingContent.match(/"version"<\/span><span class="out">: <\/span><span class="val">"(\d+\.\d+\.\d+)"/);
320
+ if (jsonMatch && jsonMatch[1] !== version) {
321
+ drifted.push({ file: landingPath, field: 'json-snippet', current: jsonMatch[1] });
322
+ if (!checkOnly) {
323
+ replaceInFile(landingPath, `"${jsonMatch[1]}"</div>`, `"${version}"</div>`);
324
+ }
325
+ }
326
+ targets.push(landingPath);
327
+ }
328
+
329
+ // 12. docs/mcp-hub-submission.md
330
+ const mcpSubmPath = 'docs/mcp-hub-submission.md';
331
+ if (fs.existsSync(path.join(PROJECT_ROOT, mcpSubmPath))) {
332
+ const mcpContent = fs.readFileSync(path.join(PROJECT_ROOT, mcpSubmPath), 'utf-8');
333
+ const versionMatch = mcpContent.match(/## Version\s+(\d+\.\d+\.\d+)/);
334
+ if (versionMatch && versionMatch[1] !== version) {
335
+ drifted.push({ file: mcpSubmPath, field: 'version-heading', current: versionMatch[1] });
336
+ if (!checkOnly) {
337
+ replaceInFile(mcpSubmPath, versionMatch[1], version);
338
+ }
339
+ }
340
+ targets.push(mcpSubmPath);
341
+ }
342
+
343
+ // 13. public/index.html — static landing proof pill + footer version
344
+ const publicIndexPath = 'public/index.html';
345
+ if (fs.existsSync(path.join(PROJECT_ROOT, publicIndexPath))) {
346
+ const publicContent = fs.readFileSync(path.join(PROJECT_ROOT, publicIndexPath), 'utf-8');
347
+ const heroVersionMatch = publicContent.match(/New in v(\d+\.\d+\.\d+):/);
348
+ if (heroVersionMatch && heroVersionMatch[1] !== version) {
349
+ drifted.push({ file: publicIndexPath, field: 'hero-release-note', current: heroVersionMatch[1] });
350
+ if (!checkOnly) {
351
+ replaceInFile(publicIndexPath, `New in v${heroVersionMatch[1]}:`, `New in v${version}:`);
352
+ }
353
+ }
354
+
355
+ const proofMatch = publicContent.match(/Versioned proof: v(\d+\.\d+\.\d+)/);
356
+ if (proofMatch && proofMatch[1] !== version) {
357
+ drifted.push({ file: publicIndexPath, field: 'proof-pill', current: proofMatch[1] });
358
+ if (!checkOnly) {
359
+ replaceInFile(publicIndexPath, `Versioned proof: v${proofMatch[1]}`, `Versioned proof: v${version}`);
360
+ }
361
+ }
362
+
363
+ const footerMatch = publicContent.match(/Context Gateway • v(\d+\.\d+\.\d+)/);
364
+ if (footerMatch && footerMatch[1] !== version) {
365
+ drifted.push({ file: publicIndexPath, field: 'footer-version', current: footerMatch[1] });
366
+ if (!checkOnly) {
367
+ replaceInFile(publicIndexPath, `Context Gateway • v${footerMatch[1]}`, `Context Gateway • v${version}`);
368
+ }
369
+ }
370
+ targets.push(publicIndexPath);
371
+ }
372
+
373
+ // 14. adapters/mcp/server-stdio.js — MCP server metadata
374
+ const serverStdioPath = 'adapters/mcp/server-stdio.js';
375
+ const serverStdioFile = path.join(PROJECT_ROOT, serverStdioPath);
376
+ if (fs.existsSync(serverStdioFile)) {
377
+ const serverStdioContent = fs.readFileSync(serverStdioFile, 'utf-8');
378
+ const serverInfoMatch = serverStdioContent.match(/version:\s*'(\d+\.\d+\.\d+)'/);
379
+ if (serverInfoMatch && serverInfoMatch[1] !== version) {
380
+ drifted.push({ file: serverStdioPath, field: 'server-info-version', current: serverInfoMatch[1] });
381
+ if (!checkOnly) {
382
+ fs.writeFileSync(
383
+ serverStdioFile,
384
+ serverStdioContent.replace(/version:\s*'\d+\.\d+\.\d+'/, `version: '${version}'`)
385
+ );
386
+ }
387
+ }
388
+ targets.push(serverStdioPath);
389
+ }
390
+
391
+ // 15. mcpize.yaml
392
+ const mcpizePath = 'mcpize.yaml';
393
+ const mcpizeFile = path.join(PROJECT_ROOT, mcpizePath);
394
+ if (fs.existsSync(mcpizeFile)) {
395
+ const mcpizeContent = fs.readFileSync(mcpizeFile, 'utf-8');
396
+ const mcpizeVersionRegex = /^version:\s*"[^"]+"/m;
397
+ const mcpizeExpected = `version: "${version}"`;
398
+ if (!mcpizeContent.includes(mcpizeExpected)) {
399
+ drifted.push({ file: mcpizePath, field: 'version', current: mcpizeContent.match(/version:\s*"([^"]+)"/)?.[1] || 'unknown' });
400
+ if (!checkOnly) {
401
+ fs.writeFileSync(mcpizeFile, mcpizeContent.replace(mcpizeVersionRegex, mcpizeExpected));
402
+ }
403
+ }
404
+ targets.push(mcpizePath);
405
+ }
406
+
407
+ return {
408
+ version,
409
+ targets,
410
+ drifted,
411
+ synced: !checkOnly && drifted.length > 0,
412
+ allInSync: drifted.length === 0,
413
+ };
414
+ }
415
+
416
+ // ---------------------------------------------------------------------------
417
+ // CLI
418
+ // ---------------------------------------------------------------------------
419
+
420
+ if (require.main === module) {
421
+ const checkOnly = process.argv.includes('--check');
422
+ const result = syncVersion({ check: checkOnly });
423
+
424
+ if (result.allInSync) {
425
+ console.log(`✔ All ${result.targets.length} targets in sync at v${result.version}`);
426
+ process.exit(0);
427
+ }
428
+
429
+ if (checkOnly) {
430
+ console.error(`✖ Version drift detected (package.json = ${result.version}):`);
431
+ result.drifted.forEach((d) => {
432
+ console.error(` ${d.file} [${d.field}] = ${d.current}`);
433
+ });
434
+ process.exit(1);
435
+ }
436
+
437
+ console.log(`✔ Synced ${result.drifted.length} targets to v${result.version}:`);
438
+ result.drifted.forEach((d) => {
439
+ console.log(` ${d.file} [${d.field}]: ${d.current} → ${result.version}`);
440
+ });
441
+ }
442
+
443
+ // ---------------------------------------------------------------------------
444
+ // Exports
445
+ // ---------------------------------------------------------------------------
446
+
447
+ module.exports = { syncVersion };
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Synthetic DPO Pair Augmentation — expands real feedback into larger training datasets.
6
+ *
7
+ * Takes existing DPO pairs (from export-dpo-pairs.js) and generates synthetic
8
+ * variations to increase dataset size for fine-tuning:
9
+ *
10
+ * 1. Principle extraction: generalize specific errors into abstract rules
11
+ * 2. Contrastive pairing: match unpaired errors/learnings by domain similarity
12
+ * 3. Scenario variation: rephrase prompts for the same chosen/rejected pair
13
+ *
14
+ * Inspired by Chroma Context-1's scalable synthetic task generation.
15
+ * Pro-only feature — gated via requirePro('dpo-synthesis').
16
+ *
17
+ * @module synthetic-dpo
18
+ */
19
+
20
+ const { requirePro } = require('./pro-features');
21
+ const { extractDomainKeys, domainOverlap, inferPrompt, buildRubricDelta } = require('./export-dpo-pairs');
22
+
23
+ /**
24
+ * Extract an abstract principle from an error+learning pair.
25
+ * Turns "never run DROP on production tables" into a generalized rule.
26
+ */
27
+ function extractPrinciple(pair) {
28
+ const rejected = pair.rejected || '';
29
+ const chosen = pair.chosen || '';
30
+ const matchedKeys = pair.metadata?.matchedKeys || [];
31
+
32
+ // Build principle from the domain keys and the contrast
33
+ const domain = matchedKeys.length > 0
34
+ ? matchedKeys.join(', ')
35
+ : 'general';
36
+
37
+ // Extract the "don't do X, do Y instead" pattern
38
+ const dontDo = rejected.length > 120
39
+ ? rejected.slice(0, 120).trim() + '...'
40
+ : rejected;
41
+ const doInstead = chosen.length > 120
42
+ ? chosen.slice(0, 120).trim() + '...'
43
+ : chosen;
44
+
45
+ return {
46
+ domain,
47
+ anti_pattern: dontDo,
48
+ correct_pattern: doInstead,
49
+ principle: `In ${domain} tasks: avoid "${truncate(dontDo, 60)}" — instead "${truncate(doInstead, 60)}"`,
50
+ };
51
+ }
52
+
53
+ function truncate(str, maxLen) {
54
+ if (str.length <= maxLen) return str;
55
+ return str.slice(0, maxLen - 3) + '...';
56
+ }
57
+
58
+ /**
59
+ * Generate scenario variations for a DPO pair by rephrasing the prompt.
60
+ * Creates 1-2 synthetic pairs with different prompt framings.
61
+ */
62
+ function generateScenarioVariations(pair) {
63
+ const variations = [];
64
+ const matchedKeys = pair.metadata?.matchedKeys || [];
65
+ const domain = matchedKeys.join(', ') || 'this domain';
66
+
67
+ // Variation 1: "What should be avoided?" framing
68
+ variations.push({
69
+ prompt: `In ${domain}: what is the wrong approach and what should be done instead?`,
70
+ chosen: pair.chosen,
71
+ rejected: pair.rejected,
72
+ metadata: {
73
+ ...pair.metadata,
74
+ synthetic: true,
75
+ syntheticType: 'scenario_variation',
76
+ syntheticVariant: 'avoidance_framing',
77
+ sourceId: pair.metadata?.errorId,
78
+ },
79
+ });
80
+
81
+ // Variation 2: "Best practice" framing (only if we have enough context)
82
+ if (matchedKeys.length > 0) {
83
+ variations.push({
84
+ prompt: `What is the best practice for ${domain}? Compare the correct approach with a common mistake.`,
85
+ chosen: pair.chosen,
86
+ rejected: pair.rejected,
87
+ metadata: {
88
+ ...pair.metadata,
89
+ synthetic: true,
90
+ syntheticType: 'scenario_variation',
91
+ syntheticVariant: 'best_practice_framing',
92
+ sourceId: pair.metadata?.errorId,
93
+ },
94
+ });
95
+ }
96
+
97
+ return variations;
98
+ }
99
+
100
+ /**
101
+ * Build contrastive pairs from unpaired errors and learnings.
102
+ * Uses softer domain matching (single key overlap) to find weak matches
103
+ * that wouldn't qualify for primary pairing but are useful for training.
104
+ */
105
+ function buildContrastivePairs(unpairedErrors, unpairedLearnings) {
106
+ const pairs = [];
107
+ const usedErrors = new Set();
108
+ const usedLearnings = new Set();
109
+
110
+ const errorKeys = unpairedErrors.map((e) => ({ memory: e, keys: extractDomainKeys(e) }));
111
+ const learningKeys = unpairedLearnings.map((l) => ({ memory: l, keys: extractDomainKeys(l) }));
112
+
113
+ // Relaxed matching: 1+ key overlap (primary pairing requires higher scores)
114
+ for (const err of errorKeys) {
115
+ if (usedErrors.has(err.memory.id)) continue;
116
+
117
+ let best = null;
118
+ let bestOverlap = 0;
119
+
120
+ for (const learn of learningKeys) {
121
+ if (usedLearnings.has(learn.memory.id)) continue;
122
+ const overlap = domainOverlap(err.keys, learn.keys);
123
+ if (overlap > bestOverlap) {
124
+ best = learn;
125
+ bestOverlap = overlap;
126
+ }
127
+ }
128
+
129
+ if (best && bestOverlap >= 1) {
130
+ const rubric = buildRubricDelta(err.memory, best.memory);
131
+ pairs.push({
132
+ prompt: inferPrompt(err.memory, best.memory),
133
+ chosen: best.memory.content,
134
+ rejected: err.memory.content,
135
+ metadata: {
136
+ errorId: err.memory.id,
137
+ learningId: best.memory.id,
138
+ matchScore: bestOverlap,
139
+ overlapScore: bestOverlap,
140
+ matchedKeys: err.keys.filter((k) => best.keys.includes(k)),
141
+ errorTitle: err.memory.title,
142
+ learningTitle: best.memory.title,
143
+ rubric,
144
+ synthetic: true,
145
+ syntheticType: 'contrastive_pair',
146
+ },
147
+ });
148
+ usedErrors.add(err.memory.id);
149
+ usedLearnings.add(best.memory.id);
150
+ }
151
+ }
152
+
153
+ return pairs;
154
+ }
155
+
156
+ /**
157
+ * Augment an existing DPO export with synthetic pairs.
158
+ *
159
+ * @param {object} dpoExport - Output from exportDpoFromMemories()
160
+ * @param {object} [options]
161
+ * @param {boolean} [options.scenarioVariations=true] - Generate prompt variations
162
+ * @param {boolean} [options.contrastivePairing=true] - Pair unmatched errors/learnings
163
+ * @param {boolean} [options.principleExtraction=true] - Extract abstract principles
164
+ * @param {boolean} [options.skipProCheck=false] - Skip Pro check (for testing)
165
+ * @param {Function} [options.requireProFn=requirePro] - Injectable Pro gate helper for testing
166
+ * @returns {{ originalPairs: number, syntheticPairs: number, totalPairs: number, pairs: object[], principles: object[] }}
167
+ */
168
+ function augmentDpoExport(dpoExport, options = {}) {
169
+ const {
170
+ scenarioVariations = true,
171
+ contrastivePairing = true,
172
+ principleExtraction = true,
173
+ skipProCheck = false,
174
+ requireProFn = requirePro,
175
+ } = options;
176
+
177
+ // Pro gate (unless testing)
178
+ if (!skipProCheck && !requireProFn('dpo-synthesis')) {
179
+ return {
180
+ originalPairs: dpoExport.pairs?.length || 0,
181
+ syntheticPairs: 0,
182
+ totalPairs: dpoExport.pairs?.length || 0,
183
+ pairs: dpoExport.pairs || [],
184
+ principles: [],
185
+ proRequired: true,
186
+ };
187
+ }
188
+
189
+ const originalPairs = dpoExport.pairs || [];
190
+ const syntheticPairs = [];
191
+ const principles = [];
192
+
193
+ // 1. Scenario variations from existing pairs
194
+ if (scenarioVariations) {
195
+ for (const pair of originalPairs) {
196
+ const variations = generateScenarioVariations(pair);
197
+ syntheticPairs.push(...variations);
198
+ }
199
+ }
200
+
201
+ // 2. Contrastive pairing from unpaired errors/learnings
202
+ if (contrastivePairing) {
203
+ const contrastive = buildContrastivePairs(
204
+ dpoExport.unpairedErrors || [],
205
+ dpoExport.unpairedLearnings || [],
206
+ );
207
+ syntheticPairs.push(...contrastive);
208
+ }
209
+
210
+ // 3. Principle extraction
211
+ if (principleExtraction) {
212
+ for (const pair of originalPairs) {
213
+ principles.push(extractPrinciple(pair));
214
+ }
215
+ }
216
+
217
+ const allPairs = [...originalPairs, ...syntheticPairs];
218
+
219
+ return {
220
+ originalPairs: originalPairs.length,
221
+ syntheticPairs: syntheticPairs.length,
222
+ totalPairs: allPairs.length,
223
+ pairs: allPairs,
224
+ principles,
225
+ };
226
+ }
227
+
228
+ module.exports = {
229
+ augmentDpoExport,
230
+ extractPrinciple,
231
+ generateScenarioVariations,
232
+ buildContrastivePairs,
233
+ truncate,
234
+ };