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,230 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const PROJECT_ROOT = path.join(__dirname, '..');
6
+ const DEFAULT_RUBRIC_PATH = path.join(PROJECT_ROOT, 'config', 'rubrics', 'default-v1.json');
7
+
8
+ function getRubricPath() {
9
+ return process.env.THUMBGATE_RUBRIC_PATH || DEFAULT_RUBRIC_PATH;
10
+ }
11
+
12
+ function loadRubricConfig() {
13
+ const raw = fs.readFileSync(getRubricPath(), 'utf-8');
14
+ const rubric = JSON.parse(raw);
15
+
16
+ if (!rubric || typeof rubric !== 'object') {
17
+ throw new Error('Invalid rubric config: expected object');
18
+ }
19
+ if (!rubric.rubricId || typeof rubric.rubricId !== 'string') {
20
+ throw new Error('Invalid rubric config: rubricId is required');
21
+ }
22
+ if (!Array.isArray(rubric.criteria) || rubric.criteria.length === 0) {
23
+ throw new Error('Invalid rubric config: criteria must be a non-empty array');
24
+ }
25
+
26
+ const seen = new Set();
27
+ let totalWeight = 0;
28
+ rubric.criteria.forEach((criterion) => {
29
+ if (!criterion.id || typeof criterion.id !== 'string') {
30
+ throw new Error('Invalid rubric config: criterion id is required');
31
+ }
32
+ if (seen.has(criterion.id)) {
33
+ throw new Error(`Invalid rubric config: duplicate criterion '${criterion.id}'`);
34
+ }
35
+ seen.add(criterion.id);
36
+
37
+ const weight = Number(criterion.weight);
38
+ if (!Number.isFinite(weight) || weight <= 0) {
39
+ throw new Error(`Invalid rubric config: criterion '${criterion.id}' has invalid weight`);
40
+ }
41
+ totalWeight += weight;
42
+
43
+ const minPassingScore = Number(criterion.minPassingScore || 3);
44
+ if (!Number.isFinite(minPassingScore) || minPassingScore < 1 || minPassingScore > 5) {
45
+ throw new Error(`Invalid rubric config: criterion '${criterion.id}' has invalid minPassingScore`);
46
+ }
47
+ });
48
+
49
+ if (Math.abs(totalWeight - 1) > 0.001) {
50
+ throw new Error(`Invalid rubric config: weights must sum to 1.0 (got ${totalWeight.toFixed(3)})`);
51
+ }
52
+
53
+ return rubric;
54
+ }
55
+
56
+ function parseRubricScores(input) {
57
+ if (input == null) return [];
58
+ if (Array.isArray(input)) return input;
59
+ if (typeof input === 'string') {
60
+ const trimmed = input.trim();
61
+ if (!trimmed) return [];
62
+ const parsed = JSON.parse(trimmed);
63
+ if (!Array.isArray(parsed)) {
64
+ throw new Error('rubricScores must be an array');
65
+ }
66
+ return parsed;
67
+ }
68
+ throw new Error('rubricScores must be array or JSON string');
69
+ }
70
+
71
+ function normalizeRubricScores(rawScores, rubric = loadRubricConfig()) {
72
+ const scores = parseRubricScores(rawScores);
73
+ if (scores.length === 0) return [];
74
+
75
+ const criterionMap = new Map(rubric.criteria.map((c) => [c.id, c]));
76
+ return scores.map((item, index) => {
77
+ if (!item || typeof item !== 'object') {
78
+ throw new Error(`rubricScores[${index}] must be an object`);
79
+ }
80
+ const criterion = String(item.criterion || '').trim();
81
+ if (!criterionMap.has(criterion)) {
82
+ throw new Error(`rubricScores[${index}] unknown criterion '${criterion}'`);
83
+ }
84
+
85
+ const score = Number(item.score);
86
+ if (!Number.isFinite(score) || score < 1 || score > 5) {
87
+ throw new Error(`rubricScores[${index}] score must be between 1 and 5`);
88
+ }
89
+
90
+ return {
91
+ criterion,
92
+ score,
93
+ evidence: item.evidence ? String(item.evidence).trim() : '',
94
+ judge: item.judge ? String(item.judge).trim() : 'unknown',
95
+ };
96
+ });
97
+ }
98
+
99
+ function groupByCriterion(scores) {
100
+ const grouped = {};
101
+ scores.forEach((scoreItem) => {
102
+ if (!grouped[scoreItem.criterion]) grouped[scoreItem.criterion] = [];
103
+ grouped[scoreItem.criterion].push(scoreItem);
104
+ });
105
+ return grouped;
106
+ }
107
+
108
+ function evaluateGuardrails(guardrails, rubric = loadRubricConfig()) {
109
+ const input = guardrails && typeof guardrails === 'object' ? guardrails : {};
110
+ const expected = Array.isArray(rubric.guardrails) ? rubric.guardrails : [];
111
+
112
+ const status = {};
113
+ const failed = [];
114
+ expected.forEach((g) => {
115
+ const value = input[g.key];
116
+ const normalized = value === true ? true : value === false ? false : null;
117
+ status[g.key] = normalized;
118
+ if (normalized === false) failed.push(g.key);
119
+ });
120
+
121
+ return {
122
+ status,
123
+ failed,
124
+ };
125
+ }
126
+
127
+ function evaluateJudgeAgreement(scoresByCriterion) {
128
+ const disagreements = [];
129
+ for (const [criterion, entries] of Object.entries(scoresByCriterion)) {
130
+ if (entries.length < 2) continue;
131
+ const values = entries.map((e) => e.score);
132
+ const max = Math.max(...values);
133
+ const min = Math.min(...values);
134
+ if (max - min >= 2) {
135
+ disagreements.push({
136
+ criterion,
137
+ max,
138
+ min,
139
+ judges: entries.map((e) => e.judge),
140
+ });
141
+ }
142
+ }
143
+ return disagreements;
144
+ }
145
+
146
+ function buildRubricEvaluation({ rubricScores, guardrails } = {}) {
147
+ const rubric = loadRubricConfig();
148
+ const normalizedScores = normalizeRubricScores(rubricScores, rubric);
149
+ const scoresByCriterion = groupByCriterion(normalizedScores);
150
+ const guardrailResult = evaluateGuardrails(guardrails, rubric);
151
+
152
+ const criterionBreakdown = {};
153
+ const failingCriteria = [];
154
+ const missingEvidenceClaims = [];
155
+ let weightedScore = 0;
156
+
157
+ rubric.criteria.forEach((criterion) => {
158
+ const entries = scoresByCriterion[criterion.id] || [];
159
+ const avg = entries.length > 0
160
+ ? entries.reduce((sum, item) => sum + item.score, 0) / entries.length
161
+ : null;
162
+
163
+ criterionBreakdown[criterion.id] = {
164
+ averageScore: avg,
165
+ minPassingScore: Number(criterion.minPassingScore || 3),
166
+ judgeCount: entries.length,
167
+ label: criterion.label || criterion.id,
168
+ };
169
+
170
+ if (avg != null) {
171
+ weightedScore += (avg / 5) * Number(criterion.weight);
172
+ if (avg < Number(criterion.minPassingScore || 3)) {
173
+ failingCriteria.push(criterion.id);
174
+ }
175
+ }
176
+
177
+ if (criterion.requiresEvidence && entries.some((entry) => entry.score >= 4 && !entry.evidence)) {
178
+ missingEvidenceClaims.push(criterion.id);
179
+ }
180
+ });
181
+
182
+ const judgeDisagreements = evaluateJudgeAgreement(scoresByCriterion);
183
+ const blockReasons = [];
184
+ if (failingCriteria.length > 0) blockReasons.push(`failing_criteria:${failingCriteria.join(',')}`);
185
+ if (guardrailResult.failed.length > 0) blockReasons.push(`failed_guardrails:${guardrailResult.failed.join(',')}`);
186
+ if (judgeDisagreements.length > 0) blockReasons.push('judge_disagreement');
187
+ if (missingEvidenceClaims.length > 0) blockReasons.push(`missing_evidence:${missingEvidenceClaims.join(',')}`);
188
+
189
+ return {
190
+ rubricId: rubric.rubricId,
191
+ rubricVersion: rubric.version || 1,
192
+ weightedScore: Math.round(weightedScore * 1000) / 1000,
193
+ criterionBreakdown,
194
+ failingCriteria,
195
+ guardrails: guardrailResult.status,
196
+ failingGuardrails: guardrailResult.failed,
197
+ judgeDisagreements,
198
+ missingEvidenceClaims,
199
+ promotionEligible: blockReasons.length === 0,
200
+ blockReasons,
201
+ rubricScores: normalizedScores,
202
+ };
203
+ }
204
+
205
+ module.exports = {
206
+ DEFAULT_RUBRIC_PATH,
207
+ getRubricPath,
208
+ loadRubricConfig,
209
+ parseRubricScores,
210
+ normalizeRubricScores,
211
+ evaluateGuardrails,
212
+ evaluateJudgeAgreement,
213
+ buildRubricEvaluation,
214
+ };
215
+
216
+ if (require.main === module) {
217
+ const args = process.argv.slice(2);
218
+ const scoresArg = args.find((arg) => arg.startsWith('--scores='));
219
+ const guardrailsArg = args.find((arg) => arg.startsWith('--guardrails='));
220
+
221
+ if (!scoresArg) {
222
+ console.log(JSON.stringify(loadRubricConfig(), null, 2));
223
+ process.exit(0);
224
+ }
225
+
226
+ const scores = scoresArg.replace('--scores=', '');
227
+ const guardrails = guardrailsArg ? JSON.parse(guardrailsArg.replace('--guardrails=', '')) : {};
228
+ const result = buildRubricEvaluation({ rubricScores: scores, guardrails });
229
+ console.log(JSON.stringify(result, null, 2));
230
+ }
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+ const { execSync } = require('child_process');
8
+ const { buildAgenticDataPipelineJobSpec } = require('./agentic-data-pipeline');
9
+
10
+ const SCHEDULES_DIR = path.join(os.homedir(), '.thumbgate', 'schedules');
11
+ const PLIST_PREFIX = 'com.thumbgate.schedule';
12
+
13
+ function ensureDir() {
14
+ if (!fs.existsSync(SCHEDULES_DIR)) fs.mkdirSync(SCHEDULES_DIR, { recursive: true });
15
+ }
16
+
17
+ function escapePlistString(value) {
18
+ return String(value || '')
19
+ .replace(/&/g, '&amp;')
20
+ .replace(/</g, '&lt;')
21
+ .replace(/>/g, '&gt;')
22
+ .replace(/"/g, '&quot;')
23
+ .replace(/'/g, '&#39;');
24
+ }
25
+
26
+ /**
27
+ * Parse a simple cron-like spec into LaunchAgent calendar intervals
28
+ * Supports: "daily 9:00", "weekly monday 8:30", "hourly", "every 6h"
29
+ */
30
+ function parseCronSpec(spec) {
31
+ const s = spec.toLowerCase().trim();
32
+
33
+ if (s === 'hourly') {
34
+ return { Minute: 0 };
35
+ }
36
+
37
+ const everyHMatch = s.match(/^every\s+(\d+)\s*h/);
38
+ if (everyHMatch) {
39
+ return { Minute: 0 }; // LaunchAgent doesn't support "every Nh" natively, use hourly
40
+ }
41
+
42
+ const dailyMatch = s.match(/^daily\s+(\d{1,2}):(\d{2})$/);
43
+ if (dailyMatch) {
44
+ return { Hour: parseInt(dailyMatch[1]), Minute: parseInt(dailyMatch[2]) };
45
+ }
46
+
47
+ const weeklyMatch = s.match(/^weekly\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)\s+(\d{1,2}):(\d{2})$/);
48
+ if (weeklyMatch) {
49
+ const dayMap = { sunday: 0, monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6 };
50
+ return {
51
+ Weekday: dayMap[weeklyMatch[1]],
52
+ Hour: parseInt(weeklyMatch[2]),
53
+ Minute: parseInt(weeklyMatch[3]),
54
+ };
55
+ }
56
+
57
+ // Fallback: try to parse as "HH:MM" (daily)
58
+ const timeMatch = s.match(/^(\d{1,2}):(\d{2})$/);
59
+ if (timeMatch) {
60
+ return { Hour: parseInt(timeMatch[1]), Minute: parseInt(timeMatch[2]) };
61
+ }
62
+
63
+ return null;
64
+ }
65
+
66
+ function generatePlist(schedule) {
67
+ const label = escapePlistString(`${PLIST_PREFIX}.${schedule.id}`);
68
+ const interval = schedule.calendarInterval;
69
+
70
+ let intervalXml = '<dict>\n';
71
+ for (const [key, value] of Object.entries(interval)) {
72
+ intervalXml += ` <key>${key}</key>\n <integer>${value}</integer>\n`;
73
+ }
74
+ intervalXml += ' </dict>';
75
+
76
+ const logDir = escapePlistString(path.join(os.homedir(), '.thumbgate', 'logs'));
77
+ const workingDirectory = escapePlistString(schedule.workingDirectory || os.homedir());
78
+ const command = escapePlistString(schedule.command);
79
+ const homeDir = escapePlistString(os.homedir());
80
+ const escapedScheduleId = escapePlistString(schedule.id);
81
+
82
+ return `<?xml version="1.0" encoding="UTF-8"?>
83
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
84
+ <plist version="1.0">
85
+ <dict>
86
+ <key>Label</key>
87
+ <string>${label}</string>
88
+ <key>ProgramArguments</key>
89
+ <array>
90
+ <string>${process.execPath}</string>
91
+ <string>-e</string>
92
+ <string>${command}</string>
93
+ </array>
94
+ <key>WorkingDirectory</key>
95
+ <string>${workingDirectory}</string>
96
+ <key>StartCalendarInterval</key>
97
+ ${intervalXml}
98
+ <key>StandardOutPath</key>
99
+ <string>${logDir}/schedule-${escapedScheduleId}.log</string>
100
+ <key>StandardErrorPath</key>
101
+ <string>${logDir}/schedule-${escapedScheduleId}-error.log</string>
102
+ <key>EnvironmentVariables</key>
103
+ <dict>
104
+ <key>PATH</key>
105
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
106
+ <key>HOME</key>
107
+ <string>${homeDir}</string>
108
+ </dict>
109
+ </dict>
110
+ </plist>`;
111
+ }
112
+
113
+ function buildManagedScheduleCommand(params = {}) {
114
+ if (!params.jobFile) {
115
+ throw new Error('buildManagedScheduleCommand requires jobFile');
116
+ }
117
+
118
+ const runnerPath = path.join(__dirname, 'async-job-runner.js');
119
+ const jobFile = path.resolve(params.jobFile);
120
+ const autoResume = params.autoResume !== false;
121
+
122
+ return [
123
+ `const runner = require(${JSON.stringify(runnerPath)});`,
124
+ `const result = runner.runJobFromFile(${JSON.stringify(jobFile)}, ${JSON.stringify({ autoResume })});`,
125
+ 'process.stdout.write(JSON.stringify(result, null, 2) + "\\n");',
126
+ 'if (["failed", "cancelled"].includes(result.status)) process.exit(1);',
127
+ ].join(' ');
128
+ }
129
+
130
+ function buildAgenticDataPipelineSchedule(params = {}) {
131
+ const id = params.id || params.name || 'agentic-data-pipeline';
132
+ const jobFile = path.resolve(
133
+ params.jobFile || path.join(SCHEDULES_DIR, `${id}.job.json`)
134
+ );
135
+ const jobSpec = buildAgenticDataPipelineJobSpec({
136
+ jobId: id,
137
+ feedbackDir: params.feedbackDir,
138
+ outDir: params.outDir,
139
+ window: params.window,
140
+ liveBilling: params.liveBilling,
141
+ recordWorkflowRun: params.recordWorkflowRun,
142
+ });
143
+
144
+ return {
145
+ id,
146
+ jobFile,
147
+ jobSpec,
148
+ command: buildManagedScheduleCommand({
149
+ jobFile,
150
+ autoResume: params.autoResume !== false,
151
+ }),
152
+ };
153
+ }
154
+
155
+ function createSchedule(params) {
156
+ ensureDir();
157
+
158
+ const id = params.id || params.name || `sched_${Date.now()}`;
159
+ const calendarInterval = parseCronSpec(params.schedule);
160
+ if (!calendarInterval) {
161
+ return { success: false, error: `Cannot parse schedule: "${params.schedule}". Use formats like "daily 9:00", "weekly monday 8:30", "hourly"` };
162
+ }
163
+
164
+ const jobFile = params.jobFile ? path.resolve(params.jobFile) : null;
165
+ const command = params.command || (jobFile ? buildManagedScheduleCommand({
166
+ jobFile,
167
+ autoResume: params.autoResume !== false,
168
+ }) : null);
169
+
170
+ if (!command) {
171
+ return { success: false, error: 'Schedule requires command or jobFile' };
172
+ }
173
+
174
+ const schedule = {
175
+ id,
176
+ name: params.name || id,
177
+ description: params.description || '',
178
+ schedule: params.schedule,
179
+ command,
180
+ jobFile,
181
+ resumePolicy: jobFile ? (params.autoResume !== false ? 'auto_resume' : 'fresh_only') : null,
182
+ workingDirectory: params.workingDirectory || (jobFile ? path.dirname(jobFile) : process.cwd()),
183
+ calendarInterval,
184
+ createdAt: new Date().toISOString(),
185
+ };
186
+
187
+ // Save schedule metadata
188
+ const metaPath = path.join(SCHEDULES_DIR, `${id}.json`);
189
+ fs.writeFileSync(metaPath, JSON.stringify(schedule, null, 2), 'utf8');
190
+
191
+ // Generate and install LaunchAgent
192
+ if (process.platform === 'darwin') {
193
+ const plistContent = generatePlist(schedule);
194
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${PLIST_PREFIX}.${id}.plist`);
195
+ const logDir = path.join(os.homedir(), '.thumbgate', 'logs');
196
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
197
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
198
+
199
+ fs.writeFileSync(plistPath, plistContent, 'utf8');
200
+ try {
201
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: 'pipe' });
202
+ } catch { /* not loaded */ }
203
+ try {
204
+ execSync(`launchctl load "${plistPath}"`, { stdio: 'pipe' });
205
+ } catch (e) {
206
+ return { success: false, error: `Failed to load LaunchAgent: ${e.message}`, schedule };
207
+ }
208
+
209
+ return { success: true, schedule, plistPath, message: `Schedule "${id}" created and loaded` };
210
+ }
211
+
212
+ // Linux keeps the schedule metadata so operators can install it via user crontab tooling.
213
+ return { success: true, schedule, message: `Schedule "${id}" saved for Linux crontab installation` };
214
+ }
215
+
216
+ function listSchedules() {
217
+ ensureDir();
218
+ const files = fs.readdirSync(SCHEDULES_DIR).filter(f => f.endsWith('.json'));
219
+ return files.map(f => {
220
+ try {
221
+ return JSON.parse(fs.readFileSync(path.join(SCHEDULES_DIR, f), 'utf8'));
222
+ } catch {
223
+ return { id: f.replace('.json', ''), error: 'corrupt' };
224
+ }
225
+ });
226
+ }
227
+
228
+ function deleteSchedule(id) {
229
+ const metaPath = path.join(SCHEDULES_DIR, `${id}.json`);
230
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${PLIST_PREFIX}.${id}.plist`);
231
+
232
+ try {
233
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: 'pipe' });
234
+ } catch { /* not loaded */ }
235
+
236
+ if (fs.existsSync(plistPath)) fs.unlinkSync(plistPath);
237
+ if (fs.existsSync(metaPath)) fs.unlinkSync(metaPath);
238
+
239
+ return { success: true, message: `Schedule "${id}" deleted` };
240
+ }
241
+
242
+ module.exports = {
243
+ createSchedule,
244
+ listSchedules,
245
+ deleteSchedule,
246
+ escapePlistString,
247
+ generatePlist,
248
+ parseCronSpec,
249
+ buildManagedScheduleCommand,
250
+ buildAgenticDataPipelineSchedule,
251
+ };