thumbgate 0.9.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/thumbgate-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1484 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +283 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1014 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +97 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +263 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -0
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { resolveFeedbackDir } = require('./feedback-paths');
6
+
7
+ const DEFAULT_SETTINGS = Object.freeze({
8
+ half_life_days: 7,
9
+ decay_floor: 0.01,
10
+ prevention_min_occurrences: 2,
11
+ verification_max_retries: 3,
12
+ dpo_beta: 0.1,
13
+ });
14
+
15
+ function ensureDir(dirPath) {
16
+ if (!fs.existsSync(dirPath)) {
17
+ fs.mkdirSync(dirPath, { recursive: true });
18
+ }
19
+ }
20
+
21
+ function appendJSONL(filePath, record) {
22
+ ensureDir(path.dirname(filePath));
23
+ fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`);
24
+ }
25
+
26
+ function normalizeState(state = {}) {
27
+ const normalized = {
28
+ version: Number.isInteger(state.version) ? state.version : 1,
29
+ updatedAt: typeof state.updatedAt === 'string' ? state.updatedAt : null,
30
+ acceptedMutations: Array.isArray(state.acceptedMutations) ? state.acceptedMutations : [],
31
+ settings: {
32
+ ...DEFAULT_SETTINGS,
33
+ ...(state.settings || {}),
34
+ },
35
+ };
36
+
37
+ return normalized;
38
+ }
39
+
40
+ function getEvolutionPaths(feedbackDir = resolveFeedbackDir()) {
41
+ return {
42
+ feedbackDir,
43
+ statePath: path.join(feedbackDir, 'evolution-state.json'),
44
+ historyPath: path.join(feedbackDir, 'evolution-history.jsonl'),
45
+ snapshotsDir: path.join(feedbackDir, 'evolution-snapshots'),
46
+ };
47
+ }
48
+
49
+ function readEvolutionState(feedbackDir = resolveFeedbackDir()) {
50
+ const { statePath } = getEvolutionPaths(feedbackDir);
51
+ if (!fs.existsSync(statePath)) {
52
+ return normalizeState();
53
+ }
54
+
55
+ try {
56
+ return normalizeState(JSON.parse(fs.readFileSync(statePath, 'utf8')));
57
+ } catch {
58
+ return normalizeState();
59
+ }
60
+ }
61
+
62
+ function writeEvolutionState(state, feedbackDir = resolveFeedbackDir()) {
63
+ const { statePath } = getEvolutionPaths(feedbackDir);
64
+ const normalized = normalizeState({
65
+ ...state,
66
+ updatedAt: new Date().toISOString(),
67
+ });
68
+ ensureDir(path.dirname(statePath));
69
+ fs.writeFileSync(statePath, `${JSON.stringify(normalized, null, 2)}\n`);
70
+ return normalized;
71
+ }
72
+
73
+ function getEffectiveSetting(key, fallback, feedbackDir = resolveFeedbackDir()) {
74
+ const state = readEvolutionState(feedbackDir);
75
+ const value = state.settings[key];
76
+ return Number.isFinite(value) ? value : fallback;
77
+ }
78
+
79
+ function captureEvolutionSnapshot({
80
+ label = 'snapshot',
81
+ reason = 'manual',
82
+ source = 'workspace-evolver',
83
+ metadata = {},
84
+ state = undefined,
85
+ } = {}, feedbackDir = resolveFeedbackDir()) {
86
+ const paths = getEvolutionPaths(feedbackDir);
87
+ const snapshotId = `evo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
88
+ const normalizedState = normalizeState(state || readEvolutionState(feedbackDir));
89
+ const payload = {
90
+ snapshotId,
91
+ label,
92
+ reason,
93
+ source,
94
+ metadata,
95
+ capturedAt: new Date().toISOString(),
96
+ state: normalizedState,
97
+ };
98
+ const snapshotPath = path.join(paths.snapshotsDir, `${snapshotId}.json`);
99
+
100
+ ensureDir(paths.snapshotsDir);
101
+ fs.writeFileSync(snapshotPath, `${JSON.stringify(payload, null, 2)}\n`);
102
+
103
+ return {
104
+ snapshotId,
105
+ snapshotPath,
106
+ payload,
107
+ };
108
+ }
109
+
110
+ function applyAcceptedMutation({
111
+ targetKey,
112
+ nextValue,
113
+ experimentId = null,
114
+ summary = '',
115
+ metrics = null,
116
+ } = {}, feedbackDir = resolveFeedbackDir()) {
117
+ if (!targetKey) {
118
+ throw new Error('applyAcceptedMutation requires targetKey');
119
+ }
120
+ if (!Number.isFinite(nextValue)) {
121
+ throw new Error('applyAcceptedMutation requires a numeric nextValue');
122
+ }
123
+
124
+ const paths = getEvolutionPaths(feedbackDir);
125
+ const currentState = readEvolutionState(feedbackDir);
126
+ const previousValue = currentState.settings[targetKey];
127
+ const rollbackSnapshot = captureEvolutionSnapshot({
128
+ label: `rollback-${targetKey}`,
129
+ reason: 'accepted-mutation',
130
+ source: 'workspace-evolver',
131
+ metadata: {
132
+ targetKey,
133
+ previousValue,
134
+ nextValue,
135
+ experimentId,
136
+ },
137
+ state: currentState,
138
+ }, feedbackDir);
139
+
140
+ const entry = {
141
+ id: `mutation_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
142
+ experimentId,
143
+ targetKey,
144
+ previousValue,
145
+ nextValue,
146
+ summary,
147
+ metrics,
148
+ rollbackSnapshotId: rollbackSnapshot.snapshotId,
149
+ acceptedAt: new Date().toISOString(),
150
+ };
151
+
152
+ const nextState = writeEvolutionState({
153
+ ...currentState,
154
+ acceptedMutations: [...currentState.acceptedMutations, entry],
155
+ settings: {
156
+ ...currentState.settings,
157
+ [targetKey]: nextValue,
158
+ },
159
+ }, feedbackDir);
160
+
161
+ appendJSONL(paths.historyPath, {
162
+ type: 'mutation_kept',
163
+ targetKey,
164
+ previousValue,
165
+ nextValue,
166
+ experimentId,
167
+ rollbackSnapshotId: rollbackSnapshot.snapshotId,
168
+ timestamp: new Date().toISOString(),
169
+ });
170
+
171
+ return {
172
+ state: nextState,
173
+ entry,
174
+ rollbackSnapshot,
175
+ };
176
+ }
177
+
178
+ function restoreEvolutionSnapshot(snapshotId, feedbackDir = resolveFeedbackDir()) {
179
+ if (!snapshotId) {
180
+ throw new Error('restoreEvolutionSnapshot requires snapshotId');
181
+ }
182
+
183
+ const paths = getEvolutionPaths(feedbackDir);
184
+ const snapshotPath = path.join(paths.snapshotsDir, `${snapshotId}.json`);
185
+ if (!fs.existsSync(snapshotPath)) {
186
+ throw new Error(`Evolution snapshot not found: ${snapshotId}`);
187
+ }
188
+
189
+ const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
190
+ const restoredState = writeEvolutionState(snapshot.state, feedbackDir);
191
+ appendJSONL(paths.historyPath, {
192
+ type: 'snapshot_restored',
193
+ snapshotId,
194
+ timestamp: new Date().toISOString(),
195
+ });
196
+
197
+ return {
198
+ snapshotId,
199
+ snapshotPath,
200
+ state: restoredState,
201
+ };
202
+ }
203
+
204
+ function withTemporaryEvolutionSettings(patch, fn, feedbackDir = resolveFeedbackDir()) {
205
+ const paths = getEvolutionPaths(feedbackDir);
206
+ const originalExists = fs.existsSync(paths.statePath);
207
+ const originalContent = originalExists ? fs.readFileSync(paths.statePath, 'utf8') : null;
208
+ const currentState = readEvolutionState(feedbackDir);
209
+
210
+ writeEvolutionState({
211
+ ...currentState,
212
+ settings: {
213
+ ...currentState.settings,
214
+ ...(patch || {}),
215
+ },
216
+ }, feedbackDir);
217
+
218
+ try {
219
+ return fn();
220
+ } finally {
221
+ if (!originalExists) {
222
+ fs.rmSync(paths.statePath, { force: true });
223
+ } else {
224
+ ensureDir(path.dirname(paths.statePath));
225
+ fs.writeFileSync(paths.statePath, originalContent);
226
+ }
227
+ }
228
+ }
229
+
230
+ module.exports = {
231
+ DEFAULT_SETTINGS,
232
+ applyAcceptedMutation,
233
+ captureEvolutionSnapshot,
234
+ getEffectiveSetting,
235
+ getEvolutionPaths,
236
+ readEvolutionState,
237
+ resolveFeedbackDir,
238
+ restoreEvolutionSnapshot,
239
+ withTemporaryEvolutionSettings,
240
+ writeEvolutionState,
241
+ };
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+ /**
3
+ * Experiment Tracker (AUTORESEARCH-01)
4
+ *
5
+ * Tracks autonomous iteration experiments inspired by Karpathy's autoresearch.
6
+ * Each experiment = a config mutation + test run + measurable score.
7
+ * Keeps/discards based on whether score improves over baseline.
8
+ *
9
+ * Persists experiments to .thumbgate/experiments.jsonl and writes a progress
10
+ * summary to .thumbgate/experiment-progress.json.
11
+ *
12
+ * Zero external dependencies — uses only node:* and existing project modules.
13
+ *
14
+ * Exports: createExperiment, recordResult, getProgress, getBestExperiment,
15
+ * loadExperiments, EXPERIMENT_LOG_PATH
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { getFeedbackPaths, readJSONL } = require('./feedback-loop');
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Paths
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function getExperimentPaths() {
27
+ const { FEEDBACK_DIR } = getFeedbackPaths();
28
+ return {
29
+ logPath: path.join(FEEDBACK_DIR, 'experiments.jsonl'),
30
+ progressPath: path.join(FEEDBACK_DIR, 'experiment-progress.json'),
31
+ };
32
+ }
33
+
34
+ function ensureDir(dirPath) {
35
+ if (!fs.existsSync(dirPath)) {
36
+ fs.mkdirSync(dirPath, { recursive: true });
37
+ }
38
+ }
39
+
40
+ function appendJSONL(filePath, record) {
41
+ ensureDir(path.dirname(filePath));
42
+ fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`);
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Experiment Lifecycle
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Create a new experiment record. Does NOT execute anything — just records
51
+ * the intent so the runner can fill in results later.
52
+ *
53
+ * @param {object} params
54
+ * @param {string} params.name - Human-readable experiment name
55
+ * @param {string} params.hypothesis - What change is being tested
56
+ * @param {string} params.mutationType - Category of mutation (config|prompt|code|threshold)
57
+ * @param {object} [params.mutation] - The actual mutation applied (key/value diff)
58
+ * @param {string} [params.branch] - Git branch name for this experiment
59
+ * @returns {object} experiment record with id and status='pending'
60
+ */
61
+ function createExperiment(params) {
62
+ if (!params || !params.name || !params.hypothesis) {
63
+ throw new Error('Experiment requires name and hypothesis');
64
+ }
65
+
66
+ const validMutationTypes = ['config', 'prompt', 'code', 'threshold'];
67
+ const mutationType = params.mutationType || 'config';
68
+ if (!validMutationTypes.includes(mutationType)) {
69
+ throw new Error(`Invalid mutationType "${mutationType}". Must be one of: ${validMutationTypes.join(', ')}`);
70
+ }
71
+
72
+ const experiment = {
73
+ id: `exp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
74
+ name: params.name,
75
+ hypothesis: params.hypothesis,
76
+ mutationType,
77
+ mutation: params.mutation || null,
78
+ branch: params.branch || null,
79
+ status: 'pending',
80
+ createdAt: new Date().toISOString(),
81
+ completedAt: null,
82
+ baseline: null,
83
+ result: null,
84
+ score: null,
85
+ kept: null,
86
+ reason: null,
87
+ };
88
+
89
+ const { logPath } = getExperimentPaths();
90
+ appendJSONL(logPath, experiment);
91
+ return experiment;
92
+ }
93
+
94
+ /**
95
+ * Record the result of a completed experiment.
96
+ *
97
+ * @param {object} params
98
+ * @param {string} params.experimentId - ID from createExperiment
99
+ * @param {number} params.score - Measured score (higher = better)
100
+ * @param {number} params.baseline - Baseline score to compare against
101
+ * @param {boolean} [params.testsPassed] - Whether the test suite passed
102
+ * @param {object} [params.metrics] - Additional metrics (coverage, duration, etc.)
103
+ * @returns {object} Updated experiment with kept/discarded decision
104
+ */
105
+ function recordResult(params) {
106
+ if (!params || !params.experimentId) {
107
+ throw new Error('recordResult requires experimentId');
108
+ }
109
+ if (typeof params.score !== 'number' || typeof params.baseline !== 'number') {
110
+ throw new Error('recordResult requires numeric score and baseline');
111
+ }
112
+
113
+ const { logPath } = getExperimentPaths();
114
+ const experiments = loadExperiments();
115
+ const experiment = experiments.find(e => e.id === params.experimentId);
116
+
117
+ if (!experiment) {
118
+ throw new Error(`Experiment ${params.experimentId} not found`);
119
+ }
120
+
121
+ const improved = params.score > params.baseline;
122
+ const testsPassed = params.testsPassed !== false;
123
+ const kept = improved && testsPassed;
124
+
125
+ const result = {
126
+ ...experiment,
127
+ status: 'completed',
128
+ completedAt: new Date().toISOString(),
129
+ baseline: params.baseline,
130
+ score: params.score,
131
+ delta: params.score - params.baseline,
132
+ testsPassed,
133
+ metrics: params.metrics || null,
134
+ kept,
135
+ reason: !testsPassed
136
+ ? 'Tests failed — discarded'
137
+ : improved
138
+ ? `Score improved by ${(params.score - params.baseline).toFixed(4)}`
139
+ : `Score did not improve (${params.score} <= ${params.baseline})`,
140
+ };
141
+
142
+ appendJSONL(logPath, result);
143
+ updateProgress();
144
+ return result;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // Progress Tracking
149
+ // ---------------------------------------------------------------------------
150
+
151
+ /**
152
+ * Load all experiment records from the JSONL log.
153
+ * @returns {object[]}
154
+ */
155
+ function loadExperiments() {
156
+ const { logPath } = getExperimentPaths();
157
+ return readJSONL(logPath);
158
+ }
159
+
160
+ /**
161
+ * Recompute and persist experiment progress summary.
162
+ * @returns {object} progress summary
163
+ */
164
+ function updateProgress() {
165
+ const experiments = loadExperiments();
166
+ const completed = experiments.filter(e => e.status === 'completed');
167
+ const kept = completed.filter(e => e.kept === true);
168
+ const discarded = completed.filter(e => e.kept === false);
169
+ const pending = experiments.filter(e => e.status === 'pending');
170
+
171
+ const bestExperiment = kept.length > 0
172
+ ? kept.reduce((best, e) => (e.delta || 0) > (best.delta || 0) ? e : best, kept[0])
173
+ : null;
174
+
175
+ const progress = {
176
+ totalExperiments: experiments.length,
177
+ completed: completed.length,
178
+ kept: kept.length,
179
+ discarded: discarded.length,
180
+ pending: pending.length,
181
+ keepRate: completed.length > 0
182
+ ? (kept.length / completed.length * 100).toFixed(1)
183
+ : '0.0',
184
+ bestExperiment: bestExperiment
185
+ ? { id: bestExperiment.id, name: bestExperiment.name, delta: bestExperiment.delta }
186
+ : null,
187
+ lastUpdated: new Date().toISOString(),
188
+ };
189
+
190
+ const { progressPath } = getExperimentPaths();
191
+ ensureDir(path.dirname(progressPath));
192
+ fs.writeFileSync(progressPath, JSON.stringify(progress, null, 2) + '\n');
193
+ return progress;
194
+ }
195
+
196
+ /**
197
+ * Get current experiment progress.
198
+ * @returns {object} progress summary
199
+ */
200
+ function getProgress() {
201
+ const { progressPath } = getExperimentPaths();
202
+ if (fs.existsSync(progressPath)) {
203
+ try {
204
+ return JSON.parse(fs.readFileSync(progressPath, 'utf-8'));
205
+ } catch {
206
+ // Fall through to recompute
207
+ }
208
+ }
209
+ return updateProgress();
210
+ }
211
+
212
+ /**
213
+ * Get the best-performing kept experiment.
214
+ * @returns {object|null}
215
+ */
216
+ function getBestExperiment() {
217
+ const experiments = loadExperiments();
218
+ const kept = experiments.filter(e => e.kept === true);
219
+ if (kept.length === 0) return null;
220
+ return kept.reduce((best, e) => (e.delta || 0) > (best.delta || 0) ? e : best, kept[0]);
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // CLI
225
+ // ---------------------------------------------------------------------------
226
+
227
+ if (require.main === module) {
228
+ const args = {};
229
+ process.argv.slice(2).forEach(arg => {
230
+ if (!arg.startsWith('--')) return;
231
+ const [key, ...rest] = arg.slice(2).split('=');
232
+ args[key] = rest.length > 0 ? rest.join('=') : true;
233
+ });
234
+
235
+ if (args.progress) {
236
+ console.log(JSON.stringify(getProgress(), null, 2));
237
+ } else if (args.best) {
238
+ const best = getBestExperiment();
239
+ console.log(best ? JSON.stringify(best, null, 2) : 'No kept experiments yet.');
240
+ } else if (args.list) {
241
+ const exps = loadExperiments().filter(e => e.status === 'completed');
242
+ console.log(`${exps.length} completed experiments (${exps.filter(e => e.kept).length} kept)`);
243
+ exps.slice(-10).forEach(e => {
244
+ const icon = e.kept ? '✓' : '✗';
245
+ console.log(` ${icon} ${e.name} — delta: ${(e.delta || 0).toFixed(4)}`);
246
+ });
247
+ } else {
248
+ console.log(`Usage:
249
+ node scripts/experiment-tracker.js --progress
250
+ node scripts/experiment-tracker.js --best
251
+ node scripts/experiment-tracker.js --list`);
252
+ }
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Exports
257
+ // ---------------------------------------------------------------------------
258
+
259
+ module.exports = {
260
+ createExperiment,
261
+ recordResult,
262
+ getProgress,
263
+ getBestExperiment,
264
+ loadExperiments,
265
+ updateProgress,
266
+ getExperimentPaths,
267
+ };