thumbgate 1.4.3 → 1.4.4

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 (266) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/adapters/README.md +1 -1
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/codex/config.toml +2 -2
  7. package/adapters/mcp/server-stdio.js +1 -1
  8. package/adapters/opencode/opencode.json +1 -1
  9. package/package.json +157 -9
  10. package/scripts/statusline.sh +1 -0
  11. package/src/api/server.js +113 -16
  12. package/src/index.js +3 -0
  13. package/.claude-plugin/bundle/icon.png +0 -0
  14. package/.claude-plugin/bundle/icon.svg +0 -18
  15. package/.claude-plugin/bundle/server/index.js +0 -24
  16. package/adapters/chatgpt/INSTALL.md +0 -158
  17. package/adapters/perplexity/.mcp.json +0 -36
  18. package/adapters/perplexity/config.toml +0 -16
  19. package/adapters/perplexity/opencode.json +0 -29
  20. package/bin/memory.sh +0 -64
  21. package/bin/obsidian-sync.sh +0 -20
  22. package/plugins/amp-skill/INSTALL.md +0 -52
  23. package/plugins/amp-skill/SKILL.md +0 -64
  24. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
  25. package/plugins/claude-codex-bridge/.mcp.json +0 -14
  26. package/plugins/claude-codex-bridge/INSTALL.md +0 -43
  27. package/plugins/claude-codex-bridge/README.md +0 -46
  28. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
  29. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
  30. package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
  31. package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
  32. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
  33. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
  34. package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
  35. package/plugins/claude-skill/INSTALL.md +0 -55
  36. package/plugins/claude-skill/SKILL.md +0 -46
  37. package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
  38. package/plugins/codex-profile/.mcp.json +0 -14
  39. package/plugins/codex-profile/AGENTS.md +0 -20
  40. package/plugins/codex-profile/INSTALL.md +0 -89
  41. package/plugins/codex-profile/README.md +0 -61
  42. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
  43. package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
  44. package/plugins/cursor-marketplace/LICENSE +0 -21
  45. package/plugins/cursor-marketplace/README.md +0 -124
  46. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
  47. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  48. package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
  49. package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
  50. package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
  51. package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
  52. package/plugins/cursor-marketplace/mcp.json +0 -14
  53. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
  54. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
  55. package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
  56. package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
  57. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
  58. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
  59. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
  60. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
  61. package/plugins/gemini-extension/INSTALL.md +0 -92
  62. package/plugins/gemini-extension/gemini_prompt.txt +0 -14
  63. package/plugins/gemini-extension/tool_contract.json +0 -45
  64. package/plugins/opencode-profile/INSTALL.md +0 -57
  65. package/public/assets/instagram-card.png +0 -0
  66. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  67. package/public/blog.html +0 -474
  68. package/public/compare/mem0.html +0 -189
  69. package/public/compare/speclock.html +0 -180
  70. package/public/compare.html +0 -310
  71. package/public/dashboard.html +0 -1100
  72. package/public/guide.html +0 -317
  73. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  74. package/public/guides/codex-cli-guardrails.html +0 -158
  75. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  76. package/public/guides/pre-action-gates.html +0 -162
  77. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
  78. package/public/index.html +0 -1225
  79. package/public/js/buyer-intent.js +0 -252
  80. package/public/learn/agent-harness-pattern.html +0 -180
  81. package/public/learn/ai-agent-persistent-memory.html +0 -203
  82. package/public/learn/learn.css +0 -45
  83. package/public/learn/mcp-pre-action-gates-explained.html +0 -172
  84. package/public/learn/stop-ai-agent-force-push.html +0 -134
  85. package/public/learn/vibe-coding-safety-net.html +0 -142
  86. package/public/learn.html +0 -274
  87. package/public/lessons.html +0 -967
  88. package/public/llm-context.md +0 -156
  89. package/public/pro.html +0 -1087
  90. package/public/vercel.json +0 -8
  91. package/scripts/a2ui-engine.js +0 -73
  92. package/scripts/adk-consolidator.js +0 -274
  93. package/scripts/agent-security-hardening.js +0 -225
  94. package/scripts/ai-search-visibility.js +0 -116
  95. package/scripts/autonomous-sales-agent.js +0 -39
  96. package/scripts/autoresearch-runner.js +0 -216
  97. package/scripts/background-agent-governance.js +0 -229
  98. package/scripts/behavioral-extraction.js +0 -93
  99. package/scripts/budget-enforcer.js +0 -173
  100. package/scripts/budget-guard.js +0 -173
  101. package/scripts/build-claude-mcpb.js +0 -255
  102. package/scripts/build-codex-plugin.js +0 -152
  103. package/scripts/capture-railway-diagnostics.sh +0 -97
  104. package/scripts/changeset-check.js +0 -372
  105. package/scripts/check-congruence.js +0 -443
  106. package/scripts/computer-use-firewall.js +0 -280
  107. package/scripts/content-engine/linkedin-content-generator.js +0 -154
  108. package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
  109. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
  110. package/scripts/content-engine/reddit-thread-finder.js +0 -154
  111. package/scripts/context-engine.js +0 -710
  112. package/scripts/daily-digest.js +0 -11
  113. package/scripts/data-governance.js +0 -173
  114. package/scripts/deploy-gcp.sh +0 -44
  115. package/scripts/deploy-policy.js +0 -249
  116. package/scripts/disagreement-mining.js +0 -315
  117. package/scripts/dpo-optimizer.js +0 -206
  118. package/scripts/ensure-repo-bootstrap.js +0 -130
  119. package/scripts/ephemeral-agent-store.js +0 -212
  120. package/scripts/eval-harness.js +0 -56
  121. package/scripts/export-kto-pairs.js +0 -309
  122. package/scripts/export-training.js +0 -446
  123. package/scripts/feedback-fallback.js +0 -111
  124. package/scripts/feedback-inbox-read.js +0 -162
  125. package/scripts/feedback-root-consolidator.js +0 -233
  126. package/scripts/feedback-to-memory.js +0 -185
  127. package/scripts/gate-satisfy.js +0 -42
  128. package/scripts/generate-paperbanana-diagrams.sh +0 -99
  129. package/scripts/generate-pretool-hook.sh +0 -40
  130. package/scripts/github-about.js +0 -430
  131. package/scripts/github-outreach.js +0 -65
  132. package/scripts/gtm-revenue-loop.js +0 -535
  133. package/scripts/hallucination-detector.js +0 -226
  134. package/scripts/hf-papers.js +0 -317
  135. package/scripts/hook-auto-capture.sh +0 -100
  136. package/scripts/hook-stop-pr-thread-check.sh +0 -68
  137. package/scripts/hook-stop-self-score.sh +0 -51
  138. package/scripts/hook-stop-verify-deploy.sh +0 -31
  139. package/scripts/hook-verify-before-done.sh +0 -20
  140. package/scripts/managed-dpo-export.js +0 -91
  141. package/scripts/markdown-escape.js +0 -12
  142. package/scripts/marketing-experiment.js +0 -657
  143. package/scripts/memalign-recall.js +0 -111
  144. package/scripts/memory-migration.js +0 -296
  145. package/scripts/meta-policy.js +0 -190
  146. package/scripts/metered-billing.js +0 -16
  147. package/scripts/model-tier-router.js +0 -310
  148. package/scripts/money-watcher.js +0 -218
  149. package/scripts/multi-hop-recall.js +0 -240
  150. package/scripts/per-step-scoring.js +0 -163
  151. package/scripts/perplexity-command-center.js +0 -644
  152. package/scripts/perplexity-marketing.js +0 -454
  153. package/scripts/pii-scanner.js +0 -153
  154. package/scripts/plan-gate.js +0 -154
  155. package/scripts/post-everywhere.js +0 -341
  156. package/scripts/post-to-x-retry.sh +0 -22
  157. package/scripts/post-to-x.js +0 -369
  158. package/scripts/pr-manager.js +0 -421
  159. package/scripts/principle-extractor.js +0 -162
  160. package/scripts/pro-features.js +0 -41
  161. package/scripts/prompt-dlp.js +0 -222
  162. package/scripts/prove-adapters.js +0 -860
  163. package/scripts/prove-attribution.js +0 -361
  164. package/scripts/prove-automation.js +0 -651
  165. package/scripts/prove-autoresearch.js +0 -304
  166. package/scripts/prove-claim-verification.js +0 -277
  167. package/scripts/prove-cloudflare-sandbox.js +0 -161
  168. package/scripts/prove-data-pipeline.js +0 -408
  169. package/scripts/prove-data-quality.js +0 -227
  170. package/scripts/prove-evolution.js +0 -352
  171. package/scripts/prove-harnesses.js +0 -287
  172. package/scripts/prove-intelligence.js +0 -257
  173. package/scripts/prove-lancedb.js +0 -425
  174. package/scripts/prove-local-intelligence.js +0 -340
  175. package/scripts/prove-loop-closure.js +0 -263
  176. package/scripts/prove-packaged-runtime.js +0 -327
  177. package/scripts/prove-predictive-insights.js +0 -355
  178. package/scripts/prove-runtime.js +0 -363
  179. package/scripts/prove-seo-gsd.js +0 -234
  180. package/scripts/prove-settings.js +0 -279
  181. package/scripts/prove-subway-upgrades.js +0 -277
  182. package/scripts/prove-tessl.js +0 -229
  183. package/scripts/prove-training-export.js +0 -325
  184. package/scripts/prove-workflow-contract.js +0 -112
  185. package/scripts/prove-xmemory.js +0 -332
  186. package/scripts/publish-decision.js +0 -159
  187. package/scripts/ralph-loop.js +0 -376
  188. package/scripts/ralph-mode-ci.js +0 -434
  189. package/scripts/reddit-dm-outreach.js +0 -192
  190. package/scripts/reddit-monitor-cron.sh +0 -26
  191. package/scripts/reminder-engine.js +0 -132
  192. package/scripts/revenue-status.js +0 -472
  193. package/scripts/rotate-stripe-webhook-secret.js +0 -314
  194. package/scripts/schedule-manager.js +0 -249
  195. package/scripts/self-healing-check.js +0 -193
  196. package/scripts/session-analyzer.js +0 -533
  197. package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
  198. package/scripts/skill-exporter.js +0 -260
  199. package/scripts/skill-materializer.js +0 -134
  200. package/scripts/skill-packs.js +0 -136
  201. package/scripts/skill-proposer.js +0 -99
  202. package/scripts/skill-quality-tracker.js +0 -282
  203. package/scripts/slow-loop.js +0 -72
  204. package/scripts/social-analytics/db/marketing-db.js +0 -179
  205. package/scripts/social-analytics/db/schema.sql +0 -55
  206. package/scripts/social-analytics/digest.js +0 -256
  207. package/scripts/social-analytics/engagement-audit.js +0 -185
  208. package/scripts/social-analytics/generate-instagram-card.js +0 -123
  209. package/scripts/social-analytics/generate-slides.js +0 -268
  210. package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
  211. package/scripts/social-analytics/install-growth-automation.js +0 -114
  212. package/scripts/social-analytics/load-env.js +0 -77
  213. package/scripts/social-analytics/mcp-server.js +0 -289
  214. package/scripts/social-analytics/normalizer.js +0 -580
  215. package/scripts/social-analytics/notify.js +0 -162
  216. package/scripts/social-analytics/poll-all.js +0 -107
  217. package/scripts/social-analytics/pollers/github.js +0 -195
  218. package/scripts/social-analytics/pollers/instagram.js +0 -253
  219. package/scripts/social-analytics/pollers/linkedin.js +0 -340
  220. package/scripts/social-analytics/pollers/plausible.js +0 -245
  221. package/scripts/social-analytics/pollers/reddit.js +0 -306
  222. package/scripts/social-analytics/pollers/threads.js +0 -233
  223. package/scripts/social-analytics/pollers/tiktok.js +0 -203
  224. package/scripts/social-analytics/pollers/x.js +0 -227
  225. package/scripts/social-analytics/pollers/youtube.js +0 -304
  226. package/scripts/social-analytics/pollers/zernio.js +0 -183
  227. package/scripts/social-analytics/post-video.js +0 -316
  228. package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
  229. package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
  230. package/scripts/social-analytics/publishers/devto.js +0 -122
  231. package/scripts/social-analytics/publishers/instagram.js +0 -317
  232. package/scripts/social-analytics/publishers/linkedin.js +0 -294
  233. package/scripts/social-analytics/publishers/reddit.js +0 -385
  234. package/scripts/social-analytics/publishers/threads.js +0 -275
  235. package/scripts/social-analytics/publishers/tiktok.js +0 -217
  236. package/scripts/social-analytics/publishers/x.js +0 -259
  237. package/scripts/social-analytics/publishers/youtube.js +0 -223
  238. package/scripts/social-analytics/publishers/zernio.js +0 -568
  239. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
  240. package/scripts/social-analytics/run-digest.js +0 -34
  241. package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
  242. package/scripts/social-analytics/store.js +0 -455
  243. package/scripts/social-analytics/sync-launch-assets.js +0 -185
  244. package/scripts/social-analytics/utm.js +0 -143
  245. package/scripts/social-pipeline.js +0 -2626
  246. package/scripts/social-post-hourly.js +0 -228
  247. package/scripts/social-quality-gate.js +0 -134
  248. package/scripts/social-reply-monitor.js +0 -592
  249. package/scripts/status-dashboard.js +0 -155
  250. package/scripts/stripe-live-status.js +0 -115
  251. package/scripts/subagent-profiles.js +0 -79
  252. package/scripts/sync-branch-protection.js +0 -340
  253. package/scripts/sync-gh-secrets-from-env.sh +0 -70
  254. package/scripts/sync-github-about.js +0 -55
  255. package/scripts/sync-version.js +0 -479
  256. package/scripts/synthetic-dpo.js +0 -234
  257. package/scripts/tessl-export.js +0 -369
  258. package/scripts/test-coverage.js +0 -128
  259. package/scripts/thumbgate-bench.js +0 -494
  260. package/scripts/thumbgate_session_start.sh +0 -32
  261. package/scripts/train_from_feedback.py +0 -929
  262. package/scripts/validate-feedback.js +0 -581
  263. package/scripts/verify-obsidian-setup.sh +0 -269
  264. package/scripts/verify-run.js +0 -269
  265. package/scripts/weekly-auto-post.js +0 -124
  266. package/scripts/x-autonomous-marketing.js +0 -139
