thumbgate 1.3.0 → 1.4.0

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 (146) hide show
  1. package/.claude-plugin/marketplace.json +32 -13
  2. package/.claude-plugin/plugin.json +15 -2
  3. package/.well-known/llms.txt +60 -0
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +109 -20
  6. package/adapters/README.md +1 -1
  7. package/adapters/chatgpt/openapi.yaml +168 -0
  8. package/adapters/claude/.mcp.json +2 -2
  9. package/adapters/codex/config.toml +2 -2
  10. package/adapters/mcp/server-stdio.js +84 -1
  11. package/adapters/opencode/opencode.json +1 -1
  12. package/bin/cli.js +200 -13
  13. package/bin/postinstall.js +8 -2
  14. package/config/budget.json +18 -0
  15. package/config/gates/code-edit.json +61 -0
  16. package/config/gates/db-write.json +61 -0
  17. package/config/gates/default.json +154 -3
  18. package/config/gates/deploy.json +61 -0
  19. package/config/github-about.json +2 -1
  20. package/config/merge-quality-checks.json +23 -0
  21. package/openapi/openapi.yaml +168 -0
  22. package/package.json +42 -10
  23. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  24. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  25. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
  26. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  27. package/plugins/codex-profile/.mcp.json +1 -1
  28. package/plugins/codex-profile/INSTALL.md +27 -4
  29. package/plugins/codex-profile/README.md +33 -9
  30. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  31. package/plugins/opencode-profile/INSTALL.md +1 -1
  32. package/public/blog.html +73 -0
  33. package/public/compare/mem0.html +189 -0
  34. package/public/compare/speclock.html +180 -0
  35. package/public/compare.html +10 -2
  36. package/public/guide.html +2 -2
  37. package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
  38. package/public/guides/codex-cli-guardrails.html +158 -0
  39. package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
  40. package/public/guides/pre-action-gates.html +162 -0
  41. package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
  42. package/public/index.html +136 -50
  43. package/public/lessons.html +33 -24
  44. package/public/llm-context.md +140 -0
  45. package/public/pro.html +24 -22
  46. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  47. package/scripts/access-anomaly-detector.js +1 -1
  48. package/scripts/adk-consolidator.js +1 -5
  49. package/scripts/agent-security-hardening.js +4 -6
  50. package/scripts/agentic-data-pipeline.js +1 -3
  51. package/scripts/async-job-runner.js +1 -5
  52. package/scripts/audit-trail.js +1 -5
  53. package/scripts/background-agent-governance.js +2 -10
  54. package/scripts/billing.js +2 -16
  55. package/scripts/budget-enforcer.js +173 -0
  56. package/scripts/build-codex-plugin.js +152 -0
  57. package/scripts/check-congruence.js +132 -14
  58. package/scripts/commercial-offer.js +5 -7
  59. package/scripts/content-engine/linkedin-content-generator.js +154 -0
  60. package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
  61. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
  62. package/scripts/content-engine/reddit-thread-finder.js +154 -0
  63. package/scripts/context-engine.js +21 -6
  64. package/scripts/contextfs.js +1 -21
  65. package/scripts/dashboard.js +20 -0
  66. package/scripts/decision-journal.js +341 -0
  67. package/scripts/delegation-runtime.js +1 -5
  68. package/scripts/distribution-surfaces.js +26 -0
  69. package/scripts/document-intake.js +927 -0
  70. package/scripts/ephemeral-agent-store.js +1 -8
  71. package/scripts/evolution-state.js +1 -5
  72. package/scripts/experiment-tracker.js +1 -5
  73. package/scripts/export-databricks-bundle.js +1 -5
  74. package/scripts/export-hf-dataset.js +1 -5
  75. package/scripts/export-training.js +1 -5
  76. package/scripts/feedback-attribution.js +1 -16
  77. package/scripts/feedback-history-distiller.js +1 -16
  78. package/scripts/feedback-loop.js +1 -5
  79. package/scripts/feedback-root-consolidator.js +2 -21
  80. package/scripts/feedback-session.js +49 -0
  81. package/scripts/feedback-to-rules.js +188 -28
  82. package/scripts/filesystem-search.js +1 -9
  83. package/scripts/fs-utils.js +104 -0
  84. package/scripts/gates-engine.js +149 -4
  85. package/scripts/github-about.js +32 -8
  86. package/scripts/gtm-revenue-loop.js +1 -5
  87. package/scripts/harness-selector.js +148 -0
  88. package/scripts/hosted-job-launcher.js +1 -5
  89. package/scripts/hybrid-feedback-context.js +7 -33
  90. package/scripts/intervention-policy.js +58 -1
  91. package/scripts/lesson-db.js +3 -18
  92. package/scripts/lesson-inference.js +194 -16
  93. package/scripts/lesson-retrieval.js +60 -24
  94. package/scripts/llm-client.js +59 -0
  95. package/scripts/managed-lesson-agent.js +183 -0
  96. package/scripts/marketing-experiment.js +8 -22
  97. package/scripts/meta-agent-loop.js +624 -0
  98. package/scripts/metered-billing.js +1 -1
  99. package/scripts/money-watcher.js +1 -4
  100. package/scripts/obsidian-export.js +1 -5
  101. package/scripts/operational-integrity.js +15 -3
  102. package/scripts/org-dashboard.js +6 -1
  103. package/scripts/per-step-scoring.js +2 -4
  104. package/scripts/pr-manager.js +201 -19
  105. package/scripts/pro-features.js +3 -2
  106. package/scripts/prompt-dlp.js +3 -3
  107. package/scripts/prove-adapters.js +1 -5
  108. package/scripts/prove-attribution.js +1 -5
  109. package/scripts/prove-automation.js +1 -3
  110. package/scripts/prove-cloudflare-sandbox.js +1 -3
  111. package/scripts/prove-data-pipeline.js +1 -3
  112. package/scripts/prove-intelligence.js +1 -3
  113. package/scripts/prove-lancedb.js +1 -5
  114. package/scripts/prove-local-intelligence.js +1 -3
  115. package/scripts/prove-packaged-runtime.js +75 -9
  116. package/scripts/prove-predictive-insights.js +1 -3
  117. package/scripts/prove-training-export.js +1 -3
  118. package/scripts/prove-workflow-contract.js +1 -5
  119. package/scripts/rate-limiter.js +3 -1
  120. package/scripts/reddit-dm-outreach.js +14 -4
  121. package/scripts/schedule-manager.js +3 -5
  122. package/scripts/security-scanner.js +448 -0
  123. package/scripts/self-distill-agent.js +579 -0
  124. package/scripts/semantic-dedup.js +115 -0
  125. package/scripts/skill-exporter.js +1 -3
  126. package/scripts/skill-generator.js +1 -5
  127. package/scripts/social-analytics/engagement-audit.js +1 -18
  128. package/scripts/social-analytics/pollers/linkedin.js +26 -16
  129. package/scripts/social-analytics/publishers/linkedin.js +1 -1
  130. package/scripts/social-analytics/publishers/zernio.js +51 -0
  131. package/scripts/social-pipeline.js +1 -3
  132. package/scripts/social-post-hourly.js +47 -4
  133. package/scripts/statusline-links.js +6 -5
  134. package/scripts/statusline.sh +29 -153
  135. package/scripts/sync-branch-protection.js +340 -0
  136. package/scripts/tessl-export.js +1 -3
  137. package/scripts/thumbgate-search.js +32 -1
  138. package/scripts/tool-kpi-tracker.js +1 -1
  139. package/scripts/tool-registry.js +106 -2
  140. package/scripts/vector-store.js +1 -5
  141. package/scripts/weekly-auto-post.js +1 -1
  142. package/scripts/workflow-sentinel.js +91 -0
  143. package/skills/thumbgate/SKILL.md +1 -1
  144. package/src/api/server.js +273 -4
  145. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  146. /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
