thumbgate 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.claude-plugin/README.md +45 -34
  2. package/.claude-plugin/marketplace.json +3 -3
  3. package/.claude-plugin/plugin.json +3 -3
  4. package/.well-known/llms.txt +1 -1
  5. package/.well-known/mcp/server-card.json +1 -1
  6. package/README.md +26 -2
  7. package/adapters/README.md +4 -1
  8. package/adapters/claude/.mcp.json +2 -2
  9. package/adapters/codex/config.toml +2 -2
  10. package/adapters/mcp/server-stdio.js +10 -4
  11. package/adapters/opencode/opencode.json +1 -1
  12. package/bin/cli.js +246 -90
  13. package/config/mcp-allowlists.json +11 -3
  14. package/package.json +184 -21
  15. package/scripts/audit-trail.js +25 -15
  16. package/scripts/auto-wire-hooks.js +127 -0
  17. package/scripts/cli-demo.js +102 -0
  18. package/scripts/cli-schema.js +285 -0
  19. package/scripts/cli-status.js +166 -0
  20. package/scripts/cross-encoder-reranker.js +235 -0
  21. package/scripts/explore-subcommands.js +277 -0
  22. package/scripts/explore.js +569 -0
  23. package/scripts/feedback-loop.js +20 -6
  24. package/scripts/lesson-inference.js +7 -1
  25. package/scripts/lesson-reranker.js +263 -0
  26. package/scripts/lesson-retrieval.js +34 -17
  27. package/scripts/lesson-search.js +69 -0
  28. package/scripts/perplexity-client.js +210 -0
  29. package/scripts/reflector-agent.js +2 -2
  30. package/scripts/statusline-local-stats.js +3 -1
  31. package/scripts/statusline.sh +12 -11
  32. package/src/api/server.js +178 -17
  33. package/src/index.js +3 -0
  34. package/.claude-plugin/bundle/icon.png +0 -0
  35. package/.claude-plugin/bundle/icon.svg +0 -18
  36. package/.claude-plugin/bundle/server/index.js +0 -24
  37. package/adapters/chatgpt/INSTALL.md +0 -138
  38. package/bin/memory.sh +0 -64
  39. package/bin/obsidian-sync.sh +0 -20
  40. package/plugins/amp-skill/INSTALL.md +0 -52
  41. package/plugins/amp-skill/SKILL.md +0 -64
  42. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
  43. package/plugins/claude-codex-bridge/.mcp.json +0 -14
  44. package/plugins/claude-codex-bridge/INSTALL.md +0 -43
  45. package/plugins/claude-codex-bridge/README.md +0 -46
  46. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
  47. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
  48. package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
  49. package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
  50. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
  51. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
  52. package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
  53. package/plugins/claude-skill/INSTALL.md +0 -55
  54. package/plugins/claude-skill/SKILL.md +0 -46
  55. package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
  56. package/plugins/codex-profile/.mcp.json +0 -14
  57. package/plugins/codex-profile/AGENTS.md +0 -20
  58. package/plugins/codex-profile/INSTALL.md +0 -89
  59. package/plugins/codex-profile/README.md +0 -61
  60. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
  61. package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
  62. package/plugins/cursor-marketplace/LICENSE +0 -21
  63. package/plugins/cursor-marketplace/README.md +0 -124
  64. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
  65. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  66. package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
  67. package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
  68. package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
  69. package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
  70. package/plugins/cursor-marketplace/mcp.json +0 -14
  71. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
  72. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
  73. package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
  74. package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
  75. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
  76. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
  77. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
  78. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
  79. package/plugins/gemini-extension/INSTALL.md +0 -92
  80. package/plugins/gemini-extension/gemini_prompt.txt +0 -14
  81. package/plugins/gemini-extension/tool_contract.json +0 -45
  82. package/plugins/opencode-profile/INSTALL.md +0 -57
  83. package/public/assets/instagram-card.png +0 -0
  84. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  85. package/public/blog.html +0 -474
  86. package/public/compare/mem0.html +0 -189
  87. package/public/compare/speclock.html +0 -180
  88. package/public/compare.html +0 -310
  89. package/public/dashboard.html +0 -1100
  90. package/public/guide.html +0 -317
  91. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  92. package/public/guides/codex-cli-guardrails.html +0 -158
  93. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  94. package/public/guides/pre-action-gates.html +0 -162
  95. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
  96. package/public/index.html +0 -1128
  97. package/public/js/buyer-intent.js +0 -252
  98. package/public/learn/agent-harness-pattern.html +0 -180
  99. package/public/learn/ai-agent-persistent-memory.html +0 -203
  100. package/public/learn/learn.css +0 -45
  101. package/public/learn/mcp-pre-action-gates-explained.html +0 -172
  102. package/public/learn/stop-ai-agent-force-push.html +0 -134
  103. package/public/learn/vibe-coding-safety-net.html +0 -142
  104. package/public/learn.html +0 -274
  105. package/public/lessons.html +0 -967
  106. package/public/llm-context.md +0 -140
  107. package/public/pro.html +0 -1087
  108. package/public/vercel.json +0 -8
  109. package/scripts/a2ui-engine.js +0 -73
  110. package/scripts/adk-consolidator.js +0 -274
  111. package/scripts/agent-security-hardening.js +0 -225
  112. package/scripts/ai-search-visibility.js +0 -142
  113. package/scripts/autonomous-sales-agent.js +0 -39
  114. package/scripts/autoresearch-runner.js +0 -216
  115. package/scripts/background-agent-governance.js +0 -229
  116. package/scripts/behavioral-extraction.js +0 -93
  117. package/scripts/budget-enforcer.js +0 -173
  118. package/scripts/budget-guard.js +0 -173
  119. package/scripts/build-claude-mcpb.js +0 -255
  120. package/scripts/build-codex-plugin.js +0 -152
  121. package/scripts/capture-railway-diagnostics.sh +0 -97
  122. package/scripts/changeset-check.js +0 -372
  123. package/scripts/check-congruence.js +0 -443
  124. package/scripts/computer-use-firewall.js +0 -280
  125. package/scripts/content-engine/linkedin-content-generator.js +0 -154
  126. package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
  127. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
  128. package/scripts/content-engine/reddit-thread-finder.js +0 -154
  129. package/scripts/context-engine.js +0 -710
  130. package/scripts/daily-digest.js +0 -11
  131. package/scripts/data-governance.js +0 -173
  132. package/scripts/deploy-gcp.sh +0 -44
  133. package/scripts/deploy-policy.js +0 -249
  134. package/scripts/disagreement-mining.js +0 -315
  135. package/scripts/dpo-optimizer.js +0 -206
  136. package/scripts/ensure-repo-bootstrap.js +0 -130
  137. package/scripts/ephemeral-agent-store.js +0 -212
  138. package/scripts/eval-harness.js +0 -56
  139. package/scripts/export-kto-pairs.js +0 -309
  140. package/scripts/export-training.js +0 -446
  141. package/scripts/feedback-fallback.js +0 -111
  142. package/scripts/feedback-inbox-read.js +0 -162
  143. package/scripts/feedback-root-consolidator.js +0 -233
  144. package/scripts/feedback-to-memory.js +0 -185
  145. package/scripts/gate-satisfy.js +0 -42
  146. package/scripts/generate-paperbanana-diagrams.sh +0 -99
  147. package/scripts/generate-pretool-hook.sh +0 -40
  148. package/scripts/github-about.js +0 -430
  149. package/scripts/github-outreach.js +0 -65
  150. package/scripts/gtm-revenue-loop.js +0 -535
  151. package/scripts/hallucination-detector.js +0 -226
  152. package/scripts/hf-papers.js +0 -317
  153. package/scripts/hook-auto-capture.sh +0 -100
  154. package/scripts/hook-stop-pr-thread-check.sh +0 -68
  155. package/scripts/hook-stop-self-score.sh +0 -51
  156. package/scripts/hook-stop-verify-deploy.sh +0 -31
  157. package/scripts/hook-verify-before-done.sh +0 -20
  158. package/scripts/managed-dpo-export.js +0 -91
  159. package/scripts/markdown-escape.js +0 -12
  160. package/scripts/marketing-experiment.js +0 -657
  161. package/scripts/memalign-recall.js +0 -111
  162. package/scripts/memory-migration.js +0 -296
  163. package/scripts/meta-policy.js +0 -190
  164. package/scripts/metered-billing.js +0 -16
  165. package/scripts/model-tier-router.js +0 -310
  166. package/scripts/money-watcher.js +0 -218
  167. package/scripts/multi-hop-recall.js +0 -240
  168. package/scripts/per-step-scoring.js +0 -163
  169. package/scripts/perplexity-marketing.js +0 -466
  170. package/scripts/pii-scanner.js +0 -153
  171. package/scripts/plan-gate.js +0 -154
  172. package/scripts/post-everywhere.js +0 -341
  173. package/scripts/post-to-x-retry.sh +0 -22
  174. package/scripts/post-to-x.js +0 -369
  175. package/scripts/pr-manager.js +0 -421
  176. package/scripts/principle-extractor.js +0 -162
  177. package/scripts/pro-features.js +0 -41
  178. package/scripts/prompt-dlp.js +0 -222
  179. package/scripts/prove-adapters.js +0 -860
  180. package/scripts/prove-attribution.js +0 -361
  181. package/scripts/prove-automation.js +0 -651
  182. package/scripts/prove-autoresearch.js +0 -304
  183. package/scripts/prove-claim-verification.js +0 -277
  184. package/scripts/prove-cloudflare-sandbox.js +0 -161
  185. package/scripts/prove-data-pipeline.js +0 -408
  186. package/scripts/prove-data-quality.js +0 -227
  187. package/scripts/prove-evolution.js +0 -352
  188. package/scripts/prove-harnesses.js +0 -287
  189. package/scripts/prove-intelligence.js +0 -257
  190. package/scripts/prove-lancedb.js +0 -425
  191. package/scripts/prove-local-intelligence.js +0 -340
  192. package/scripts/prove-loop-closure.js +0 -263
  193. package/scripts/prove-packaged-runtime.js +0 -326
  194. package/scripts/prove-predictive-insights.js +0 -355
  195. package/scripts/prove-runtime.js +0 -363
  196. package/scripts/prove-seo-gsd.js +0 -234
  197. package/scripts/prove-settings.js +0 -279
  198. package/scripts/prove-subway-upgrades.js +0 -277
  199. package/scripts/prove-tessl.js +0 -229
  200. package/scripts/prove-training-export.js +0 -325
  201. package/scripts/prove-workflow-contract.js +0 -112
  202. package/scripts/prove-xmemory.js +0 -332
  203. package/scripts/publish-decision.js +0 -159
  204. package/scripts/ralph-loop.js +0 -376
  205. package/scripts/ralph-mode-ci.js +0 -331
  206. package/scripts/reddit-dm-outreach.js +0 -192
  207. package/scripts/reddit-monitor-cron.sh +0 -26
  208. package/scripts/reminder-engine.js +0 -132
  209. package/scripts/revenue-status.js +0 -472
  210. package/scripts/rotate-stripe-webhook-secret.js +0 -314
  211. package/scripts/schedule-manager.js +0 -249
  212. package/scripts/self-healing-check.js +0 -193
  213. package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
  214. package/scripts/skill-exporter.js +0 -260
  215. package/scripts/skill-materializer.js +0 -134
  216. package/scripts/skill-packs.js +0 -136
  217. package/scripts/skill-proposer.js +0 -99
  218. package/scripts/skill-quality-tracker.js +0 -282
  219. package/scripts/slow-loop.js +0 -72
  220. package/scripts/social-analytics/db/analytics.sqlite +0 -0
  221. package/scripts/social-analytics/db/schema.sql +0 -32
  222. package/scripts/social-analytics/digest.js +0 -256
  223. package/scripts/social-analytics/engagement-audit.js +0 -185
  224. package/scripts/social-analytics/generate-instagram-card.js +0 -97
  225. package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
  226. package/scripts/social-analytics/install-growth-automation.js +0 -114
  227. package/scripts/social-analytics/load-env.js +0 -77
  228. package/scripts/social-analytics/mcp-server.js +0 -289
  229. package/scripts/social-analytics/normalizer.js +0 -580
  230. package/scripts/social-analytics/notify.js +0 -162
  231. package/scripts/social-analytics/poll-all.js +0 -107
  232. package/scripts/social-analytics/pollers/github.js +0 -195
  233. package/scripts/social-analytics/pollers/instagram.js +0 -253
  234. package/scripts/social-analytics/pollers/linkedin.js +0 -340
  235. package/scripts/social-analytics/pollers/plausible.js +0 -245
  236. package/scripts/social-analytics/pollers/reddit.js +0 -306
  237. package/scripts/social-analytics/pollers/threads.js +0 -233
  238. package/scripts/social-analytics/pollers/tiktok.js +0 -203
  239. package/scripts/social-analytics/pollers/x.js +0 -227
  240. package/scripts/social-analytics/pollers/youtube.js +0 -304
  241. package/scripts/social-analytics/pollers/zernio.js +0 -183
  242. package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
  243. package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
  244. package/scripts/social-analytics/publishers/devto.js +0 -122
  245. package/scripts/social-analytics/publishers/instagram.js +0 -317
  246. package/scripts/social-analytics/publishers/linkedin.js +0 -294
  247. package/scripts/social-analytics/publishers/reddit.js +0 -385
  248. package/scripts/social-analytics/publishers/threads.js +0 -275
  249. package/scripts/social-analytics/publishers/tiktok.js +0 -217
  250. package/scripts/social-analytics/publishers/x.js +0 -259
  251. package/scripts/social-analytics/publishers/youtube.js +0 -223
  252. package/scripts/social-analytics/publishers/zernio.js +0 -539
  253. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
  254. package/scripts/social-analytics/run-digest.js +0 -34
  255. package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
  256. package/scripts/social-analytics/store.js +0 -455
  257. package/scripts/social-analytics/sync-launch-assets.js +0 -185
  258. package/scripts/social-analytics/utm.js +0 -143
  259. package/scripts/social-pipeline.js +0 -2626
  260. package/scripts/social-post-hourly.js +0 -228
  261. package/scripts/social-quality-gate.js +0 -134
  262. package/scripts/social-reply-monitor.js +0 -592
  263. package/scripts/status-dashboard.js +0 -155
  264. package/scripts/stripe-live-status.js +0 -115
  265. package/scripts/subagent-profiles.js +0 -79
  266. package/scripts/sync-branch-protection.js +0 -340
  267. package/scripts/sync-gh-secrets-from-env.sh +0 -70
  268. package/scripts/sync-github-about.js +0 -55
  269. package/scripts/sync-version.js +0 -479
  270. package/scripts/synthetic-dpo.js +0 -234
  271. package/scripts/tessl-export.js +0 -369
  272. package/scripts/test-coverage.js +0 -128
  273. package/scripts/thumbgate_session_start.sh +0 -32
  274. package/scripts/train_from_feedback.py +0 -929
  275. package/scripts/validate-feedback.js +0 -581
  276. package/scripts/verify-obsidian-setup.sh +0 -269
  277. package/scripts/verify-run.js +0 -269
  278. package/scripts/weekly-auto-post.js +0 -124
  279. package/scripts/x-autonomous-marketing.js +0 -139