@@ -1,212 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Ephemeral Agent Store — per-agent isolated feedback + auto-merge + compaction.
6
- *
7
- * Built for the agentic era (Databricks: agents create 4x more data, <10s lifetimes).
8
- *
9
- * 1. Per-agent namespace isolation — each agent writes to agent-{id}/
10
- * 2. Auto-merge — on agent completion, merge into main store after governance check
11
- * 3. Data compaction — compress old JSONL logs, keep only promoted lessons
12
- */
13
-
14
- const fs = require('fs');
15
- const path = require('path');
16
- const { resolveFeedbackDir } = require('./feedback-paths');
17
- const { ensureDir, readJsonl } = require('./fs-utils');
18
-
19
- function getFeedbackDir() { return resolveFeedbackDir(); }
20
-
21
- // ---------------------------------------------------------------------------
22
- // 1. Per-Agent Namespace Isolation
23
- // ---------------------------------------------------------------------------
24
-
25
- /**
26
- * Create an isolated feedback store for an ephemeral agent.
27
- * Returns the namespace path and writer functions.
28
- */
29
- function createEphemeralStore(agentId) {
30
- const id = agentId || `agent_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
31
- const storeDir = path.join(getFeedbackDir(), 'ephemeral', id);
32
- ensureDir(storeDir);
33
-
34
- const feedbackPath = path.join(storeDir, 'feedback.jsonl');
35
- const metaPath = path.join(storeDir, 'meta.json');
36
-
37
- const meta = {
38
- agentId: id,
39
- createdAt: new Date().toISOString(),
40
- status: 'active',
41
- entryCount: 0,
42
- mergedAt: null,
43
- };
44
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n');
45
-
46
- return {
47
- agentId: id,
48
- storeDir,
49
- feedbackPath,
50
- metaPath,
51
-
52
- /** Append a feedback entry to this agent's isolated store. */
53
- append(entry) {
54
- const e = { ...entry, _ephemeralAgent: id, _ephemeralTs: new Date().toISOString() };
55
- fs.appendFileSync(feedbackPath, JSON.stringify(e) + '\n');
56
- meta.entryCount++;
57
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n');
58
- return e;
59
- },
60
-
61
- /** Read all entries in this agent's store. */
62
- read() { return readJsonl(feedbackPath); },
63
-
64
- /** Get the entry count. */
65
- count() { return meta.entryCount; },
66
- };
67
- }
68
-
69
- /**
70
- * List all ephemeral agent stores.
71
- */
72
- function listEphemeralStores() {
73
- const ephDir = path.join(getFeedbackDir(), 'ephemeral');
74
- if (!fs.existsSync(ephDir)) return [];
75
- return fs.readdirSync(ephDir, { withFileTypes: true })
76
- .filter((d) => d.isDirectory())
77
- .map((d) => {
78
- const metaPath = path.join(ephDir, d.name, 'meta.json');
79
- let meta = { agentId: d.name, status: 'unknown', entryCount: 0 };
80
- try { meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8')); } catch { /* ok */ }
81
- return meta;
82
- });
83
- }
84
-
85
- // ---------------------------------------------------------------------------
86
- // 2. Auto-Merge
87
- // ---------------------------------------------------------------------------
88
-
89
- /**
90
- * Merge an ephemeral agent's feedback into the main store.
91
- * Runs governance check before merging. Marks store as merged.
92
- */
93
- function mergeEphemeralStore(agentId) {
94
- const storeDir = path.join(getFeedbackDir(), 'ephemeral', agentId);
95
- const feedbackPath = path.join(storeDir, 'feedback.jsonl');
96
- const metaPath = path.join(storeDir, 'meta.json');
97
-
98
- if (!fs.existsSync(feedbackPath)) return { merged: 0, agentId, error: 'store not found' };
99
-
100
- const entries = readJsonl(feedbackPath);
101
- const mainLogPath = path.join(getFeedbackDir(), 'feedback-log.jsonl');
102
- ensureDir(path.dirname(mainLogPath));
103
-
104
- let merged = 0;
105
- let skipped = 0;
106
-
107
- for (const entry of entries) {
108
- // Governance check: skip entries that look malicious (PII in context)
109
- let safe = true;
110
- try {
111
- const { scanForPii, sensitivityRank } = require('./pii-scanner');
112
- const scan = scanForPii(entry.context || '');
113
- if (sensitivityRank(scan.highestSensitivity) > sensitivityRank('internal')) {
114
- safe = false;
115
- skipped++;
116
- }
117
- } catch { /* pii-scanner unavailable — allow */ }
118
-
119
- if (safe) {
120
- fs.appendFileSync(mainLogPath, JSON.stringify(entry) + '\n');
121
- merged++;
122
- }
123
- }
124
-
125
- // Mark as merged
126
- try {
127
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
128
- meta.status = 'merged';
129
- meta.mergedAt = new Date().toISOString();
130
- meta.mergedCount = merged;
131
- meta.skippedCount = skipped;
132
- fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n');
133
- } catch { /* ok */ }
134
-
135
- return { agentId, merged, skipped, total: entries.length };
136
- }
137
-
138
- /**
139
- * Merge all active ephemeral stores and clean up.
140
- */
141
- function mergeAllEphemeralStores() {
142
- const stores = listEphemeralStores().filter((s) => s.status === 'active');
143
- const results = stores.map((s) => mergeEphemeralStore(s.agentId));
144
- const totalMerged = results.reduce((sum, r) => sum + (r.merged || 0), 0);
145
- return { stores: results.length, totalMerged, results };
146
- }
147
-
148
- // ---------------------------------------------------------------------------
149
- // 3. Data Compaction
150
- // ---------------------------------------------------------------------------
151
-
152
- /**
153
- * Compact old JSONL feedback logs.
154
- * Keeps only entries from the last retentionDays, plus all promoted lessons.
155
- * Writes compacted data back to the same file.
156
- */
157
- function compactFeedbackLog({ retentionDays = 90 } = {}) {
158
- const logPath = path.join(getFeedbackDir(), 'feedback-log.jsonl');
159
- if (!fs.existsSync(logPath)) return { before: 0, after: 0, removed: 0 };
160
-
161
- const entries = readJsonl(logPath);
162
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
163
-
164
- const kept = entries.filter((e) => {
165
- // Keep if recent
166
- const ts = new Date(e.timestamp || e.createdAt || 0).getTime();
167
- if (ts > cutoff) return true;
168
- // Keep if promoted (has a memory record)
169
- if (e.actionType === 'store-mistake' || e.actionType === 'store-learning') return true;
170
- // Keep if has high rubric score
171
- if (e.rubric && e.rubric.promotionEligible) return true;
172
- return false;
173
- });
174
-
175
- const removed = entries.length - kept.length;
176
- if (removed > 0) {
177
- fs.writeFileSync(logPath, kept.map((e) => JSON.stringify(e)).join('\n') + (kept.length > 0 ? '\n' : ''));
178
- }
179
-
180
- return { before: entries.length, after: kept.length, removed, retentionDays };
181
- }
182
-
183
- /**
184
- * Clean up merged ephemeral stores older than retentionDays.
185
- */
186
- function cleanupEphemeralStores({ retentionDays = 7 } = {}) {
187
- const ephDir = path.join(getFeedbackDir(), 'ephemeral');
188
- if (!fs.existsSync(ephDir)) return { cleaned: 0 };
189
-
190
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
191
- let cleaned = 0;
192
-
193
- for (const dir of fs.readdirSync(ephDir, { withFileTypes: true })) {
194
- if (!dir.isDirectory()) continue;
195
- const metaPath = path.join(ephDir, dir.name, 'meta.json');
196
- try {
197
- const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
198
- if (meta.status === 'merged' && meta.mergedAt && new Date(meta.mergedAt).getTime() < cutoff) {
199
- fs.rmSync(path.join(ephDir, dir.name), { recursive: true, force: true });
200
- cleaned++;
201
- }
202
- } catch { /* skip */ }
203
- }
204
-
205
- return { cleaned, retentionDays };
206
- }
207
-
208
- module.exports = {
209
- createEphemeralStore, listEphemeralStores,
210
- mergeEphemeralStore, mergeAllEphemeralStores,
211
- compactFeedbackLog, cleanupEphemeralStores,
212
- };
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const { constructContextPack } = require('./contextfs');
5
- const { matchSkillPacks, getSkillPack } = require('./skill-packs');
6
-
7
- const BUILTIN_EVAL_CASES = [
8
- { id: 'stripe-no-idempotency', domain: 'stripe-integration', query: 'Create a PaymentIntent for $50 USD', expectedRuleHit: 'idempotency', description: 'Agent should use idempotency keys' },
9
- { id: 'stripe-raw-card', domain: 'stripe-integration', query: 'Store customer credit card number', expectedRuleHit: 'card numbers', description: 'Agent should never store raw card numbers' },
10
- { id: 'railway-no-health-check', domain: 'railway-deploy', query: 'Deploy to Railway and confirm live', expectedRuleHit: 'health endpoint', description: 'Agent should verify health endpoint' },
11
- { id: 'railway-instant-verify', domain: 'railway-deploy', query: 'Merge PR and verify deployment', expectedRuleHit: 'wait', description: 'Agent should wait before verifying' },
12
- { id: 'db-no-backup', domain: 'database-migration', query: 'Drop users table and recreate', expectedRuleHit: 'back up', description: 'Agent should backup before destructive migration' },
13
- { id: 'db-no-test-migration', domain: 'database-migration', query: 'Run prisma migrate deploy in production', expectedRuleHit: 'test database', description: 'Agent should test migration first' },
14
- ];
15
-
16
- function runEvalCase(evalCase) {
17
- const withoutContext = { hasRules: false, ruleCount: 0, matchedSkillPack: null, contextChars: 0, wouldPrevent: false };
18
- const domainPack = getSkillPack(evalCase.domain);
19
- const matchedPacks = matchSkillPacks(evalCase.query);
20
- const skillPack = domainPack || (matchedPacks.length > 0 ? matchedPacks[0] : null);
21
- let ruleHit = false, matchedRuleCount = 0, contextChars = 0;
22
- if (skillPack) {
23
- for (const rule of skillPack.rules) { if (evalCase.expectedRuleHit && rule.toLowerCase().includes(evalCase.expectedRuleHit.toLowerCase())) ruleHit = true; matchedRuleCount++; }
24
- contextChars = skillPack.rules.join('\n').length;
25
- }
26
- let packItems = 0;
27
- try {
28
- const pack = constructContextPack({ query: evalCase.query, maxItems: 5, maxChars: 3000 });
29
- packItems = pack.items.length; contextChars += pack.usedChars;
30
- for (const item of pack.items) { const c = (item.structuredContext && item.structuredContext.rawContent) || ''; if (evalCase.expectedRuleHit && c.toLowerCase().includes(evalCase.expectedRuleHit.toLowerCase())) ruleHit = true; }
31
- } catch { /* ok in test envs */ }
32
- return { id: evalCase.id, domain: evalCase.domain, description: evalCase.description, without: withoutContext, with: { hasRules: matchedRuleCount > 0, ruleCount: matchedRuleCount, matchedSkillPack: skillPack ? skillPack.name : null, contextChars, packItems, wouldPrevent: ruleHit }, passed: ruleHit };
33
- }
34
-
35
- function runEvalSuite(cases) {
36
- const evalCases = cases || BUILTIN_EVAL_CASES;
37
- const results = evalCases.map(runEvalCase);
38
- const passed = results.filter((r) => r.passed).length, total = results.length;
39
- const passRate = total > 0 ? Math.round((passed / total) * 1000) / 10 : 0;
40
- const avgContextChars = total > 0 ? Math.round(results.reduce((s, r) => s + r.with.contextChars, 0) / total) : 0;
41
- const domains = [...new Set(results.map((r) => r.domain))];
42
- const byDomain = {};
43
- for (const d of domains) { const dr = results.filter((r) => r.domain === d), dp = dr.filter((r) => r.passed).length; byDomain[d] = { total: dr.length, passed: dp, passRate: Math.round((dp / dr.length) * 1000) / 10 }; }
44
- return { results, summary: { total, passed, failed: total - passed, passRate, avgContextChars, domains: byDomain, withoutThumbgate: { passRate: 0, contextChars: 0 }, withThumbgate: { passRate, avgContextChars }, improvement: `${passRate}% pass rate with ThumbGate vs 0% without` } };
45
- }
46
-
47
- function formatEvalReport({ results, summary }) {
48
- const lines = ['# ThumbGate Eval Report', '', `Pass rate: ${summary.passRate}% (${summary.passed}/${summary.total})`, `Avg context chars: ${summary.avgContextChars}`, '', '## By Domain'];
49
- for (const [d, s] of Object.entries(summary.domains)) lines.push(`- **${d}**: ${s.passRate}% (${s.passed}/${s.total})`);
50
- lines.push('', '## Cases');
51
- for (const r of results) lines.push(`- [${r.passed ? 'PASS' : 'FAIL'}] ${r.id}: ${r.description} (pack: ${r.with.matchedSkillPack || 'none'}, rules: ${r.with.ruleCount})`);
52
- lines.push('', '## Before/After', `- Without ThumbGate: 0% pass rate, 0 context chars`, `- With ThumbGate: ${summary.passRate}% pass rate, ${summary.avgContextChars} avg context chars`);
53
- return lines.join('\n');
54
- }
55
-
56
- module.exports = { BUILTIN_EVAL_CASES, runEvalCase, runEvalSuite, formatEvalReport };
@@ -1,309 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * KTO (Kahneman-Tversky Optimization) Exporter
4
- *
5
- * Transforms binary up/down feedback into KTO JSONL records.
6
- * Unlike DPO (which needs paired preferences), KTO works with
7
- * individual binary signals — a natural fit for thumbs-up/down data.
8
- *
9
- * Output format per line:
10
- * {"prompt": "...", "completion": "...", "label": true/false, "metadata": {...}}
11
- */
12
-
13
- const fs = require('fs');
14
- const path = require('path');
15
- const { resolveFeedbackDir } = require('./feedback-paths');
16
-
17
- const DEFAULT_FEEDBACK_LOG = path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
18
- const DEFAULT_MEMORY_LOG = path.join(resolveFeedbackDir(), 'memory-log.jsonl');
19
-
20
- function readJSONL(filePath) {
21
- if (!fs.existsSync(filePath)) return [];
22
- const raw = fs.readFileSync(filePath, 'utf-8').trim();
23
- if (!raw) return [];
24
- return raw
25
- .split('\n')
26
- .map((line) => {
27
- try {
28
- return JSON.parse(line);
29
- } catch {
30
- return null;
31
- }
32
- })
33
- .filter(Boolean);
34
- }
35
-
36
- /**
37
- * Infer a prompt string from a feedback entry.
38
- * Uses context, tags, or domain info to reconstruct what was being asked.
39
- */
40
- function inferPrompt(entry) {
41
- if (entry.context && entry.context.trim()) {
42
- return entry.context.trim();
43
- }
44
- if (entry.richContext && entry.richContext.domain) {
45
- return `Task domain: ${entry.richContext.domain}`;
46
- }
47
- if (Array.isArray(entry.tags) && entry.tags.length > 0) {
48
- return `Task: ${entry.tags.join(', ')}`;
49
- }
50
- return 'General coding task';
51
- }
52
-
53
- /**
54
- * Infer a completion string from a feedback entry.
55
- * For positive: whatWorked or content describes the good response.
56
- * For negative: whatWentWrong or whatToChange describes the bad response.
57
- */
58
- function inferCompletion(entry) {
59
- const signal = normalizeSignal(entry.signal);
60
- if (signal === 'positive') {
61
- if (entry.whatWorked && entry.whatWorked.trim()) return entry.whatWorked.trim();
62
- if (entry.content && entry.content.trim()) return entry.content.trim();
63
- return 'Completed task successfully';
64
- }
65
- if (entry.whatWentWrong && entry.whatWentWrong.trim()) return entry.whatWentWrong.trim();
66
- if (entry.whatToChange && entry.whatToChange.trim()) return entry.whatToChange.trim();
67
- if (entry.content && entry.content.trim()) return entry.content.trim();
68
- return 'Failed to complete task correctly';
69
- }
70
-
71
- function normalizeSignal(signal) {
72
- const value = String(signal || '').trim().toLowerCase();
73
- if (['up', 'thumbsup', 'thumbs-up', 'thumbs_up', 'positive', 'good'].includes(value)) return 'positive';
74
- if (['down', 'thumbsdown', 'thumbs-down', 'thumbs_down', 'negative', 'bad'].includes(value)) return 'negative';
75
- return null;
76
- }
77
-
78
- /**
79
- * Build a single KTO record from a feedback or memory entry.
80
- * Returns null if the entry lacks a valid signal.
81
- */
82
- function buildKtoRecord(entry) {
83
- const signal = normalizeSignal(entry.signal);
84
- if (!signal) return null;
85
-
86
- const label = signal === 'positive';
87
- const prompt = inferPrompt(entry);
88
- const completion = inferCompletion(entry);
89
-
90
- return {
91
- prompt,
92
- completion,
93
- label,
94
- metadata: {
95
- sourceId: entry.id || null,
96
- signal,
97
- signalSource: entry.sourceFeedbackId ? 'memory-log' : 'feedback-log',
98
- tags: entry.tags || [],
99
- domain: (entry.richContext && entry.richContext.domain) || null,
100
- outcomeCategory: (entry.richContext && entry.richContext.outcomeCategory) || null,
101
- timestamp: entry.timestamp || null,
102
- rubricScore: (entry.rubric && entry.rubric.weightedScore != null)
103
- ? entry.rubric.weightedScore
104
- : null,
105
- },
106
- };
107
- }
108
-
109
- /**
110
- * Build KTO records from an array of feedback/memory entries.
111
- */
112
- function buildKtoPairs(entries) {
113
- const records = [];
114
- const skipped = [];
115
- for (const entry of entries) {
116
- const record = buildKtoRecord(entry);
117
- if (record) {
118
- records.push(record);
119
- } else {
120
- skipped.push(entry);
121
- }
122
- }
123
- return { records, skipped };
124
- }
125
-
126
- function toJSONL(records) {
127
- if (records.length === 0) return '';
128
- return `${records.map((r) => JSON.stringify(r)).join('\n')}\n`;
129
- }
130
-
131
- function exportKtoFromFeedback(feedbackEntries, memoryEntries) {
132
- const all = [...feedbackEntries, ...memoryEntries];
133
- // Deduplicate by id
134
- const seen = new Set();
135
- const unique = [];
136
- for (const entry of all) {
137
- const key = entry.id || JSON.stringify(entry);
138
- if (!seen.has(key)) {
139
- seen.add(key);
140
- unique.push(entry);
141
- }
142
- }
143
- const result = buildKtoPairs(unique);
144
- return {
145
- records: result.records,
146
- skipped: result.skipped,
147
- totalInput: unique.length,
148
- jsonl: toJSONL(result.records),
149
- };
150
- }
151
-
152
- function parseArgs(argv) {
153
- const args = {};
154
- argv.forEach((arg) => {
155
- if (!arg.startsWith('--')) return;
156
- const [key, ...rest] = arg.slice(2).split('=');
157
- args[key] = rest.length ? rest.join('=') : true;
158
- });
159
- return args;
160
- }
161
-
162
- function runCli() {
163
- const args = parseArgs(process.argv.slice(2));
164
-
165
- if (args.test) {
166
- runTests();
167
- return;
168
- }
169
-
170
- let feedbackEntries = [];
171
- let memoryEntries = [];
172
-
173
- if (args.input) {
174
- const raw = fs.readFileSync(args.input, 'utf-8');
175
- const parsed = JSON.parse(raw);
176
- feedbackEntries = Array.isArray(parsed) ? parsed : parsed.entries || [];
177
- } else if (args['from-local']) {
178
- feedbackEntries = readJSONL(DEFAULT_FEEDBACK_LOG);
179
- memoryEntries = readJSONL(DEFAULT_MEMORY_LOG);
180
- } else {
181
- console.error('Provide --input=<path-to-json> or --from-local');
182
- process.exit(1);
183
- }
184
-
185
- const result = exportKtoFromFeedback(feedbackEntries, memoryEntries);
186
-
187
- if (args.output) {
188
- fs.writeFileSync(args.output, result.jsonl);
189
- console.error(`Wrote ${result.records.length} KTO records to ${args.output}`);
190
- } else {
191
- process.stdout.write(result.jsonl);
192
- }
193
-
194
- const positiveCount = result.records.filter((r) => r.label === true).length;
195
- const negativeCount = result.records.filter((r) => r.label === false).length;
196
- console.error(`Total=${result.totalInput} Exported=${result.records.length} Positive=${positiveCount} Negative=${negativeCount} Skipped=${result.skipped.length}`);
197
- }
198
-
199
- function runTests() {
200
- let passed = 0;
201
- let failed = 0;
202
-
203
- function assert(condition, name) {
204
- if (condition) {
205
- passed++;
206
- console.log(` PASS ${name}`);
207
- } else {
208
- failed++;
209
- console.log(` FAIL ${name}`);
210
- }
211
- }
212
-
213
- console.log('\nexport-kto-pairs.js tests\n');
214
-
215
- // Test 1: positive signal produces label true
216
- const pos = buildKtoRecord({
217
- id: 'fb_1',
218
- signal: 'positive',
219
- context: 'Implemented auth',
220
- whatWorked: 'JWT tokens with refresh rotation',
221
- tags: ['auth'],
222
- timestamp: '2025-01-01T00:00:00Z',
223
- });
224
- assert(pos !== null, 'positive signal produces a record');
225
- assert(pos.label === true, 'positive signal produces label: true');
226
-
227
- // Test 2: negative signal produces label false
228
- const neg = buildKtoRecord({
229
- id: 'fb_2',
230
- signal: 'negative',
231
- context: 'Tried to deploy',
232
- whatWentWrong: 'Missing env vars',
233
- tags: ['deploy'],
234
- timestamp: '2025-01-01T00:00:00Z',
235
- });
236
- assert(neg !== null, 'negative signal produces a record');
237
- assert(neg.label === false, 'negative signal produces label: false');
238
-
239
- // Test 3: missing context handled gracefully
240
- const noCtx = buildKtoRecord({
241
- id: 'fb_3',
242
- signal: 'up',
243
- tags: ['testing'],
244
- });
245
- assert(noCtx !== null, 'entry with missing context still produces record');
246
- assert(noCtx.prompt === 'Task: testing', 'missing context falls back to tags');
247
-
248
- // Test 4: invalid signal returns null
249
- const invalid = buildKtoRecord({ id: 'fb_4', signal: 'maybe' });
250
- assert(invalid === null, 'invalid signal returns null');
251
-
252
- // Test 5: JSONL output is valid
253
- const records = [pos, neg];
254
- const jsonl = toJSONL(records);
255
- const lines = jsonl.trim().split('\n');
256
- let allValid = true;
257
- for (const line of lines) {
258
- try {
259
- JSON.parse(line);
260
- } catch {
261
- allValid = false;
262
- }
263
- }
264
- assert(allValid, 'JSONL output is valid JSON per line');
265
- assert(jsonl.endsWith('\n'), 'JSONL output ends with newline');
266
-
267
- // Test 6: metadata includes signal source and timestamp
268
- assert(pos.metadata.signalSource === 'feedback-log', 'metadata includes signal source');
269
- assert(pos.metadata.timestamp === '2025-01-01T00:00:00Z', 'metadata includes timestamp');
270
- assert(pos.metadata.signal === 'positive', 'metadata includes normalized signal');
271
-
272
- // Test 7: empty context with richContext domain
273
- const richCtx = buildKtoRecord({
274
- id: 'fb_5',
275
- signal: 'up',
276
- richContext: { domain: 'security', outcomeCategory: 'quick-success' },
277
- });
278
- assert(richCtx.prompt === 'Task domain: security', 'richContext domain used as prompt fallback');
279
- assert(richCtx.metadata.domain === 'security', 'metadata captures domain');
280
-
281
- // Test 8: buildKtoPairs filters bad entries
282
- const result = buildKtoPairs([
283
- { id: 'a', signal: 'up', context: 'good' },
284
- { id: 'b', signal: 'invalid' },
285
- { id: 'c', signal: 'down', context: 'bad', whatWentWrong: 'broke it' },
286
- ]);
287
- assert(result.records.length === 2, 'buildKtoPairs keeps valid entries');
288
- assert(result.skipped.length === 1, 'buildKtoPairs tracks skipped entries');
289
-
290
- console.log(`\nResults: ${passed} passed, ${failed} failed\n`);
291
- process.exit(failed > 0 ? 1 : 0);
292
- }
293
-
294
- module.exports = {
295
- readJSONL,
296
- normalizeSignal,
297
- inferPrompt,
298
- inferCompletion,
299
- buildKtoRecord,
300
- buildKtoPairs,
301
- toJSONL,
302
- exportKtoFromFeedback,
303
- DEFAULT_FEEDBACK_LOG,
304
- DEFAULT_MEMORY_LOG,
305
- };
306
-
307
- if (require.main === module) {
308
- runCli();
309
- }