@@ -10,6 +10,9 @@ const { execFileSync } = require('child_process');
10
10
 
11
11
  const ROOT = path.join(__dirname, '..');
12
12
  const DEFAULT_TIMEOUT_MS = 15000;
13
+ const DEFAULT_PUBLISH_INSTALL_RETRIES = 6;
14
+ const DEFAULT_PUBLISH_INSTALL_DELAY_MS = 5000;
15
+ const MAX_PUBLISH_INSTALL_DELAY_MS = 30000;
13
16
  const STATUSLINE_INPUT = JSON.stringify({ context_window: { used_percentage: 12 } });
14
17
 
15
18
  function parseArgs(argv = process.argv.slice(2)) {
@@ -55,6 +58,53 @@ function installPackage(prefixDir, packageSpec) {
55
58
  return path.join(prefixDir, 'node_modules', '.bin', 'thumbgate');
56
59
  }
57
60
 
61
+ function isRemotePackageSpec(packageSpec) {
62
+ if (!packageSpec) return false;
63
+ return !/^(?:\.{0,2}\/|\/|file:)/.test(packageSpec) && !packageSpec.endsWith('.tgz');
64
+ }
65
+
66
+ function isTransientRegistryMiss(error) {
67
+ const text = [
68
+ error && error.message,
69
+ error && error.stdout,
70
+ error && error.stderr,
71
+ ]
72
+ .filter(Boolean)
73
+ .join('\n');
74
+ return /ETARGET|No matching version found|npm error code E404|404 Not Found/i.test(text);
75
+ }
76
+
77
+ async function installPackageWithRetry(prefixDir, packageSpec, options = {}) {
78
+ const installImpl = options.installImpl || installPackage;
79
+ const sleepImpl = options.sleepImpl || sleep;
80
+ const remotePackage = options.remotePackage !== undefined ? options.remotePackage : isRemotePackageSpec(packageSpec);
81
+ const attempts = remotePackage ? Number(options.attempts || DEFAULT_PUBLISH_INSTALL_RETRIES) : 1;
82
+ let delayMs = Number(options.delayMs || DEFAULT_PUBLISH_INSTALL_DELAY_MS);
83
+ let lastError = null;
84
+
85
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
86
+ if (attempt > 1) {
87
+ fs.rmSync(prefixDir, { recursive: true, force: true });
88
+ }
89
+ try {
90
+ return installImpl(prefixDir, packageSpec);
91
+ } catch (error) {
92
+ lastError = error;
93
+ const retryable = remotePackage && isTransientRegistryMiss(error) && attempt < attempts;
94
+ if (!retryable) {
95
+ throw error;
96
+ }
97
+ process.stderr.write(
98
+ `Retrying published package install for ${packageSpec} after transient registry miss (${attempt}/${attempts - 1})\n`
99
+ );
100
+ await sleepImpl(delayMs);
101
+ delayMs = Math.min(Math.round(delayMs * 1.5), MAX_PUBLISH_INSTALL_DELAY_MS);
102
+ }
103
+ }
104
+
105
+ throw lastError || new Error(`Failed to install package ${packageSpec}`);
106
+ }
107
+
58
108
  function request(url, timeoutMs = 2000) {
59
109
  return new Promise((resolve, reject) => {
60
110
  const req = http.get(url, (res) => {
@@ -175,7 +225,10 @@ async function runPackagedRuntimeSmoke(options = {}) {
175
225
 
176
226
  try {
177
227
  const packageSpec = options.packageSpec || packCurrentRepo(packDir);
178
- const runtimeBin = installPackage(runtimeDir, packageSpec);
228
+ const runtimeBin = await installPackageWithRetry(runtimeDir, packageSpec, {
229
+ attempts: options.installAttempts,
230
+ delayMs: options.installDelayMs,
231
+ });
179
232
  if (!fs.existsSync(runtimeBin)) {
180
233
  throw new Error(`Installed runtime binary is missing: ${runtimeBin}`);
181
234
  }
@@ -210,17 +263,25 @@ async function runPackagedRuntimeSmoke(options = {}) {
210
263
  }
211
264
 
212
265
  const readyStatusline = renderStatusline(runtimeBin, projectDir, env);
213
- if (!readyStatusline.includes(`${origin}/dashboard`)) {
214
- throw new Error(`Ready statusline missing dashboard URL: ${readyStatusline.trim()}`);
266
+ if (!/(Dashboard|Dashboard…)/.test(readyStatusline)) {
267
+ throw new Error(`Ready statusline missing dashboard label: ${readyStatusline.trim()}`);
268
+ }
269
+ if (!/(Lessons|Lessons…)/.test(readyStatusline)) {
270
+ throw new Error(`Ready statusline missing lessons label: ${readyStatusline.trim()}`);
271
+ }
272
+ if (readyStatusline.includes(`${origin}/dashboard`)) {
273
+ throw new Error(`Ready statusline leaked dashboard URL: ${readyStatusline.trim()}`);
215
274
  }
216
- if (!readyStatusline.includes(`${origin}/lessons`)) {
217
- throw new Error(`Ready statusline missing lessons URL: ${readyStatusline.trim()}`);
275
+ if (readyStatusline.includes(`${origin}/lessons`)) {
276
+ throw new Error(`Ready statusline leaked lessons URL: ${readyStatusline.trim()}`);
218
277
  }
219
- if (!readyStatusline.includes(`${origin}/feedback/quick?signal=up`)) {
220
- throw new Error(`Ready statusline missing thumbs-up URL: ${readyStatusline.trim()}`);
278
+ // Thumbs-up/down icons stay inline while dashboard + lessons remain compact
279
+ // labels, even after the local API is up.
280
+ if (!readyStatusline.includes('👍')) {
281
+ throw new Error(`Ready statusline missing thumbs-up icon: ${readyStatusline.trim()}`);
221
282
  }
222
- if (!readyStatusline.includes(`${origin}/feedback/quick?signal=down`)) {
223
- throw new Error(`Ready statusline missing thumbs-down URL: ${readyStatusline.trim()}`);
283
+ if (!readyStatusline.includes('👎')) {
284
+ throw new Error(`Ready statusline missing thumbs-down icon: ${readyStatusline.trim()}`);
224
285
  }
225
286
 
226
287
  return {
@@ -241,6 +302,8 @@ async function main() {
241
302
  packageSpec: args.packageSpec,
242
303
  expectedVersion: args.expectedVersion,
243
304
  timeoutMs: args.timeoutMs,
305
+ installAttempts: args.installAttempts,
306
+ installDelayMs: args.installDelayMs,
244
307
  });
245
308
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
246
309
  }
@@ -254,6 +317,9 @@ if (require.main === module) {
254
317
 
255
318
  module.exports = {
256
319
  getAvailablePort,
320
+ installPackageWithRetry,
321
+ isRemotePackageSpec,
322
+ isTransientRegistryMiss,
257
323
  packCurrentRepo,
258
324
  runPackagedRuntimeSmoke,
259
325
  waitForHealthy,
@@ -4,6 +4,7 @@
4
4
  const fs = require('node:fs');
5
5
  const os = require('node:os');
6
6
  const path = require('node:path');
7
+ const { ensureDir } = require('./fs-utils');
7
8
 
8
9
  const ROOT = path.join(__dirname, '..');
9
10
 
@@ -16,9 +17,6 @@ function resolveProofPaths() {
16
17
  };
17
18
  }
18
19
 
19
- function ensureDir(dirPath) {
20
- fs.mkdirSync(dirPath, { recursive: true });
21
- }
22
20
 
23
21
  function buildStubTelemetry() {
24
22
  return {
@@ -13,15 +13,13 @@ const fs = require('fs');
13
13
  const os = require('os');
14
14
  const path = require('path');
15
15
  const { execSync } = require('child_process');
16
+ const { ensureDir } = require('./fs-utils');
16
17
 
17
18
  const ROOT = path.join(__dirname, '..');
18
19
  function getProofDir() {
19
20
  return process.env.THUMBGATE_PROOF_DIR || path.join(ROOT, 'proof');
20
21
  }
21
22
 
22
- function ensureDir(d) {
23
- if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
24
- }
25
23
 
26
24
  function runTests() {
27
25
  try {
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const fs = require('node:fs');
3
3
  const path = require('node:path');
4
+ const { ensureDir } = require('./fs-utils');
4
5
  const {
5
6
  runWorkflowContractValidation,
6
7
  } = require('./validate-workflow-contract');
@@ -8,11 +9,6 @@ const {
8
9
  const PROJECT_ROOT = path.join(__dirname, '..');
9
10
  const DEFAULT_PROOF_DIR = path.join(PROJECT_ROOT, 'proof', 'workflow-contract');
10
11
 
11
- function ensureDir(dirPath) {
12
- if (!fs.existsSync(dirPath)) {
13
- fs.mkdirSync(dirPath, { recursive: true });
14
- }
15
- }
16
12
 
17
13
  function toMarkdown(report) {
18
14
  const lines = [
@@ -5,6 +5,8 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const {
7
7
  PRO_MONTHLY_PAYMENT_LINK,
8
+ PRO_PRICE_LABEL,
9
+ TEAM_PRICE_LABEL,
8
10
  } = require('./commercial-offer');
9
11
 
10
12
  const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
@@ -20,7 +22,7 @@ const FREE_TIER_LIMITS = {
20
22
 
21
23
  const FREE_TIER_MAX_GATES = 5;
22
24
 
23
- const UPGRADE_MESSAGE = `Upgrade to Pro ($19/mo) for a personal local dashboard, DPO export, and optional hosted API key: ${PRO_MONTHLY_PAYMENT_LINK}`;
25
+ const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} dashboard and DPO export: ${PRO_MONTHLY_PAYMENT_LINK}\n Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
24
26
 
25
27
  function isProTier(authContext) {
26
28
  if (authContext && authContext.tier === 'pro') return true;
@@ -151,13 +151,23 @@ async function main() {
151
151
  const messages = [
152
152
  {
153
153
  to: 'game-of-kton',
154
- subject: 'ThumbGate Pro Try it free, get the hook',
155
- text: 'Hey, thanks for the thoughtful comments on the Cursor thread about agent memory. You clearly get the hooks-based enforcement approach. Would you be open to trying ThumbGate Pro for free? I\'d love to get your honest take if you like it, a one-sentence quote I can use on the landing page would be huge. No strings attached either way. Here\'s the repo: https://github.com/IgorGanapolsky/ThumbGate'
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
156
  },
157
157
  {
158
158
  to: 'Deep_Ad1959',
159
- subject: 'ThumbGate Pro — Context-dependent blocking (your idea)',
160
- text: 'Hey, your point about context-dependent blocking was spot on — it\'s exactly why we use Thompson Sampling instead of hard binary blocks. Would you be interested in trying ThumbGate Pro for free? If you find it useful, I\'d appreciate a quick testimonial quote for the site. No obligation. Repo: https://github.com/IgorGanapolsky/ThumbGate'
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'
161
171
  }
162
172
  ];
163
173
 
@@ -6,13 +6,11 @@ const path = require('path');
6
6
  const os = require('os');
7
7
  const { execSync } = require('child_process');
8
8
  const { buildAgenticDataPipelineJobSpec } = require('./agentic-data-pipeline');
9
+ const { ensureDir } = require('./fs-utils');
9
10
 
10
11
  const SCHEDULES_DIR = path.join(os.homedir(), '.thumbgate', 'schedules');
11
12
  const PLIST_PREFIX = 'com.thumbgate.schedule';
12
13
 
13
- function ensureDir() {
14
- if (!fs.existsSync(SCHEDULES_DIR)) fs.mkdirSync(SCHEDULES_DIR, { recursive: true });
15
- }
16
14
 
17
15
  function escapePlistString(value) {
18
16
  return String(value || '')
@@ -153,7 +151,7 @@ function buildAgenticDataPipelineSchedule(params = {}) {
153
151
  }
154
152
 
155
153
  function createSchedule(params) {
156
- ensureDir();
154
+ ensureDir(SCHEDULES_DIR);
157
155
 
158
156
  const id = params.id || params.name || `sched_${Date.now()}`;
159
157
  const calendarInterval = parseCronSpec(params.schedule);
@@ -214,7 +212,7 @@ function createSchedule(params) {
214
212
  }
215
213
 
216
214
  function listSchedules() {
217
- ensureDir();
215
+ ensureDir(SCHEDULES_DIR);
218
216
  const files = fs.readdirSync(SCHEDULES_DIR).filter(f => f.endsWith('.json'));
219
217
  return files.map(f => {
220
218
  try {