thumbgate 0.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1483 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +286 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1195 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +93 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +231 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * History Distiller — Self-Healing Brain for ThumbGate (Pro feature).
6
+ *
7
+ * When a user gives thumbs-down, this module:
8
+ * 1. Takes the last N conversation messages (chatHistory)
9
+ * 2. Identifies the failed tool call and its context
10
+ * 3. Auto-proposes a lesson: "I noticed X. I've recorded a rule to NEVER do X. Correct?"
11
+ * 4. Creates a prevention rule if the user confirms
12
+ *
13
+ * This closes the gap from "manual guardrail" to "self-healing brain."
14
+ * Strategic value: justifies Pro $19/mo subscription via outcome-based intelligence.
15
+ */
16
+
17
+ const { createLesson, inferFromSurroundingMessages } = require('./lesson-inference');
18
+ const contextfs = require('./contextfs');
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Chat History Analysis
22
+ // ---------------------------------------------------------------------------
23
+
24
+
25
+ const ANTI_PATTERNS = [
26
+ { pattern: /\b(?:tailwind|tw-)\b/i, label: 'Tailwind CSS', ruleTemplate: 'NEVER use Tailwind CSS in this project' },
27
+ { pattern: /(?:force[- ]?push|push\s*--force|--force)\b/i, label: 'force push', ruleTemplate: 'NEVER force-push to any branch' },
28
+ { pattern: /\b(?:rm\s+-rf|delete\s+all)\b/i, label: 'destructive deletion', ruleTemplate: 'NEVER run destructive delete commands without confirmation' },
29
+ { pattern: /\bskip\s*(?:test|ci|check)/i, label: 'skipping tests', ruleTemplate: 'NEVER skip tests or CI checks' },
30
+ { pattern: /\b(?:mock(?:ed|ing)?|stub(?:bed|bing)?)\b.*\b(?:database|db)\b/i, label: 'mocking database', ruleTemplate: 'NEVER mock the database — use real test instances' },
31
+ { pattern: /\b(?:hardcod|hard[- ]cod)/i, label: 'hardcoded values', ruleTemplate: 'NEVER hardcode secrets, URLs, or configuration values' },
32
+ { pattern: /\b(?:console\.log|print\s+debug)\b/i, label: 'debug logging', ruleTemplate: 'NEVER leave debug console.log/print statements in production code' },
33
+ { pattern: /\b(?:any\b.*type|:\s*any\b)/i, label: 'TypeScript any', ruleTemplate: 'NEVER use the `any` type — use proper type annotations' },
34
+ ];
35
+
36
+ /**
37
+ * Analyze chat history to find the correction pattern.
38
+ * Looks for: user corrected the agent about something → agent did it again → user gave thumbs down.
39
+ *
40
+ * @param {Array} chatHistory - Array of {role, content} messages, most recent last
41
+ * @param {Object} failedToolCall - The tool call that triggered the thumbs-down
42
+ * @returns {{ correction, antiPattern, proposedRule, confidence, evidence }}
43
+ */
44
+ function analyzeChatHistory(chatHistory, failedToolCall = null) {
45
+ const messages = Array.isArray(chatHistory) ? chatHistory : [];
46
+ if (messages.length === 0) return { correction: null, antiPattern: null, proposedRule: null, confidence: 0, evidence: [] };
47
+
48
+ const evidence = [];
49
+ let bestAntiPattern = null;
50
+ let userCorrection = null;
51
+
52
+ // Scan for user corrections (messages where user said "don't", "stop", "no", "never", "wrong")
53
+ const correctionPatterns = [
54
+ /\b(?:don'?t|do not|stop|never|wrong|no,?\s+(?:that|not|I said))\b/i,
55
+ /\b(?:I (?:told|said|asked) you|I already|we don'?t)\b/i,
56
+ /\b(?:should(?:n'?t| not)|must not|not supposed to)\b/i,
57
+ ];
58
+
59
+ for (const msg of messages) {
60
+ if (msg.role !== 'user') continue;
61
+ const text = msg.content || msg.text || '';
62
+ for (const cp of correctionPatterns) {
63
+ if (cp.test(text)) {
64
+ userCorrection = text.slice(0, 200);
65
+ evidence.push({ type: 'user_correction', text: userCorrection });
66
+ break;
67
+ }
68
+ }
69
+ }
70
+
71
+ // Scan assistant messages for anti-patterns
72
+ const assistantMessages = messages.filter((m) => m.role === 'assistant').map((m) => m.content || m.text || '');
73
+ const allAssistantText = assistantMessages.join('\n');
74
+
75
+ // Also check the failed tool call
76
+ const toolCallText = failedToolCall
77
+ ? `${failedToolCall.tool || ''} ${failedToolCall.input || ''} ${failedToolCall.output || ''}`
78
+ : '';
79
+ const combinedText = `${allAssistantText}\n${toolCallText}`;
80
+
81
+ for (const ap of ANTI_PATTERNS) {
82
+ if (ap.pattern.test(combinedText)) {
83
+ bestAntiPattern = ap;
84
+ evidence.push({ type: 'anti_pattern', label: ap.label });
85
+ break;
86
+ }
87
+ }
88
+
89
+ // Build proposed rule
90
+ let proposedRule = null;
91
+ let confidence = 0;
92
+
93
+ if (bestAntiPattern && userCorrection) {
94
+ proposedRule = bestAntiPattern.ruleTemplate;
95
+ confidence = 90;
96
+ } else if (bestAntiPattern) {
97
+ proposedRule = bestAntiPattern.ruleTemplate;
98
+ confidence = 60;
99
+ } else if (userCorrection) {
100
+ // Extract the "don't X" part as a rule
101
+ const dontMatch = userCorrection.match(/(?:don'?t|never|stop)\s+(.{5,60})/i);
102
+ if (dontMatch) {
103
+ proposedRule = `NEVER ${dontMatch[1].trim().replace(/[.!?]+$/, '')}`;
104
+ confidence = 50;
105
+ }
106
+ }
107
+
108
+ return {
109
+ correction: userCorrection,
110
+ antiPattern: bestAntiPattern ? bestAntiPattern.label : null,
111
+ proposedRule,
112
+ confidence,
113
+ evidence,
114
+ messageCount: messages.length,
115
+ };
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // History-Aware Distillation (the main entry point)
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Distill a lesson from chat history when thumbs-down is received.
124
+ * This is the "Reflector" agent — it takes conversation context and
125
+ * auto-proposes what went wrong + a prevention rule.
126
+ *
127
+ * @param {Object} opts
128
+ * @param {Array} opts.chatHistory - Last N messages [{role, content}]
129
+ * @param {Object} [opts.failedToolCall] - The tool call that failed
130
+ * @param {string} [opts.feedbackContext] - User's feedback context string
131
+ * @param {string} [opts.signal] - 'negative' or 'positive'
132
+ * @returns {{ lesson, proposedRule, confirmation, autoCreated }}
133
+ */
134
+ function distillFromHistory({ chatHistory = [], failedToolCall = null, feedbackContext = '', signal = 'negative' } = {}) {
135
+ // Step 1: Analyze chat history for corrections and anti-patterns
136
+ const analysis = analyzeChatHistory(chatHistory, failedToolCall);
137
+
138
+ // Step 2: Infer from surrounding messages (leverage existing module)
139
+ const inference = inferFromSurroundingMessages({
140
+ priorMessages: chatHistory.slice(-10),
141
+ signal,
142
+ feedbackContext,
143
+ });
144
+
145
+ // Step 3: Build the auto-proposed lesson
146
+ let proposedWhatWentWrong;
147
+ let proposedRule = analysis.proposedRule;
148
+
149
+ if (analysis.correction && analysis.antiPattern) {
150
+ proposedWhatWentWrong = `I noticed I used ${analysis.antiPattern} despite your earlier correction: "${analysis.correction.slice(0, 100)}". This has been recorded as a mistake.`;
151
+ } else if (analysis.antiPattern) {
152
+ proposedWhatWentWrong = `I detected a ${analysis.antiPattern} anti-pattern in my output that likely caused the issue.`;
153
+ } else if (analysis.correction) {
154
+ proposedWhatWentWrong = `You previously corrected me: "${analysis.correction.slice(0, 100)}". I may have repeated the same mistake.`;
155
+ } else if (inference.inferredAction) {
156
+ proposedWhatWentWrong = `The ${inference.inferredAction.type} action on ${inference.inferredAction.target} did not produce the expected result.`;
157
+ } else {
158
+ proposedWhatWentWrong = feedbackContext || 'The agent action did not meet expectations.';
159
+ }
160
+
161
+ // Step 4: Build confirmation message
162
+ const confirmation = proposedRule
163
+ ? `I've recorded a rule: "${proposedRule}". Correct?`
164
+ : `Lesson captured: "${proposedWhatWentWrong.slice(0, 100)}". Any corrections?`;
165
+
166
+ // Step 5: Auto-create the lesson
167
+ const lesson = createLesson({
168
+ signal,
169
+ inferredLesson: proposedWhatWentWrong,
170
+ triggerMessage: inference.triggerMessage,
171
+ priorSummary: inference.priorSummary,
172
+ confidence: analysis.confidence || inference.confidence,
173
+ tags: analysis.antiPattern ? [analysis.antiPattern] : [],
174
+ metadata: { distilled: true, hasCorrection: !!analysis.correction, hasAntiPattern: !!analysis.antiPattern },
175
+ });
176
+
177
+ // Step 6: If high confidence + anti-pattern, auto-install prevention rule
178
+ let ruleInstalled = false;
179
+ if (proposedRule && analysis.confidence >= 60) {
180
+ try {
181
+ contextfs.registerPreventionRules(`# Auto-Distilled Rule\n\n- ${proposedRule}\n\nSource: history-distiller (confidence: ${analysis.confidence}%)`, { source: 'history-distiller', lessonId: lesson.id });
182
+ ruleInstalled = true;
183
+ } catch { /* non-critical */ }
184
+ }
185
+
186
+ return {
187
+ lesson,
188
+ proposedWhatWentWrong,
189
+ proposedRule,
190
+ confirmation,
191
+ ruleInstalled,
192
+ analysis,
193
+ inference: { action: inference.inferredAction, confidence: inference.confidence },
194
+ autoCreated: true,
195
+ };
196
+ }
197
+
198
+ module.exports = {
199
+ ANTI_PATTERNS, analyzeChatHistory, distillFromHistory,
200
+ };
@@ -0,0 +1,100 @@
1
+ #!/bin/bash
2
+ # Claude Code UserPromptSubmit hook — auto-captures thumbs up/down feedback
3
+ # Triggered on every user message. Only acts on feedback signals.
4
+ # Shows full verbose output with storage paths, memory IDs, and stats.
5
+
6
+ PROMPT="$CLAUDE_USER_PROMPT"
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ CAPTURE="$SCRIPT_DIR/../.claude/scripts/feedback/capture-feedback.js"
9
+ PROMPT_GUARD="$SCRIPT_DIR/prompt-guard.js"
10
+ ACTIVE_CWD="${CLAUDE_PROJECT_DIR:-${PWD:-$(pwd)}}"
11
+ FEEDBACK_DIR="$(node -e "const path = require('path'); const { resolveFeedbackDir } = require(path.join(process.argv[1], 'feedback-paths.js')); process.stdout.write(resolveFeedbackDir({ cwd: process.argv[2] || process.cwd(), feedbackDir: process.env.THUMBGATE_FEEDBACK_DIR || undefined }));" "$SCRIPT_DIR" "$ACTIVE_CWD" 2>/dev/null)"
12
+ if [ -z "$FEEDBACK_DIR" ]; then
13
+ FEEDBACK_DIR="${THUMBGATE_FEEDBACK_DIR:-$ACTIVE_CWD/.rlhf}"
14
+ fi
15
+ FEEDBACK_LOG="$FEEDBACK_DIR/feedback-log.jsonl"
16
+ MEMORY_LOG="$FEEDBACK_DIR/memory-log.jsonl"
17
+
18
+ # Record the latest user prompt so statusline thumbs can distill lessons
19
+ # from recent conversation even when the click itself has no body payload.
20
+ THUMBGATE_CONVERSATION_TEXT="$PROMPT" node -e "
21
+ const { recordConversationEntry } = require(process.argv[1]);
22
+ recordConversationEntry({
23
+ author: 'user',
24
+ text: process.env.THUMBGATE_CONVERSATION_TEXT || '',
25
+ source: 'claude_user_prompt',
26
+ });
27
+ " "$SCRIPT_DIR/feedback-history-distiller.js" 2>/dev/null || true
28
+
29
+ # Normalize to lowercase for matching
30
+ LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
31
+
32
+ if [ -f "$PROMPT_GUARD" ]; then
33
+ GUARD_RESULT=$(node "$PROMPT_GUARD" 2>/dev/null || true)
34
+ if [ -n "$GUARD_RESULT" ]; then
35
+ echo "$GUARD_RESULT"
36
+ exit 0
37
+ fi
38
+ fi
39
+
40
+ capture_and_report() {
41
+ local SIGNAL="$1"
42
+
43
+ # Capture feedback (verbose output already shows IDs, signal, storage)
44
+ node "$CAPTURE" --feedback="$SIGNAL" --context="$PROMPT" --tags="auto-capture,hook"
45
+ local CAPTURE_STATUS=$?
46
+
47
+ if [ "$CAPTURE_STATUS" -eq 2 ]; then
48
+ echo "Reusable memory status: signal logged only. Add one specific sentence so the MCP can promote it."
49
+ echo ""
50
+ fi
51
+
52
+ # Show storage proof
53
+ echo ""
54
+ echo "Storage Proof:"
55
+ echo " Feedback log : $FEEDBACK_LOG ($(wc -l < "$FEEDBACK_LOG" 2>/dev/null || echo 0) entries)"
56
+ echo " Memory log : $MEMORY_LOG ($(wc -l < "$MEMORY_LOG" 2>/dev/null || echo 0) entries)"
57
+ echo " LanceDB : $FEEDBACK_DIR/lancedb/"
58
+ echo ""
59
+
60
+ # Show last entry written
61
+ echo "Last Entry Written:"
62
+ tail -1 "$FEEDBACK_LOG" 2>/dev/null | node -e "
63
+ const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
64
+ console.log(' ID :', d.id);
65
+ console.log(' Signal :', d.signal, '(' + d.actionType + ')');
66
+ console.log(' Context :', (d.context||'').slice(0,80));
67
+ console.log(' Tags :', (d.tags||[]).join(', '));
68
+ console.log(' Timestamp :', d.timestamp);
69
+ console.log(' Domain :', (d.richContext||{}).domain || 'general');
70
+ " 2>/dev/null
71
+
72
+ # Show cumulative stats
73
+ echo ""
74
+ echo "Cumulative Stats:"
75
+ node -e "
76
+ const fs = require('fs');
77
+ const lines = fs.readFileSync('$FEEDBACK_LOG','utf8').trim().split('\n').filter(Boolean);
78
+ const entries = lines.map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean);
79
+ const pos = entries.filter(e => e.signal === 'positive').length;
80
+ const neg = entries.filter(e => e.signal === 'negative').length;
81
+ const promoted = entries.filter(e => e.actionType === 'store-learning' || e.actionType === 'store-mistake').length;
82
+ console.log(' Total feedback :', entries.length);
83
+ console.log(' Positive (up) :', pos);
84
+ console.log(' Negative (down) :', neg);
85
+ console.log(' Promoted to mem :', promoted);
86
+ console.log(' Ratio :', pos > 0 ? (pos/(pos+neg)*100).toFixed(0) + '% positive' : 'n/a');
87
+ " 2>/dev/null
88
+ }
89
+
90
+ # Check for thumbs up signals
91
+ if echo "$LOWER" | grep -qE '(thumbs? ?up|that worked|looks good|nice work|perfect|good job)'; then
92
+ capture_and_report "up"
93
+ exit 0
94
+ fi
95
+
96
+ # Check for thumbs down signals
97
+ if echo "$LOWER" | grep -qE '(thumbs? ?down|that failed|that was wrong|fix this)'; then
98
+ capture_and_report "down"
99
+ exit 0
100
+ fi
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ # Stop hook — blocks "done/pushed/resolved" claims without PR thread evidence
3
+ # Inspects the assistant response for completion signals and requires proof
4
+ # that all PR review threads are resolved before allowing the claim.
5
+ #
6
+ # Environment variables (Stop hooks):
7
+ # CLAUDE_RESPONSE — the assistant's last response text
8
+ #
9
+ # Returns JSON with decision: "block" + message, or passes through silently.
10
+
11
+ set -euo pipefail
12
+
13
+ RESPONSE="${CLAUDE_RESPONSE:-}"
14
+
15
+ # If no response, nothing to check
16
+ if [ -z "$RESPONSE" ]; then
17
+ exit 0
18
+ fi
19
+
20
+ node -e '
21
+ "use strict";
22
+
23
+ const response = process.env.CLAUDE_RESPONSE || "";
24
+
25
+ // 1. Detect completion/done signals
26
+ const doneSignal = /\b(done|pushed|resolved|ready for review|all comments addressed|merged|ship it|completed|fixed and pushed)\b/i.test(response);
27
+ if (!doneSignal) {
28
+ // No completion claim — pass through silently
29
+ process.exit(0);
30
+ }
31
+
32
+ // 2. Check for PR thread evidence in the response
33
+ const threadEvidence =
34
+ /\b(0 unresolved|all threads resolved|no open threads|no unresolved|all comments resolved)\b/i.test(response) ||
35
+ /gh pr view.*review/i.test(response) ||
36
+ /reviewDecision.*APPROVED/i.test(response) ||
37
+ /comments.*:\s*0\b/i.test(response) ||
38
+ /unresolved.*:\s*0\b/i.test(response);
39
+
40
+ if (threadEvidence) {
41
+ // Evidence present — allow
42
+ process.exit(0);
43
+ }
44
+
45
+ // 3. Check if we are actually in a PR context (has a branch that is not main/master)
46
+ const { execSync } = require("child_process");
47
+ let branch = "";
48
+ try {
49
+ branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
50
+ } catch {
51
+ // Not in a git repo — skip check
52
+ process.exit(0);
53
+ }
54
+
55
+ if (branch === "main" || branch === "master") {
56
+ // Not on a feature branch — no PR to check
57
+ process.exit(0);
58
+ }
59
+
60
+ // 4. Block: completion claim on a feature branch without thread evidence
61
+ const output = {
62
+ decision: "block",
63
+ reason: "MANDATORY: Before declaring done, run `gh pr view --json reviewDecision,comments` and confirm 0 unresolved threads. Show the output in your response."
64
+ };
65
+ process.stdout.write(JSON.stringify(output));
66
+ ' 2>/dev/null || true
67
+
68
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # Claude Code / Amp Stop hook — autonomous self-scoring after every agent turn
3
+ # Fires after the agent completes a response. Runs selfAuditAndLog to produce
4
+ # a RLAIF self-score entry in self-score-log.jsonl.
5
+ #
6
+ # Environment variables available in Stop hooks:
7
+ # CLAUDE_STOP_REASON — why the agent stopped (e.g., "end_turn", "tool_use")
8
+ # CLAUDE_TOOL_OUTPUT — last tool output (if any)
9
+ #
10
+ # This hook is NON-BLOCKING — it exits 0 regardless of errors.
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ THUMBGATE_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
+
15
+ # Run the self-score via Node.js — sync, no API calls, ~5ms
16
+ node -e '
17
+ "use strict";
18
+ const path = require("path");
19
+
20
+ // Resolve modules relative to ThumbGate package root
21
+ const rlhfRoot = process.env.THUMBGATE_ROOT;
22
+ const { selfAuditAndLog } = require(path.join(rlhfRoot, "scripts", "rlaif-self-audit"));
23
+ const { getFeedbackPaths } = require(path.join(rlhfRoot, "scripts", "feedback-loop"));
24
+
25
+ const stopReason = process.env.CLAUDE_STOP_REASON || "unknown";
26
+
27
+ // Build a minimal feedback event for self-scoring
28
+ const feedbackEvent = {
29
+ id: `stop_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
30
+ signal: "positive",
31
+ context: `Agent turn completed (stop_reason: ${stopReason}). Autonomous self-score checkpoint.`,
32
+ tags: ["stop-hook", "auto-score"],
33
+ whatWorked: null,
34
+ whatWentWrong: null,
35
+ whatToChange: null,
36
+ rubric: null,
37
+ };
38
+
39
+ const paths = getFeedbackPaths();
40
+ const result = selfAuditAndLog(feedbackEvent, paths);
41
+
42
+ // Output minimal JSON for hook response (non-blocking)
43
+ process.stdout.write(JSON.stringify({
44
+ hookSpecificOutput: {
45
+ hookEventName: "Stop",
46
+ additionalContext: `Self-score: ${result.score} (${result.constraints.filter(c => c.passed).length}/${result.constraints.length} constraints passed)`
47
+ }
48
+ }));
49
+ ' 2>/dev/null || true
50
+
51
+ exit 0
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # Hook: Stop
3
+ #
4
+ # When it fires: After Claude finishes responding (every turn).
5
+ # What it does: Checks if a curl to production happened this session.
6
+ # If not, prints a warning reminding the CTO to verify.
7
+ # Why: Prevents saying "deployed" without proof.
8
+ # Env vars:
9
+ # CLAUDE_STOP_REASON — why the agent stopped (set by Claude Code)
10
+ # Marker file:
11
+ # /tmp/.thumbgate-last-deploy-verify — written by hook-verify-before-done.sh
12
+ # Exit code: Always 0 (informational only).
13
+
14
+ PROD_URL="thumbgate-production.up.railway.app"
15
+ VERIFICATION_MARKER="/tmp/.thumbgate-last-deploy-verify"
16
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
+ REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
18
+ EXPECTED_VERSION="$(node -e "console.log(require(process.argv[1]).version)" "${REPO_ROOT}/package.json")"
19
+
20
+ if [ -f "$VERIFICATION_MARKER" ]; then
21
+ VERIFY_TIME=$(cat "$VERIFICATION_MARKER")
22
+ echo "✅ Last deployment verification: $VERIFY_TIME"
23
+ else
24
+ echo "⚠️ WARNING: No deployment verification found this session."
25
+ echo " If you deployed to Railway, run:"
26
+ echo " curl -s https://${PROD_URL}/health | grep '\"version\":\"${EXPECTED_VERSION}\"'"
27
+ echo " curl -s https://${PROD_URL}/dashboard | grep 'ThumbGate Dashboard'"
28
+ echo " before claiming done."
29
+ fi
30
+
31
+ exit 0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PostToolUse hook: updates ThumbGate statusline cache after feedback_stats calls.
4
+ * Installed by: npx thumbgate init --agent claude-code
5
+ */
6
+ 'use strict';
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { resolveFeedbackDir } = require('./feedback-paths');
10
+
11
+ const CACHE_PATH = path.join(resolveFeedbackDir(), 'statusline_cache.json');
12
+
13
+ let input = '';
14
+ process.stdin.setEncoding('utf8');
15
+ process.stdin.on('data', chunk => { input += chunk; });
16
+ process.stdin.on('end', () => {
17
+ try {
18
+ const event = JSON.parse(input);
19
+ const tool = event.tool_name || '';
20
+ if (tool !== 'mcp__rlhf__feedback_stats' && tool !== 'mcp__rlhf__dashboard') return;
21
+
22
+ const raw = event.tool_response;
23
+ if (!raw) return;
24
+ const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
25
+
26
+ const cache = {};
27
+ if (tool === 'mcp__rlhf__feedback_stats') {
28
+ cache.thumbs_up = String(data.totalPositive || 0);
29
+ cache.thumbs_down = String(data.totalNegative || 0);
30
+ cache.lessons = String((data.rubric || {}).samples || 0);
31
+ cache.approval_rate = String(Math.round((data.approvalRate || 0) * 1000) / 10);
32
+ cache.trend = data.trend || '?';
33
+ cache.total_feedback = String(data.total || 0);
34
+ } else if (tool === 'mcp__rlhf__dashboard') {
35
+ const approval = data.approval || {};
36
+ cache.thumbs_up = String(approval.totalPositive || 0);
37
+ cache.thumbs_down = String(approval.totalNegative || 0);
38
+ cache.lessons = String((data.rubric || {}).samples || 0);
39
+ cache.approval_rate = String(approval.approvalRate || '?');
40
+ cache.trend = approval.trendDirection || '?';
41
+ cache.total_feedback = String(approval.total || 0);
42
+ }
43
+ cache.updated_at = String(Math.floor(Date.now() / 1000));
44
+
45
+ fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
46
+ fs.writeFileSync(CACHE_PATH, JSON.stringify(cache));
47
+ } catch (_) { /* silent — statusline cache is best-effort */ }
48
+ });
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Hook: PreToolUse (matcher: Bash)
3
+ #
4
+ # When it fires: Before every Bash tool call in Claude Code.
5
+ # What it does: If the Bash command contains a curl to production,
6
+ # records the timestamp to a marker file.
7
+ # Why: The Stop hook (hook-stop-verify-deploy.sh) checks this
8
+ # marker to warn if no prod verification happened.
9
+ # Env vars:
10
+ # CLAUDE_TOOL_INPUT — the Bash command about to execute (set by Claude Code)
11
+ # Exit code: Always 0 (never blocks tool calls).
12
+
13
+ PROD_URL="thumbgate-production.up.railway.app"
14
+ VERIFICATION_MARKER="/tmp/.thumbgate-last-deploy-verify"
15
+
16
+ if echo "${CLAUDE_TOOL_INPUT:-}" | grep -q "curl.*${PROD_URL}"; then
17
+ date -u +"%Y-%m-%dT%H:%M:%SZ" > "$VERIFICATION_MARKER"
18
+ fi
19
+
20
+ exit 0
@@ -0,0 +1,156 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const {
5
+ PRO_MONTHLY_PAYMENT_LINK,
6
+ PRO_MONTHLY_PRICE_DOLLARS,
7
+ PRO_PRICE_LABEL,
8
+ } = require('./commercial-offer');
9
+
10
+ const DEFAULT_PUBLIC_APP_ORIGIN = 'https://thumbgate-production.up.railway.app';
11
+ const DEFAULT_CHECKOUT_FALLBACK_URL = PRO_MONTHLY_PAYMENT_LINK;
12
+ const DEFAULT_PRO_PRICE_DOLLARS = PRO_MONTHLY_PRICE_DOLLARS;
13
+ const DEFAULT_PRO_PRICE_LABEL = PRO_PRICE_LABEL;
14
+ const GA_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]+$/i;
15
+
16
+ function normalizeOrigin(value) {
17
+ if (!value || typeof value !== 'string') {
18
+ return '';
19
+ }
20
+
21
+ const trimmed = value.trim();
22
+ if (!trimmed) {
23
+ return '';
24
+ }
25
+
26
+ try {
27
+ const parsed = new URL(trimmed);
28
+ if (!/^https?:$/.test(parsed.protocol)) {
29
+ return '';
30
+ }
31
+ parsed.pathname = parsed.pathname.replace(/\/+$/, '') || '/';
32
+ parsed.search = '';
33
+ parsed.hash = '';
34
+ const serialized = parsed.toString();
35
+ return serialized.endsWith('/') ? serialized.slice(0, -1) : serialized;
36
+ } catch {
37
+ return '';
38
+ }
39
+ }
40
+
41
+ function normalizeAbsoluteUrl(value) {
42
+ if (!value || typeof value !== 'string') {
43
+ return '';
44
+ }
45
+
46
+ const trimmed = value.trim();
47
+ if (!trimmed) {
48
+ return '';
49
+ }
50
+
51
+ try {
52
+ const parsed = new URL(trimmed);
53
+ if (!/^https?:$/.test(parsed.protocol)) {
54
+ return '';
55
+ }
56
+ return parsed.toString();
57
+ } catch {
58
+ return '';
59
+ }
60
+ }
61
+
62
+ function normalizeTrackingId(value, pattern) {
63
+ if (!value || typeof value !== 'string') {
64
+ return '';
65
+ }
66
+
67
+ const trimmed = value.trim();
68
+ if (!trimmed) {
69
+ return '';
70
+ }
71
+
72
+ if (pattern && !pattern.test(trimmed)) {
73
+ return '';
74
+ }
75
+
76
+ return trimmed;
77
+ }
78
+
79
+ function joinPublicUrl(baseOrigin, pathname) {
80
+ const normalized = normalizeOrigin(baseOrigin);
81
+ if (!normalized) {
82
+ throw new Error(`Invalid public origin: ${String(baseOrigin || '')}`);
83
+ }
84
+ const cleanPath = pathname.startsWith('/') ? pathname : `/${pathname}`;
85
+ return `${normalized}${cleanPath}`;
86
+ }
87
+
88
+ function createTraceId(prefix = 'trace') {
89
+ return `${prefix}_${Date.now().toString(36)}_${crypto.randomBytes(6).toString('hex')}`;
90
+ }
91
+
92
+ function normalizePriceDollars(value) {
93
+ if (value === undefined || value === null || value === '') {
94
+ return null;
95
+ }
96
+ const parsed = Number(value);
97
+ if (!Number.isFinite(parsed) || parsed <= 0) {
98
+ return null;
99
+ }
100
+ return Math.round(parsed);
101
+ }
102
+
103
+ function buildHostedSuccessUrl(appOrigin, traceId) {
104
+ const encodedTraceId = encodeURIComponent(String(traceId || ''));
105
+ const traceQuery = traceId ? `&trace_id=${encodedTraceId}` : '';
106
+ return `${joinPublicUrl(appOrigin, '/success')}?session_id={CHECKOUT_SESSION_ID}${traceQuery}`;
107
+ }
108
+
109
+ function buildHostedCancelUrl(appOrigin, traceId) {
110
+ const encodedTraceId = encodeURIComponent(String(traceId || ''));
111
+ const traceQuery = traceId ? `?trace_id=${encodedTraceId}` : '';
112
+ return `${joinPublicUrl(appOrigin, '/cancel')}${traceQuery}`;
113
+ }
114
+
115
+ function resolveHostedBillingConfig({ requestOrigin } = {}) {
116
+ const inferredOrigin = normalizeOrigin(requestOrigin) || DEFAULT_PUBLIC_APP_ORIGIN;
117
+ const appOrigin = normalizeOrigin(process.env.THUMBGATE_PUBLIC_APP_ORIGIN) || inferredOrigin;
118
+ const billingApiBaseUrl = normalizeOrigin(
119
+ process.env.THUMBGATE_BILLING_API_BASE_URL || process.env.THUMBGATE_CANONICAL_API_BASE_URL || appOrigin
120
+ ) || appOrigin;
121
+ const proPriceDollars = normalizePriceDollars(process.env.THUMBGATE_PRO_PRICE_DOLLARS) || DEFAULT_PRO_PRICE_DOLLARS;
122
+ const proPriceLabel = process.env.THUMBGATE_PRO_PRICE_LABEL || DEFAULT_PRO_PRICE_LABEL;
123
+ const gaMeasurementId = normalizeTrackingId(process.env.THUMBGATE_GA_MEASUREMENT_ID, GA_MEASUREMENT_ID_PATTERN);
124
+ const googleSiteVerification = normalizeTrackingId(process.env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
125
+
126
+ return {
127
+ appOrigin,
128
+ billingApiBaseUrl,
129
+ checkoutEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/checkout'),
130
+ sessionEndpoint: joinPublicUrl(billingApiBaseUrl, '/v1/billing/session'),
131
+ checkoutFallbackUrl: normalizeAbsoluteUrl(
132
+ process.env.THUMBGATE_CHECKOUT_FALLBACK_URL || DEFAULT_CHECKOUT_FALLBACK_URL
133
+ ) || DEFAULT_CHECKOUT_FALLBACK_URL,
134
+ proPriceDollars,
135
+ proPriceLabel,
136
+ gaMeasurementId,
137
+ googleSiteVerification,
138
+ };
139
+ }
140
+
141
+ module.exports = {
142
+ DEFAULT_PUBLIC_APP_ORIGIN,
143
+ DEFAULT_CHECKOUT_FALLBACK_URL,
144
+ DEFAULT_PRO_PRICE_DOLLARS,
145
+ DEFAULT_PRO_PRICE_LABEL,
146
+ GA_MEASUREMENT_ID_PATTERN,
147
+ normalizeAbsoluteUrl,
148
+ normalizeOrigin,
149
+ normalizeTrackingId,
150
+ normalizePriceDollars,
151
+ joinPublicUrl,
152
+ createTraceId,
153
+ buildHostedSuccessUrl,
154
+ buildHostedCancelUrl,
155
+ resolveHostedBillingConfig,
156
+ };