@@ -1,331 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Ralph Mode CI — runs in GitHub Actions with secrets injected.
6
- * Handles: X tweets, X mention replies, LinkedIn posts, GitHub issue monitoring,
7
- * GitHub repo search + outreach, dev.to publishing, ThumbGate stats.
8
- */
9
-
10
- const crypto = require('crypto');
11
- const https = require('https');
12
-
13
- // ── Env ─────────────────────────────────────────────────────────────────
14
- const X_API_KEY = process.env.X_API_KEY;
15
- const X_API_SECRET = process.env.X_API_SECRET;
16
- const X_ACCESS_TOKEN = process.env.X_ACCESS_TOKEN;
17
- const X_ACCESS_TOKEN_SECRET = process.env.X_ACCESS_TOKEN_SECRET;
18
- const X_BEARER_TOKEN = process.env.X_BEARER_TOKEN;
19
- const LINKEDIN_ACCESS_TOKEN = process.env.LINKEDIN_ACCESS_TOKEN;
20
- const LINKEDIN_PERSON_URN = process.env.LINKEDIN_PERSON_URN;
21
- const DEVTO_API_KEY = process.env.DEVTO_API_KEY;
22
- const GH_TOKEN = process.env.GH_TOKEN;
23
-
24
- // ── X OAuth 1.0a ───────────────────────────────────────────────────────
25
- function oauthSign(method, url, params, consumerSecret, tokenSecret) {
26
- const sp = Object.keys(params).sort().map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])).join('&');
27
- const bs = method + '&' + encodeURIComponent(url) + '&' + encodeURIComponent(sp);
28
- return crypto.createHmac('sha1', encodeURIComponent(consumerSecret) + '&' + encodeURIComponent(tokenSecret)).update(bs).digest('base64');
29
- }
30
-
31
- function xAuthHeader(method, url) {
32
- const p = {
33
- oauth_consumer_key: X_API_KEY,
34
- oauth_nonce: crypto.randomBytes(16).toString('hex'),
35
- oauth_signature_method: 'HMAC-SHA1',
36
- oauth_timestamp: Math.floor(Date.now() / 1000).toString(),
37
- oauth_token: X_ACCESS_TOKEN,
38
- oauth_version: '1.0',
39
- };
40
- p.oauth_signature = oauthSign(method, url, p, X_API_SECRET, X_ACCESS_TOKEN_SECRET);
41
- return 'OAuth ' + Object.keys(p).sort().map(k => encodeURIComponent(k) + '="' + encodeURIComponent(p[k]) + '"').join(', ');
42
- }
43
-
44
- // ── Helpers ─────────────────────────────────────────────────────────────
45
- async function postTweet(text) {
46
- const url = 'https://api.twitter.com/2/tweets';
47
- const r = await fetch(url, {
48
- method: 'POST',
49
- headers: { Authorization: xAuthHeader('POST', url), 'Content-Type': 'application/json' },
50
- body: JSON.stringify({ text }),
51
- });
52
- const j = await r.json();
53
- return { id: j.data?.id, error: j.detail };
54
- }
55
-
56
- async function replyTweet(text, replyTo) {
57
- const url = 'https://api.twitter.com/2/tweets';
58
- const r = await fetch(url, {
59
- method: 'POST',
60
- headers: { Authorization: xAuthHeader('POST', url), 'Content-Type': 'application/json' },
61
- body: JSON.stringify({ text, reply: { in_reply_to_tweet_id: replyTo } }),
62
- });
63
- const j = await r.json();
64
- return { id: j.data?.id, error: j.detail };
65
- }
66
-
67
- async function postLinkedIn(text) {
68
- const r = await fetch('https://api.linkedin.com/v2/ugcPosts', {
69
- method: 'POST',
70
- headers: {
71
- Authorization: 'Bearer ' + LINKEDIN_ACCESS_TOKEN,
72
- 'Content-Type': 'application/json',
73
- 'X-Restli-Protocol-Version': '2.0.0',
74
- },
75
- body: JSON.stringify({
76
- author: LINKEDIN_PERSON_URN,
77
- lifecycleState: 'PUBLISHED',
78
- specificContent: {
79
- 'com.linkedin.ugc.ShareContent': {
80
- shareCommentary: { text },
81
- shareMediaCategory: 'NONE',
82
- },
83
- },
84
- visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },
85
- }),
86
- });
87
- const j = await r.json();
88
- return { id: j.id, status: r.status };
89
- }
90
-
91
- async function ghApi(endpoint) {
92
- const r = await fetch('https://api.github.com' + endpoint, {
93
- headers: { Authorization: 'token ' + GH_TOKEN, Accept: 'application/vnd.github+json' },
94
- });
95
- return r.json();
96
- }
97
-
98
- async function ghPostComment(repo, issueNum, body) {
99
- const r = await fetch(`https://api.github.com/repos/${repo}/issues/${issueNum}/comments`, {
100
- method: 'POST',
101
- headers: {
102
- Authorization: 'token ' + GH_TOKEN,
103
- Accept: 'application/vnd.github+json',
104
- 'Content-Type': 'application/json',
105
- },
106
- body: JSON.stringify({ body }),
107
- });
108
- return r.json();
109
- }
110
-
111
- // ── State file (persisted via git) ──────────────────────────────────────
112
- const fs = require('fs');
113
- const STATE_PATH = '.thumbgate/ralph-state.json';
114
-
115
- function loadState() {
116
- try { return JSON.parse(fs.readFileSync(STATE_PATH, 'utf8')); } catch { return {}; }
117
- }
118
-
119
- function saveState(state) {
120
- fs.mkdirSync('.thumbgate', { recursive: true });
121
- fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2) + '\n');
122
- }
123
-
124
- // ── Tweet angles ────────────────────────────────────────────────────────
125
- const TWEET_ANGLES = [
126
- 'Your CLAUDE.md has 50 rules. Your agent ignores half.\n\nThumbGate turns each into a PreToolUse gate — a physical block before the tool call executes.\n\nnpx thumbgate quick-start\nhttps://github.com/IgorGanapolsky/ThumbGate',
127
- 'Self-distillation: your AI agent learns from its own mistakes.\n\n1. Agent runs tool call\n2. System checks outcome\n3. Failure → rule auto-generated\n4. Next session: gate blocks it\n\nZero human feedback needed.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
128
- 'Thompson Sampling for AI agent gates:\n\nEach gate: Beta(alpha, beta)\nCorrect block → alpha++ → tighter\nFalse positive → beta++ → relaxes\n\nNo thresholds. Gates converge on their own.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
129
- 'Google DeepMind: hidden prompt injections commandeer AI agents 86% of the time.\n\nThumbGate gates the action, not the prompt. PreToolUse hooks are the last defense.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
130
- 'Every AI agent framework ships memory. None ship enforcement.\n\nMemory: "Don\'t force-push to main"\nEnforcement: *physically blocked*\n\nThumbGate is the enforcement layer.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
131
- 'Founding Member: $49 once. ThumbGate Pro forever.\n\n50 spots. No subscription.\n\nSelf-distillation, SQL MCP gates, Thompson Sampling, context-stuffing, 68 tools on Smithery.\n\nhttps://buy.stripe.com/aFa4gz1M84r419v7mb3sI05',
132
- 'Context-stuffing: skip RAG entirely.\n\nDump ALL prevention rules into agent context at session start. 20-200 rules = 1K-10K tokens.\n\nInspired by Karpathy. Simpler. Faster.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
133
- 'The AI agent safety stack:\n\nGovernance: Paperclip\nOrchestration: iloom\nContext: RepoWise\nEnforcement: ThumbGate\n\nAll open source. All necessary.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
134
- ];
135
-
136
- // ── GitHub issues to monitor ────────────────────────────────────────────
137
- const WATCHED_ISSUES = [
138
- { repo: 'leogodin217/leos_claude_starter', num: 1 },
139
- { repo: 'RepoWise/backend', num: 34 },
140
- { repo: 'ScaleLeanChris/paperclip-ing', num: 1 },
141
- { repo: 'sd0xdev/sd0x-dev-flow', num: 5 },
142
- { repo: 'logi-cmd/agent-guardrails', num: 3 },
143
- ];
144
-
145
- // ── Main ────────────────────────────────────────────────────────────────
146
- async function main() {
147
- console.log('=== RALPH MODE CI — ' + new Date().toISOString() + ' ===\n');
148
- const state = loadState();
149
- const report = { tweets: 0, replies: 0, linkedin: 0, ghIssues: 0, ghOutreach: 0 };
150
-
151
- // ── 1. Check X mentions and reply ──
152
- if (X_BEARER_TOKEN && X_API_KEY) {
153
- try {
154
- const bearer = decodeURIComponent(X_BEARER_TOKEN);
155
- const mentionsRes = await fetch(
156
- 'https://api.twitter.com/2/tweets/search/recent?query=(@IgorGanapolsky OR thumbgate) -from:IgorGanapolsky&max_results=20&tweet.fields=author_id,text,created_at,id&expansions=author_id&user.fields=username',
157
- { headers: { Authorization: 'Bearer ' + bearer } }
158
- ).then(r => r.json());
159
-
160
- const mu = {};
161
- (mentionsRes.includes?.users || []).forEach(u => mu[u.id] = u);
162
- const lastChecked = state.lastMentionCheck || '2026-04-01T00:00:00Z';
163
- const newMentions = (mentionsRes.data || []).filter(
164
- t => new Date(t.created_at) > new Date(lastChecked) && (mu[t.author_id] || {}).username !== 'IgorGanapolsky'
165
- );
166
-
167
- console.log('X mentions since last check: ' + newMentions.length);
168
-
169
- for (const t of newMentions.slice(0, 5)) {
170
- const u = mu[t.author_id] || {};
171
- const replyText = '@' + u.username + ' ThumbGate: PreToolUse enforcement for AI agents. Thompson Sampling adapts confidence. 68 tools on Smithery.\n\nhttps://github.com/IgorGanapolsky/ThumbGate';
172
- const r = await replyTweet(replyText, t.id);
173
- console.log(' Replied to @' + u.username + ': ' + (r.id || r.error));
174
- report.replies++;
175
- }
176
-
177
- state.lastMentionCheck = new Date().toISOString();
178
- } catch (e) {
179
- console.log('X mentions error: ' + e.message);
180
- }
181
-
182
- // ── 2. Post new tweet ──
183
- try {
184
- const angleIndex = Math.floor(Date.now() / 7200000) % TWEET_ANGLES.length;
185
- const r = await postTweet(TWEET_ANGLES[angleIndex]);
186
- console.log('Tweet posted: ' + (r.id || r.error));
187
- report.tweets++;
188
- } catch (e) {
189
- console.log('Tweet error: ' + e.message);
190
- }
191
- } else {
192
- console.log('X: skipped (no API keys)');
193
- }
194
-
195
- // ── 3. LinkedIn post ──
196
- if (LINKEDIN_ACCESS_TOKEN && LINKEDIN_PERSON_URN) {
197
- try {
198
- const lastLinkedin = state.lastLinkedinPost || '2026-04-01T00:00:00Z';
199
- const hoursSince = (Date.now() - new Date(lastLinkedin).getTime()) / 3600000;
200
- if (hoursSince >= 4) {
201
- const angles = [
202
- 'ThumbGate: pre-action gates for AI coding agents. 68 tools on Smithery. Works with Claude Code, Cursor, Codex, Gemini, Amp.\n\nnpx thumbgate quick-start\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
203
- 'Every AI agent framework ships memory. None ship enforcement.\n\nThumbGate adds PreToolUse hooks that block bad actions before execution. Thompson Sampling adapts. Self-distillation auto-learns.\n\nhttps://github.com/IgorGanapolsky/ThumbGate',
204
- ];
205
- const r = await postLinkedIn(angles[Math.floor(Date.now() / 14400000) % angles.length]);
206
- console.log('LinkedIn posted: ' + (r.id || r.status));
207
- state.lastLinkedinPost = new Date().toISOString();
208
- report.linkedin++;
209
- } else {
210
- console.log('LinkedIn: skipped (' + Math.round(4 - hoursSince) + 'hr until next)');
211
- }
212
- } catch (e) {
213
- console.log('LinkedIn error: ' + e.message);
214
- }
215
- } else {
216
- console.log('LinkedIn: skipped (no token)');
217
- }
218
-
219
- // ── 4. GitHub issue monitoring ──
220
- if (GH_TOKEN) {
221
- const knownComments = state.issueComments || {};
222
-
223
- for (const { repo, num } of WATCHED_ISSUES) {
224
- try {
225
- const issue = await ghApi('/repos/' + repo + '/issues/' + num);
226
- const key = repo + '#' + num;
227
- const prev = knownComments[key] || 0;
228
-
229
- if (issue.comments > prev) {
230
- console.log(key + ': ' + (issue.comments - prev) + ' new comment(s)');
231
-
232
- // Read latest comment
233
- const comments = await ghApi('/repos/' + repo + '/issues/' + num + '/comments?per_page=1&page=' + issue.comments);
234
- const latest = comments[0];
235
- if (latest && latest.user.login !== 'IgorGanapolsky') {
236
- const reply = 'Thanks for the response! ThumbGate adds PreToolUse enforcement — gates that block known-bad actions before execution. Thompson Sampling adapts confidence. Self-distillation auto-generates rules from outcomes.\n\n68 tools on [Smithery](https://smithery.ai/servers/rlhf-loop/thumbgate). Would love to explore integration.\n\nhttps://github.com/IgorGanapolsky/ThumbGate';
237
- await ghPostComment(repo, num, reply);
238
- console.log(' Replied to @' + latest.user.login);
239
- report.ghIssues++;
240
- }
241
- } else {
242
- console.log(key + ': no new comments');
243
- }
244
-
245
- knownComments[key] = issue.comments;
246
- } catch (e) {
247
- console.log(repo + '#' + num + ' error: ' + e.message);
248
- }
249
- }
250
-
251
- // ── 5. Search for new repos ──
252
- try {
253
- const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10);
254
- const search = await ghApi('/search/repositories?q=agent+safety+OR+pretooluse+OR+claude+code+hooks+OR+mcp+gate+pushed:>' + weekAgo + '&sort=stars&order=desc&per_page=5');
255
- const contacted = new Set(state.contactedRepos || []);
256
- let opened = 0;
257
-
258
- for (const repo of (search.items || [])) {
259
- if (opened >= 2) break;
260
- if (repo.stargazers_count < 3) continue;
261
- if (contacted.has(repo.full_name)) continue;
262
- if (repo.full_name.includes('IgorGanapolsky')) continue;
263
-
264
- const body = 'Hey — noticed you\'re building in the AI agent safety space. [ThumbGate](https://github.com/IgorGanapolsky/ThumbGate) adds PreToolUse hooks that block known-bad actions before execution, with Thompson Sampling for adaptive gate confidence and self-distillation for auto-learning from outcomes.\n\n68 tools on [Smithery](https://smithery.ai/servers/rlhf-loop/thumbgate). Could be complementary — would love to explore integration.\n\nMIT licensed, free tier available.';
265
-
266
- try {
267
- await fetch('https://api.github.com/repos/' + repo.full_name + '/issues', {
268
- method: 'POST',
269
- headers: {
270
- Authorization: 'token ' + GH_TOKEN,
271
- Accept: 'application/vnd.github+json',
272
- 'Content-Type': 'application/json',
273
- },
274
- body: JSON.stringify({
275
- title: 'Integration: ThumbGate enforcement layer for ' + repo.name,
276
- body: body,
277
- }),
278
- });
279
- console.log('Opened issue on ' + repo.full_name + ' (stars=' + repo.stargazers_count + ')');
280
- contacted.add(repo.full_name);
281
- opened++;
282
- report.ghOutreach++;
283
- } catch (e) {
284
- console.log('Issue creation failed on ' + repo.full_name + ': ' + e.message);
285
- }
286
- }
287
-
288
- state.contactedRepos = [...contacted];
289
- } catch (e) {
290
- console.log('Repo search error: ' + e.message);
291
- }
292
-
293
- // ── 6. ThumbGate stats ──
294
- try {
295
- const repo = await ghApi('/repos/IgorGanapolsky/ThumbGate');
296
- console.log('ThumbGate: stars=' + repo.stargazers_count + ' forks=' + repo.forks_count);
297
- } catch (e) {
298
- console.log('Stats error: ' + e.message);
299
- }
300
-
301
- // ── 7. awesome-mcp PR ──
302
- try {
303
- const pr = await ghApi('/repos/punkpeye/awesome-mcp-servers/pulls/4474');
304
- console.log('awesome-mcp#4474: state=' + pr.state + ' merged=' + pr.merged);
305
- } catch (e) {
306
- console.log('PR check error: ' + e.message);
307
- }
308
-
309
- state.issueComments = knownComments;
310
- } else {
311
- console.log('GitHub: skipped (no token)');
312
- }
313
-
314
- // ── Save state ──
315
- state.lastRun = new Date().toISOString();
316
- saveState(state);
317
-
318
- // ── Report ──
319
- console.log('\n=== REPORT ===');
320
- console.log('Tweets: ' + report.tweets);
321
- console.log('Replies: ' + report.replies);
322
- console.log('LinkedIn: ' + report.linkedin);
323
- console.log('GitHub issues responded: ' + report.ghIssues);
324
- console.log('GitHub outreach opened: ' + report.ghOutreach);
325
- console.log('=== DONE ===');
326
- }
327
-
328
- main().catch(e => {
329
- console.error('Ralph Mode CI fatal error:', e.message);
330
- process.exit(1);
331
- });
@@ -1,192 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * reddit-dm-outreach.js
6
- * Send direct messages to engaged Reddit users via OAuth2 password grant flow.
7
- *
8
- * Usage:
9
- * node scripts/reddit-dm-outreach.js
10
- *
11
- * Requires env vars:
12
- * REDDIT_CLIENT_ID
13
- * REDDIT_CLIENT_SECRET
14
- * REDDIT_USERNAME
15
- * REDDIT_PASSWORD
16
- */
17
-
18
- const https = require('https');
19
- const fs = require('fs');
20
- const path = require('path');
21
-
22
- // Parse .env credentials line-by-line
23
- function parseEnv(filePath) {
24
- const content = fs.readFileSync(filePath, 'utf8');
25
- const result = {};
26
- const lines = content.split('\n');
27
-
28
- for (const line of lines) {
29
- const trimmed = line.trim();
30
- if (!trimmed || trimmed.startsWith('#')) continue;
31
-
32
- const eqIdx = trimmed.indexOf('=');
33
- if (eqIdx === -1) continue;
34
-
35
- const key = trimmed.substring(0, eqIdx).trim();
36
- const value = trimmed.substring(eqIdx + 1).trim();
37
- result[key] = value;
38
- }
39
-
40
- return result;
41
- }
42
-
43
- const envPath = path.join(__dirname, '..', '.env');
44
- const env = parseEnv(envPath);
45
-
46
- const REDDIT_CLIENT_ID = env.REDDIT_CLIENT_ID;
47
- const REDDIT_CLIENT_SECRET = env.REDDIT_CLIENT_SECRET;
48
- const REDDIT_USERNAME = env.REDDIT_USERNAME;
49
- const REDDIT_PASSWORD = env.REDDIT_PASSWORD;
50
-
51
- if (!REDDIT_CLIENT_ID || !REDDIT_CLIENT_SECRET || !REDDIT_USERNAME || !REDDIT_PASSWORD) {
52
- console.error('❌ Missing Reddit credentials in .env');
53
- process.exit(1);
54
- }
55
-
56
- console.log('📧 Reddit DM Outreach');
57
- console.log('---');
58
-
59
- // OAuth2 password grant flow
60
- function authenticate() {
61
- return new Promise((resolve, reject) => {
62
- const authHeader = 'Basic ' + Buffer.from(
63
- REDDIT_CLIENT_ID + ':' + REDDIT_CLIENT_SECRET
64
- ).toString('base64');
65
-
66
- const postData = [
67
- 'grant_type=password',
68
- `username=${encodeURIComponent(REDDIT_USERNAME)}`,
69
- `password=${encodeURIComponent(REDDIT_PASSWORD)}`,
70
- 'duration=permanent'
71
- ].join('&');
72
-
73
- const authOptions = {
74
- hostname: 'www.reddit.com',
75
- port: 443,
76
- path: '/api/v1/access_token',
77
- method: 'POST',
78
- headers: {
79
- 'Authorization': authHeader,
80
- 'Content-Type': 'application/x-www-form-urlencoded',
81
- 'User-Agent': 'ThumbGate-Testimonial/1.0',
82
- 'Content-Length': Buffer.byteLength(postData)
83
- }
84
- };
85
-
86
- const authReq = https.request(authOptions, (res) => {
87
- let data = '';
88
- res.on('data', chunk => { data += chunk; });
89
- res.on('end', () => {
90
- try {
91
- const parsed = JSON.parse(data);
92
- if (parsed.access_token) {
93
- console.log('✅ Authenticated as', REDDIT_USERNAME);
94
- resolve(parsed.access_token);
95
- } else {
96
- reject(new Error('Auth failed: ' + JSON.stringify(parsed)));
97
- }
98
- } catch (e) {
99
- reject(new Error('Parse error: ' + e.message));
100
- }
101
- });
102
- });
103
-
104
- authReq.on('error', reject);
105
- authReq.write(postData);
106
- authReq.end();
107
- });
108
- }
109
-
110
- // Send a single DM
111
- function sendDM(accessToken, to, subject, text) {
112
- return new Promise((resolve, reject) => {
113
- const dmData = JSON.stringify({ to, subject, text });
114
-
115
- const dmOptions = {
116
- hostname: 'oauth.reddit.com',
117
- port: 443,
118
- path: '/api/compose',
119
- method: 'POST',
120
- headers: {
121
- 'Authorization': 'Bearer ' + accessToken,
122
- 'Content-Type': 'application/json',
123
- 'User-Agent': 'ThumbGate-Testimonial/1.0',
124
- 'Content-Length': Buffer.byteLength(dmData)
125
- }
126
- };
127
-
128
- const dmReq = https.request(dmOptions, (res) => {
129
- let data = '';
130
- res.on('data', chunk => { data += chunk; });
131
- res.on('end', () => {
132
- if (res.statusCode === 200) {
133
- resolve();
134
- } else {
135
- reject(new Error(`HTTP ${res.statusCode}: ${data.substring(0, 200)}`));
136
- }
137
- });
138
- });
139
-
140
- dmReq.on('error', reject);
141
- dmReq.write(dmData);
142
- dmReq.end();
143
- });
144
- }
145
-
146
- // Main
147
- async function main() {
148
- try {
149
- const accessToken = await authenticate();
150
-
151
- const messages = [
152
- {
153
- to: 'game-of-kton',
154
- subject: 'Quick question about your agent workflow',
155
- text: 'Hey — you left some really thoughtful comments on the AI coding agent thread. I\'m building ThumbGate (agent mistake prevention via PreToolUse hooks) and your feedback was the most useful I got.\n\nI\'m trying to figure out if this is worth building further. Would you be open to a quick 15-min call this week? Just want to understand how you handle agent mistakes in your workflow.\n\nI\'ll give you lifetime Pro access regardless — no strings attached.\n\nHere\'s the repo if you want to look first: https://github.com/IgorGanapolsky/ThumbGate'
156
- },
157
- {
158
- to: 'Deep_Ad1959',
159
- subject: 'Your context-dependent blocking idea',
160
- text: 'Hey — your point about context-dependent blocking was really insightful. That\'s exactly the problem I\'m trying to solve with ThumbGate (using Thompson Sampling for adaptive gates instead of hard binary blocks).\n\nWould you be open to a quick 15-min call this week? I\'m trying to figure out what developers would actually pay for in this space. Your perspective would be genuinely valuable.\n\nLifetime Pro access is yours either way. Repo: https://github.com/IgorGanapolsky/ThumbGate'
161
- },
162
- {
163
- to: 'leogodin217',
164
- subject: 'Quick question about AI agent safety in your workflow',
165
- text: 'Hey — you engaged with one of my posts about AI coding agent tooling and your take stood out. I\'m building ThumbGate (prevents AI agents from repeating mistakes via automated prevention rules).\n\nI\'m at the stage where I need honest feedback from people who actually use agents daily. Would you do a quick 15-min call this week? Just want to understand your pain points.\n\nLifetime Pro access is yours regardless. Repo: https://github.com/IgorGanapolsky/ThumbGate'
166
- },
167
- {
168
- to: 'Enthu-Cutlet-1337',
169
- subject: 'Quick question about your AI coding agent setup',
170
- text: 'Hey — you commented on one of my posts about agent memory/safety tooling and your feedback was one of the few that was genuinely useful.\n\nI\'m building ThumbGate (automated mistake prevention for AI coding agents) and I\'m trying to figure out if this solves a real problem or if everyone just uses CLAUDE.md files. Would you be open to a quick 15-min call this week?\n\nLifetime Pro access is yours either way — no pitch, just questions. Repo: https://github.com/IgorGanapolsky/ThumbGate'
171
- }
172
- ];
173
-
174
- console.log(`\n📨 Sending ${messages.length} direct messages...\n`);
175
-
176
- for (const msg of messages) {
177
- try {
178
- await sendDM(accessToken, msg.to, msg.subject, msg.text);
179
- console.log(`✅ DM sent to u/${msg.to}`);
180
- } catch (err) {
181
- console.error(`❌ Failed to send DM to u/${msg.to}:`, err.message);
182
- }
183
- }
184
-
185
- console.log(`\n✅ Outreach complete (${messages.length}/${messages.length} sent)`);
186
- } catch (err) {
187
- console.error('❌ Error:', err.message);
188
- process.exit(1);
189
- }
190
- }
191
-
192
- main();
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- REPO_DIR="/Users/igorganapolsky/workspace/git/igor/thumbgate"
5
- LOG_FILE="${REPO_DIR}/.thumbgate/reddit-monitor.log"
6
-
7
- mkdir -p "${REPO_DIR}/.thumbgate"
8
-
9
- # Load environment
10
- if [ -f "${REPO_DIR}/.env" ]; then
11
- while IFS='=' read -r key value; do
12
- # Skip comments and empty lines
13
- [[ -z "$key" || "$key" =~ ^# ]] && continue
14
- # Strip leading/trailing whitespace from key
15
- key=$(echo "$key" | xargs)
16
- # Export the variable (value preserved as-is)
17
- export "$key=$value"
18
- done < "${REPO_DIR}/.env"
19
- fi
20
-
21
- echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run starting" >> "$LOG_FILE"
22
-
23
- cd "$REPO_DIR"
24
- /opt/homebrew/bin/node scripts/social-reply-monitor.js >> "$LOG_FILE" 2>&1
25
-
26
- echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] Reddit monitor run complete" >> "$LOG_FILE"
@@ -1,132 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const PROJECT_ROOT = path.join(__dirname, '..');
7
- const DEFAULT_STATE_PATH = path.join(PROJECT_ROOT, '.thumbgate', 'reminder-state.json');
8
-
9
- const REMINDER_TEMPLATES = {
10
- guardrail_spike: 'Safety guardrails triggered {{count}} times. Re-apply rule: {{rule}}',
11
- iteration_limit: 'Approaching max iterations ({{count}}/{{limit}}). Prioritize essential actions only.',
12
- tool_misuse: 'Tool misuse detected {{count}} times for: {{tools}}. Verify tool schemas before calling.',
13
- error_cascade: 'Repeated errors ({{count}}). Switch strategy: {{suggestion}}',
14
- };
15
-
16
- const DEFAULT_THRESHOLDS = {
17
- guardrail_spike: 3,
18
- iteration_limit: 1,
19
- tool_misuse: 2,
20
- error_cascade: 3,
21
- };
22
-
23
- function getStatePath(stateFile) {
24
- return stateFile || DEFAULT_STATE_PATH;
25
- }
26
-
27
- function loadState(stateFile) {
28
- const p = getStatePath(stateFile);
29
- try {
30
- if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf-8'));
31
- } catch {
32
- // corrupted — start fresh
33
- }
34
- return { counts: {} };
35
- }
36
-
37
- function saveState(state, stateFile) {
38
- const p = getStatePath(stateFile);
39
- fs.mkdirSync(path.dirname(p), { recursive: true });
40
- fs.writeFileSync(p, JSON.stringify(state, null, 2));
41
- }
42
-
43
- /**
44
- * Increment the event counter for a given event type.
45
- * @param {string} eventType - One of the keys in REMINDER_TEMPLATES
46
- * @param {string} [stateFile] - Path to state JSON (default: .thumbgate/reminder-state.json)
47
- * @returns {number} New count after incrementing
48
- */
49
- function trackEvent(eventType, stateFile) {
50
- const state = loadState(stateFile);
51
- state.counts[eventType] = (state.counts[eventType] || 0) + 1;
52
- saveState(state, stateFile);
53
- return state.counts[eventType];
54
- }
55
-
56
- /**
57
- * Get the current event count without modifying state.
58
- * @param {string} eventType
59
- * @param {string} [stateFile]
60
- * @returns {number}
61
- */
62
- function getEventCount(eventType, stateFile) {
63
- return loadState(stateFile).counts[eventType] || 0;
64
- }
65
-
66
- /**
67
- * Return true if the event count meets or exceeds its threshold.
68
- * @param {string} eventType
69
- * @param {number} [threshold] - Defaults to DEFAULT_THRESHOLDS[eventType] or 3
70
- * @param {string} [stateFile]
71
- * @returns {boolean}
72
- */
73
- function shouldInjectReminder(eventType, threshold, stateFile) {
74
- const t = typeof threshold === 'number' ? threshold : (DEFAULT_THRESHOLDS[eventType] || 3);
75
- return getEventCount(eventType, stateFile) >= t;
76
- }
77
-
78
- /**
79
- * Render a reminder template with context variable substitution.
80
- * @param {string} eventType
81
- * @param {object} ctx - Variables to substitute into {{var}} placeholders
82
- * @returns {string}
83
- */
84
- function renderTemplate(eventType, ctx) {
85
- const template = REMINDER_TEMPLATES[eventType];
86
- if (!template) return `[Reminder] Event: ${eventType}`;
87
- return template.replace(/\{\{(\w+)\}\}/g, (_, key) => (ctx && ctx[key] !== undefined ? ctx[key] : `{${key}}`));
88
- }
89
-
90
- /**
91
- * Append a system reminder to a turns array without modifying state.
92
- * Callers are responsible for calling trackEvent before/after as needed.
93
- * @param {object[]} turns - Existing turns array
94
- * @param {string} eventType
95
- * @param {object} ctx - Template variables (count will be added automatically)
96
- * @param {string} [stateFile]
97
- * @returns {object[]} New turns array with reminder appended
98
- */
99
- function injectReminder(turns, eventType, ctx, stateFile) {
100
- const count = getEventCount(eventType, stateFile);
101
- const message = renderTemplate(eventType, { ...ctx, count });
102
- const reminder = {
103
- role: 'user',
104
- content: `[System Reminder] ${message}`,
105
- injectedAt: new Date().toISOString(),
106
- eventType,
107
- };
108
- return [...turns, reminder];
109
- }
110
-
111
- /**
112
- * Reset the event counter for a given event type (e.g., after a reminder is acted on).
113
- * @param {string} eventType
114
- * @param {string} [stateFile]
115
- */
116
- function resetEvent(eventType, stateFile) {
117
- const state = loadState(stateFile);
118
- state.counts[eventType] = 0;
119
- saveState(state, stateFile);
120
- }
121
-
122
- module.exports = {
123
- REMINDER_TEMPLATES,
124
- DEFAULT_THRESHOLDS,
125
- DEFAULT_STATE_PATH,
126
- trackEvent,
127
- getEventCount,
128
- shouldInjectReminder,
129
- renderTemplate,
130
- injectReminder,
131
- resetEvent,
132
- };