thumbgate 1.2.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 (160) hide show
  1. package/.claude-plugin/README.md +4 -4
  2. package/.claude-plugin/marketplace.json +32 -13
  3. package/.claude-plugin/plugin.json +15 -2
  4. package/.well-known/llms.txt +60 -0
  5. package/.well-known/mcp/server-card.json +1 -1
  6. package/README.md +133 -23
  7. package/adapters/README.md +1 -1
  8. package/adapters/chatgpt/openapi.yaml +168 -0
  9. package/adapters/claude/.mcp.json +2 -2
  10. package/adapters/codex/config.toml +2 -2
  11. package/adapters/mcp/server-stdio.js +85 -2
  12. package/adapters/opencode/opencode.json +1 -1
  13. package/bin/cli.js +215 -19
  14. package/bin/postinstall.js +8 -2
  15. package/config/budget.json +18 -0
  16. package/config/gates/code-edit.json +61 -0
  17. package/config/gates/db-write.json +61 -0
  18. package/config/gates/default.json +154 -3
  19. package/config/gates/deploy.json +61 -0
  20. package/config/github-about.json +2 -1
  21. package/config/merge-quality-checks.json +23 -0
  22. package/config/model-tiers.json +11 -0
  23. package/openapi/openapi.yaml +168 -0
  24. package/package.json +47 -13
  25. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  26. package/plugins/claude-codex-bridge/.mcp.json +1 -1
  27. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
  28. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  29. package/plugins/codex-profile/.mcp.json +1 -1
  30. package/plugins/codex-profile/INSTALL.md +27 -4
  31. package/plugins/codex-profile/README.md +33 -9
  32. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  33. package/plugins/cursor-marketplace/README.md +2 -2
  34. package/plugins/cursor-marketplace/commands/capture-feedback.md +2 -2
  35. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +3 -3
  36. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +3 -2
  37. package/plugins/opencode-profile/INSTALL.md +1 -1
  38. package/public/blog.html +73 -0
  39. package/public/compare/mem0.html +189 -0
  40. package/public/compare/speclock.html +180 -0
  41. package/public/compare.html +12 -4
  42. package/public/guide.html +5 -5
  43. package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
  44. package/public/guides/codex-cli-guardrails.html +158 -0
  45. package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
  46. package/public/guides/pre-action-gates.html +162 -0
  47. package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
  48. package/public/index.html +169 -70
  49. package/public/learn/ai-agent-persistent-memory.html +1 -0
  50. package/public/lessons.html +334 -17
  51. package/public/llm-context.md +140 -0
  52. package/public/pro.html +24 -22
  53. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  54. package/scripts/access-anomaly-detector.js +1 -1
  55. package/scripts/adk-consolidator.js +1 -5
  56. package/scripts/agent-security-hardening.js +4 -6
  57. package/scripts/agentic-data-pipeline.js +1 -3
  58. package/scripts/async-job-runner.js +1 -5
  59. package/scripts/audit-trail.js +7 -5
  60. package/scripts/background-agent-governance.js +2 -10
  61. package/scripts/billing.js +2 -16
  62. package/scripts/budget-enforcer.js +173 -0
  63. package/scripts/build-codex-plugin.js +152 -0
  64. package/scripts/capture-railway-diagnostics.sh +97 -0
  65. package/scripts/check-congruence.js +133 -15
  66. package/scripts/claude-feedback-sync.js +320 -0
  67. package/scripts/cli-telemetry.js +4 -1
  68. package/scripts/commercial-offer.js +5 -7
  69. package/scripts/content-engine/linkedin-content-generator.js +154 -0
  70. package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
  71. package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
  72. package/scripts/content-engine/reddit-thread-finder.js +154 -0
  73. package/scripts/context-engine.js +21 -6
  74. package/scripts/contextfs.js +33 -44
  75. package/scripts/dashboard.js +104 -0
  76. package/scripts/decision-journal.js +341 -0
  77. package/scripts/delegation-runtime.js +1 -5
  78. package/scripts/distribution-surfaces.js +26 -0
  79. package/scripts/document-intake.js +927 -0
  80. package/scripts/ephemeral-agent-store.js +1 -8
  81. package/scripts/evolution-state.js +1 -5
  82. package/scripts/experiment-tracker.js +1 -5
  83. package/scripts/export-databricks-bundle.js +1 -5
  84. package/scripts/export-hf-dataset.js +1 -5
  85. package/scripts/export-training.js +1 -5
  86. package/scripts/feedback-attribution.js +1 -16
  87. package/scripts/feedback-history-distiller.js +1 -16
  88. package/scripts/feedback-loop.js +17 -5
  89. package/scripts/feedback-root-consolidator.js +2 -21
  90. package/scripts/feedback-session.js +49 -0
  91. package/scripts/feedback-to-rules.js +188 -28
  92. package/scripts/filesystem-search.js +1 -9
  93. package/scripts/fs-utils.js +104 -0
  94. package/scripts/gates-engine.js +149 -4
  95. package/scripts/github-about.js +32 -8
  96. package/scripts/gtm-revenue-loop.js +1 -5
  97. package/scripts/harness-selector.js +148 -0
  98. package/scripts/hosted-job-launcher.js +1 -5
  99. package/scripts/hybrid-feedback-context.js +7 -33
  100. package/scripts/intervention-policy.js +753 -0
  101. package/scripts/lesson-db.js +3 -18
  102. package/scripts/lesson-inference.js +194 -16
  103. package/scripts/lesson-retrieval.js +60 -24
  104. package/scripts/llm-client.js +59 -0
  105. package/scripts/local-model-profile.js +18 -2
  106. package/scripts/managed-lesson-agent.js +183 -0
  107. package/scripts/marketing-experiment.js +8 -22
  108. package/scripts/meta-agent-loop.js +624 -0
  109. package/scripts/metered-billing.js +1 -1
  110. package/scripts/model-tier-router.js +10 -1
  111. package/scripts/money-watcher.js +1 -4
  112. package/scripts/obsidian-export.js +1 -5
  113. package/scripts/operational-integrity.js +369 -34
  114. package/scripts/org-dashboard.js +6 -1
  115. package/scripts/per-step-scoring.js +2 -4
  116. package/scripts/pr-manager.js +201 -19
  117. package/scripts/pro-features.js +3 -2
  118. package/scripts/prompt-dlp.js +3 -3
  119. package/scripts/prove-adapters.js +2 -5
  120. package/scripts/prove-attribution.js +1 -5
  121. package/scripts/prove-automation.js +3 -5
  122. package/scripts/prove-cloudflare-sandbox.js +1 -3
  123. package/scripts/prove-data-pipeline.js +1 -3
  124. package/scripts/prove-intelligence.js +1 -3
  125. package/scripts/prove-lancedb.js +1 -5
  126. package/scripts/prove-local-intelligence.js +1 -3
  127. package/scripts/prove-packaged-runtime.js +326 -0
  128. package/scripts/prove-predictive-insights.js +1 -3
  129. package/scripts/prove-runtime.js +13 -0
  130. package/scripts/prove-training-export.js +1 -3
  131. package/scripts/prove-workflow-contract.js +1 -5
  132. package/scripts/rate-limiter.js +6 -4
  133. package/scripts/reddit-dm-outreach.js +14 -4
  134. package/scripts/schedule-manager.js +3 -5
  135. package/scripts/security-scanner.js +448 -0
  136. package/scripts/self-distill-agent.js +579 -0
  137. package/scripts/semantic-dedup.js +115 -0
  138. package/scripts/skill-exporter.js +1 -3
  139. package/scripts/skill-generator.js +1 -5
  140. package/scripts/social-analytics/engagement-audit.js +1 -18
  141. package/scripts/social-analytics/pollers/linkedin.js +26 -16
  142. package/scripts/social-analytics/publishers/linkedin.js +1 -1
  143. package/scripts/social-analytics/publishers/zernio.js +51 -0
  144. package/scripts/social-pipeline.js +1 -3
  145. package/scripts/social-post-hourly.js +47 -4
  146. package/scripts/statusline-links.js +6 -5
  147. package/scripts/statusline-local-stats.js +2 -0
  148. package/scripts/statusline.sh +38 -7
  149. package/scripts/sync-branch-protection.js +340 -0
  150. package/scripts/tessl-export.js +1 -3
  151. package/scripts/thumbgate-search.js +32 -1
  152. package/scripts/tool-kpi-tracker.js +1 -1
  153. package/scripts/tool-registry.js +108 -4
  154. package/scripts/vector-store.js +1 -5
  155. package/scripts/weekly-auto-post.js +1 -1
  156. package/scripts/workflow-sentinel.js +205 -4
  157. package/skills/thumbgate/SKILL.md +2 -2
  158. package/src/api/server.js +273 -4
  159. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  160. /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const { execFileSync } = require('node:child_process');
7
+ const {
8
+ getCodexPluginVersionedAssetName,
9
+ } = require('./distribution-surfaces');
10
+
11
+ const PROJECT_ROOT = path.join(__dirname, '..');
12
+ const DEFAULT_OUTPUT_DIR = path.join(PROJECT_ROOT, '.artifacts', 'codex-plugin');
13
+ const BUNDLE_ROOT_NAME = 'thumbgate-codex-plugin';
14
+ const FIXED_BINARY_DIRS = ['/usr/bin', '/bin'];
15
+
16
+ function readJson(relativePath) {
17
+ return JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, relativePath), 'utf8'));
18
+ }
19
+
20
+ function readText(relativePath) {
21
+ return fs.readFileSync(path.join(PROJECT_ROOT, relativePath), 'utf8');
22
+ }
23
+
24
+ function copyEntry(sourceRelativePath, targetRelativePath, stageDir) {
25
+ const sourcePath = path.join(PROJECT_ROOT, sourceRelativePath);
26
+ if (!fs.existsSync(sourcePath)) return;
27
+
28
+ const targetPath = path.join(stageDir, targetRelativePath || sourceRelativePath);
29
+ const stat = fs.statSync(sourcePath);
30
+
31
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
32
+ if (stat.isDirectory()) {
33
+ fs.cpSync(sourcePath, targetPath, { recursive: true });
34
+ return;
35
+ }
36
+
37
+ fs.copyFileSync(sourcePath, targetPath);
38
+ }
39
+
40
+ function buildStandaloneMarketplace() {
41
+ const marketplace = readJson('.agents/plugins/marketplace.json');
42
+ return {
43
+ ...marketplace,
44
+ plugins: (marketplace.plugins || []).map((plugin) => ({
45
+ ...plugin,
46
+ source: {
47
+ ...plugin.source,
48
+ path: './',
49
+ },
50
+ })),
51
+ };
52
+ }
53
+
54
+ function resolveFixedBinary(binaryName, options = {}) {
55
+ const accessSync = options.accessSync || fs.accessSync;
56
+ const dirs = options.dirs || FIXED_BINARY_DIRS;
57
+
58
+ for (const dir of dirs) {
59
+ const binaryPath = path.join(dir, binaryName);
60
+ try {
61
+ accessSync(binaryPath, fs.constants.X_OK);
62
+ return binaryPath;
63
+ } catch {
64
+ // Try the next fixed, system-owned directory.
65
+ }
66
+ }
67
+
68
+ throw new Error(`Unable to find executable ${binaryName} in fixed system paths: ${dirs.join(', ')}`);
69
+ }
70
+
71
+ function stageCodexPluginBundle(outputDir = DEFAULT_OUTPUT_DIR) {
72
+ const packageJson = readJson('package.json');
73
+ const bundleDir = path.join(outputDir, 'bundle');
74
+ const stageDir = path.join(bundleDir, BUNDLE_ROOT_NAME);
75
+ const outputFile = path.join(outputDir, getCodexPluginVersionedAssetName(packageJson.version));
76
+
77
+ fs.rmSync(outputDir, { recursive: true, force: true });
78
+ fs.mkdirSync(stageDir, { recursive: true });
79
+
80
+ copyEntry('plugins/codex-profile/.codex-plugin', '.codex-plugin', stageDir);
81
+ copyEntry('plugins/codex-profile/.mcp.json', '.mcp.json', stageDir);
82
+ copyEntry('plugins/codex-profile/README.md', 'README.md', stageDir);
83
+ copyEntry('plugins/codex-profile/INSTALL.md', 'INSTALL.md', stageDir);
84
+ copyEntry('plugins/codex-profile/AGENTS.md', 'AGENTS.md', stageDir);
85
+ copyEntry('LICENSE', 'LICENSE', stageDir);
86
+ copyEntry('adapters/codex/config.toml', 'config.toml', stageDir);
87
+
88
+ fs.mkdirSync(path.join(stageDir, '.agents', 'plugins'), { recursive: true });
89
+ fs.writeFileSync(
90
+ path.join(stageDir, '.agents', 'plugins', 'marketplace.json'),
91
+ JSON.stringify(buildStandaloneMarketplace(), null, 2) + '\n'
92
+ );
93
+
94
+ return {
95
+ bundleDir,
96
+ stageDir,
97
+ outputFile,
98
+ };
99
+ }
100
+
101
+ function buildCodexPlugin(outputDir = DEFAULT_OUTPUT_DIR) {
102
+ const { bundleDir, stageDir, outputFile } = stageCodexPluginBundle(outputDir);
103
+ const zipBinary = resolveFixedBinary('zip');
104
+ const unzipBinary = resolveFixedBinary('unzip');
105
+
106
+ execFileSync(zipBinary, ['-qr', outputFile, BUNDLE_ROOT_NAME], {
107
+ cwd: bundleDir,
108
+ stdio: 'inherit',
109
+ });
110
+
111
+ const contents = execFileSync(unzipBinary, ['-l', outputFile], {
112
+ cwd: PROJECT_ROOT,
113
+ encoding: 'utf8',
114
+ });
115
+
116
+ process.stdout.write(contents);
117
+
118
+ return {
119
+ stageDir,
120
+ outputFile,
121
+ contents,
122
+ };
123
+ }
124
+
125
+ function isCliEntrypoint(argv = process.argv) {
126
+ return path.resolve(argv[1] || '') === __filename;
127
+ }
128
+
129
+ function runCli(argv = process.argv, cwd = process.cwd(), log = console.log) {
130
+ const outputDir = argv[2]
131
+ ? path.resolve(cwd, argv[2])
132
+ : DEFAULT_OUTPUT_DIR;
133
+ const { outputFile } = buildCodexPlugin(outputDir);
134
+ log(`Built Codex plugin bundle: ${outputFile}`);
135
+ return outputFile;
136
+ }
137
+
138
+ if (isCliEntrypoint()) {
139
+ runCli();
140
+ }
141
+
142
+ module.exports = {
143
+ BUNDLE_ROOT_NAME,
144
+ DEFAULT_OUTPUT_DIR,
145
+ FIXED_BINARY_DIRS,
146
+ buildCodexPlugin,
147
+ buildStandaloneMarketplace,
148
+ isCliEntrypoint,
149
+ resolveFixedBinary,
150
+ runCli,
151
+ stageCodexPluginBundle,
152
+ };
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ DIAG_DIR="${1:-railway-diagnostics}"
5
+ LOG_LINES="${RAILWAY_LOG_LINES:-200}"
6
+ HTTP_LOG_LINES="${RAILWAY_HTTP_LOG_LINES:-50}"
7
+ CONNECT_TIMEOUT_SECONDS="${RAILWAY_HEALTHCHECK_CONNECT_TIMEOUT_SECONDS:-5}"
8
+ MAX_TIME_SECONDS="${RAILWAY_HEALTHCHECK_MAX_TIME_SECONDS:-20}"
9
+
10
+ mkdir -p "$DIAG_DIR"
11
+
12
+ if ! command -v railway >/dev/null 2>&1; then
13
+ echo 'Railway CLI is not installed; skipping Railway diagnostics capture.'
14
+ exit 0
15
+ fi
16
+
17
+ if [ -z "${RAILWAY_PROJECT_ID:-}" ] || [ -z "${RAILWAY_ENVIRONMENT_ID:-}" ]; then
18
+ echo 'RAILWAY_PROJECT_ID and RAILWAY_ENVIRONMENT_ID are required for Railway diagnostics.'
19
+ exit 1
20
+ fi
21
+
22
+ SERVICE_ARGS=()
23
+ if [ -n "${RAILWAY_SERVICE:-}" ]; then
24
+ SERVICE_ARGS+=(--service "$RAILWAY_SERVICE")
25
+ fi
26
+
27
+ ENV_ARGS=(--environment "$RAILWAY_ENVIRONMENT_ID")
28
+
29
+ run_capture() {
30
+ local label="$1"
31
+ local output_path="$2"
32
+ shift 2
33
+
34
+ echo "== ${label} =="
35
+ if "$@" >"$output_path" 2>"${output_path}.stderr"; then
36
+ sed -n '1,120p' "$output_path" || true
37
+ return 0
38
+ fi
39
+
40
+ sed -n '1,120p' "${output_path}.stderr" || true
41
+ return 1
42
+ }
43
+
44
+ run_capture \
45
+ 'railway link' \
46
+ "$DIAG_DIR/link.json" \
47
+ railway link --project "$RAILWAY_PROJECT_ID" "${ENV_ARGS[@]}" "${SERVICE_ARGS[@]}" --json || true
48
+
49
+ run_capture \
50
+ 'railway status' \
51
+ "$DIAG_DIR/status.json" \
52
+ railway status --json || true
53
+
54
+ run_capture \
55
+ 'railway service status' \
56
+ "$DIAG_DIR/service-status.json" \
57
+ railway service status "${SERVICE_ARGS[@]}" "${ENV_ARGS[@]}" --json || true
58
+
59
+ run_capture \
60
+ 'railway deployment logs' \
61
+ "$DIAG_DIR/deployment-logs.jsonl" \
62
+ railway logs "${SERVICE_ARGS[@]}" "${ENV_ARGS[@]}" --latest --deployment --lines "$LOG_LINES" --json || true
63
+
64
+ run_capture \
65
+ 'railway build logs' \
66
+ "$DIAG_DIR/build-logs.jsonl" \
67
+ railway logs "${SERVICE_ARGS[@]}" "${ENV_ARGS[@]}" --latest --build --lines "$LOG_LINES" --json || true
68
+
69
+ run_capture \
70
+ 'railway http logs (/health 5xx)' \
71
+ "$DIAG_DIR/http-health-5xx.jsonl" \
72
+ railway logs "${SERVICE_ARGS[@]}" "${ENV_ARGS[@]}" --latest --http --path /health --status '>=500' --lines "$HTTP_LOG_LINES" --json || true
73
+
74
+ run_capture \
75
+ 'railway http logs (all 5xx)' \
76
+ "$DIAG_DIR/http-5xx.jsonl" \
77
+ railway logs "${SERVICE_ARGS[@]}" "${ENV_ARGS[@]}" --latest --http --status '>=500' --lines "$HTTP_LOG_LINES" --json || true
78
+
79
+ HEALTHCHECK_URL="${RAILWAY_HEALTHCHECK_URL:-}"
80
+ if [ -n "$HEALTHCHECK_URL" ]; then
81
+ echo '== direct health probe =='
82
+ HEALTH_STATUS="$(
83
+ curl \
84
+ --connect-timeout "$CONNECT_TIMEOUT_SECONDS" \
85
+ --max-time "$MAX_TIME_SECONDS" \
86
+ -sS \
87
+ -D "$DIAG_DIR/health-headers.txt" \
88
+ -o "$DIAG_DIR/health-body.txt" \
89
+ -w '%{http_code}' \
90
+ "$HEALTHCHECK_URL" || true
91
+ )"
92
+ printf '%s\n' "$HEALTH_STATUS" > "$DIAG_DIR/health-status.txt"
93
+ echo "Health status: ${HEALTH_STATUS:-<curl_failed>}"
94
+ if [ -s "$DIAG_DIR/health-body.txt" ]; then
95
+ sed -n '1,120p' "$DIAG_DIR/health-body.txt" || true
96
+ fi
97
+ fi
@@ -18,8 +18,53 @@ const {
18
18
  PRODUCTHUNT_URL,
19
19
  getClaudePluginLatestDownloadUrl,
20
20
  } = require('./distribution-surfaces');
21
+ const {
22
+ TEAM_MIN_SEATS,
23
+ TEAM_MONTHLY_PRICE_DOLLARS,
24
+ TEAM_PRICE_LABEL,
25
+ } = require('./commercial-offer');
21
26
 
22
27
  const ROOT = path.join(__dirname, '..');
28
+ const PRICING_SURFACE_ROOTS = [
29
+ 'README.md',
30
+ 'SKILL.md',
31
+ 'bin',
32
+ 'docs',
33
+ 'public',
34
+ '.agents/skills/thumbgate/SKILL.md',
35
+ '.claude/skills/thumbgate/SKILL.md',
36
+ ];
37
+ const PRICING_SURFACE_EXTENSIONS = new Set(['.html', '.js', '.json', '.md', '.txt']);
38
+ const LEGACY_THUMBGATE_PRICING_PATTERNS = [
39
+ {
40
+ label: 'legacy $12 Team seat price',
41
+ pattern: /\$12\s*\/\s*seat\s*\/\s*mo|\$12\/seat|\bTEAM \$12\b|"price":\s*"12"/i,
42
+ },
43
+ {
44
+ label: 'retired founder $5 pricing',
45
+ pattern: /(?:Founding Member|Founding|founder)[^\n]{0,80}\$5\/mo|\$5\/mo[^\n]{0,80}(?:Founding Member|Founding|founder)|Price:\s*\$5\/mo recurring/i,
46
+ },
47
+ {
48
+ label: 'retired $10 Pro pricing',
49
+ pattern: /(?:\*\*Pro\*\*|\bPro\b|price reverts|paying users)[^\n]{0,80}\$10\/mo|\$10\/mo[^\n]{0,80}(?:\*\*Pro\*\*|\bPro\b|price reverts|paying users)/i,
50
+ },
51
+ {
52
+ label: 'retired $29 Team pricing',
53
+ pattern: /(?:\*\*Team\*\*|Team)[^\n]{0,80}\$29\/mo|\$29\/mo[^\n]{0,80}(?:\*\*Team\*\*|Team)/i,
54
+ },
55
+ {
56
+ label: 'retired $19 starter-pack positioning',
57
+ pattern: /Mistake-Free Starter Pack|Mistake-Free Starter Pack[^\n]{0,80}\$19\/mo|\$19\/mo[^\n]{0,80}subscriptions/i,
58
+ },
59
+ {
60
+ label: 'retired $49 founder lifetime pricing',
61
+ pattern: /(?:Founding Member|Founder|Founding Member Deal)[^\n]{0,100}\$49|\$49[^\n]{0,100}(?:Pro forever|Founding Member|Founder)/i,
62
+ },
63
+ {
64
+ label: 'retired founder-license positioning',
65
+ pattern: /founder[- ]license/i,
66
+ },
67
+ ];
23
68
 
24
69
  function read(rel) {
25
70
  const full = path.join(ROOT, rel);
@@ -27,6 +72,27 @@ function read(rel) {
27
72
  return fs.readFileSync(full, 'utf-8');
28
73
  }
29
74
 
75
+ function listTextFiles(rel) {
76
+ const full = path.join(ROOT, rel);
77
+ if (!fs.existsSync(full)) return [];
78
+ const stat = fs.statSync(full);
79
+ if (stat.isFile()) {
80
+ return PRICING_SURFACE_EXTENSIONS.has(path.extname(full)) ? [rel] : [];
81
+ }
82
+ if (!stat.isDirectory()) return [];
83
+
84
+ const files = [];
85
+ for (const entry of fs.readdirSync(full, { withFileTypes: true })) {
86
+ const childRel = path.join(rel, entry.name);
87
+ if (entry.isDirectory()) {
88
+ files.push(...listTextFiles(childRel));
89
+ } else if (PRICING_SURFACE_EXTENSIONS.has(path.extname(entry.name))) {
90
+ files.push(childRel);
91
+ }
92
+ }
93
+ return files;
94
+ }
95
+
30
96
  async function main() {
31
97
  const errors = [];
32
98
  const githubAbout = loadGitHubAboutConfig(ROOT);
@@ -43,16 +109,32 @@ async function main() {
43
109
 
44
110
  const landingHtml = read('public/index.html') || '';
45
111
  const guideHtml = read('public/guide.html') || '';
112
+ const compareHtml = read('public/compare.html') || '';
113
+ const proHtml = read('public/pro.html') || '';
46
114
  const readmeMd = read('README.md') || '';
47
115
  const commercialTruth = read('docs/COMMERCIAL_TRUTH.md') || '';
116
+ const docsLandingHtml = read('docs/landing-page.html') || '';
48
117
  const agentsMd = read('AGENTS.md') || '';
49
118
  const claudeMd = read('CLAUDE.md') || '';
50
119
  const geminiMd = read('GEMINI.md') || '';
51
120
  const serverStdio = read('adapters/mcp/server-stdio.js') || '';
52
121
  const productHuntKit = read('docs/marketing/product-hunt-launch.md') || '';
122
+ const productHuntLaunchKit = read('docs/marketing/product-hunt-launch-kit.md') || '';
53
123
  const claudePluginReadme = read('.claude-plugin/README.md') || '';
54
124
  const claudeDesktopPacket = read('docs/CLAUDE_DESKTOP_EXTENSION.md') || '';
55
125
  const latestClaudePluginUrl = getClaudePluginLatestDownloadUrl(ROOT);
126
+ const teamSeatPrice = `$${TEAM_MONTHLY_PRICE_DOLLARS}/seat/mo`;
127
+ const teamSeatPricePattern = new RegExp(`\\$${TEAM_MONTHLY_PRICE_DOLLARS}/seat/mo`, 'i');
128
+ const pricingSurfaceFiles = PRICING_SURFACE_ROOTS.flatMap(listTextFiles);
129
+ const legacyPricingHits = [];
130
+ for (const rel of pricingSurfaceFiles) {
131
+ const text = read(rel) || '';
132
+ for (const { label, pattern } of LEGACY_THUMBGATE_PRICING_PATTERNS) {
133
+ if (pattern.test(text)) {
134
+ legacyPricingHits.push(`${rel} (${label})`);
135
+ }
136
+ }
137
+ }
56
138
 
57
139
  check(
58
140
  landingHtml.includes(`v${version}`),
@@ -137,7 +219,19 @@ async function main() {
137
219
  'public/guide.html must advertise the current Pro monthly and annual pricing'
138
220
  );
139
221
  check(
140
- /\$12\/seat\/mo/i.test(guideHtml),
222
+ TEAM_MONTHLY_PRICE_DOLLARS === 99 && TEAM_MIN_SEATS === 3,
223
+ 'scripts/commercial-offer.js must anchor Team at $99/seat/mo with a 3-seat minimum'
224
+ );
225
+ check(
226
+ TEAM_PRICE_LABEL.includes(teamSeatPrice),
227
+ 'scripts/commercial-offer.js Team label must match the canonical Team seat price'
228
+ );
229
+ check(
230
+ legacyPricingHits.length === 0,
231
+ `Legacy ThumbGate pricing found in public pricing surfaces: ${legacyPricingHits.join(', ')}`
232
+ );
233
+ check(
234
+ teamSeatPricePattern.test(guideHtml),
141
235
  'public/guide.html must advertise the current Team pricing anchor'
142
236
  );
143
237
  check(
@@ -145,17 +239,33 @@ async function main() {
145
239
  'docs/COMMERCIAL_TRUTH.md must record the current Pro offer'
146
240
  );
147
241
  check(
148
- /shared lessons and org visibility/i.test(githubAbout.description),
149
- 'config/github-about.json description must mention shared lessons and org visibility'
242
+ teamSeatPricePattern.test(commercialTruth),
243
+ 'docs/COMMERCIAL_TRUTH.md must record the current Team pricing anchor'
244
+ );
245
+ check(
246
+ /shared lessons and org visibility/i.test(githubAbout.metaDescription),
247
+ 'config/github-about.json metaDescription must mention shared lessons and org visibility'
150
248
  );
151
249
  check(
152
250
  /\$19\/mo or \$149\/yr/i.test(readmeMd),
153
251
  'README.md must advertise the current Pro monthly and annual pricing'
154
252
  );
155
253
  check(
156
- /\$12\/seat\/mo/i.test(readmeMd),
254
+ teamSeatPricePattern.test(readmeMd),
157
255
  'README.md must advertise the current Team pricing anchor'
158
256
  );
257
+ for (const [surface, text] of Object.entries({
258
+ 'public/index.html': landingHtml,
259
+ 'public/compare.html': compareHtml,
260
+ 'public/pro.html': proHtml,
261
+ 'docs/landing-page.html': docsLandingHtml,
262
+ 'docs/marketing/product-hunt-launch-kit.md': productHuntLaunchKit,
263
+ })) {
264
+ check(
265
+ teamSeatPricePattern.test(text),
266
+ `${surface} must advertise the current Team pricing anchor`
267
+ );
268
+ }
159
269
  check(
160
270
  /shared hosted lesson db/i.test(readmeMd),
161
271
  'README.md must describe the shared hosted Team lesson database'
@@ -222,7 +332,7 @@ async function main() {
222
332
  'public/index.html must mention the linked feedback session flow'
223
333
  );
224
334
  check(
225
- /unlimited feedback captures/i.test(landingHtml) && /5 lesson searches\/day/i.test(landingHtml),
335
+ /3 feedback captures\/day/i.test(landingHtml) && /5 lesson searches\/day/i.test(landingHtml),
226
336
  'public/index.html must advertise the truthful free-tier capture and lesson-search limits'
227
337
  );
228
338
  check(
@@ -246,24 +356,32 @@ async function main() {
246
356
  'public/index.html must explain the thumbs-down feedback path'
247
357
  );
248
358
  check(
249
- githubAbout.description.includes('👍'),
250
- 'config/github-about.json description must include the thumbs-up icon'
359
+ githubAbout.metaDescription.includes('👍'),
360
+ 'config/github-about.json metaDescription must include the thumbs-up icon'
361
+ );
362
+ check(
363
+ githubAbout.metaDescription.includes('👎'),
364
+ 'config/github-about.json metaDescription must include the thumbs-down icon'
365
+ );
366
+ check(
367
+ /thumbs[\s-]?up/i.test(githubAbout.metaDescription),
368
+ 'config/github-about.json metaDescription must mention thumbs-up feedback'
251
369
  );
252
370
  check(
253
- githubAbout.description.includes('👎'),
254
- 'config/github-about.json description must include the thumbs-down icon'
371
+ /thumbs[\s-]?down/i.test(githubAbout.metaDescription),
372
+ 'config/github-about.json metaDescription must mention thumbs-down feedback'
255
373
  );
256
374
  check(
257
- /thumbs[\s-]?up/i.test(githubAbout.description),
258
- 'config/github-about.json description must mention thumbs-up feedback'
375
+ /history-aware lessons/i.test(githubAbout.metaDescription),
376
+ 'config/github-about.json metaDescription must mention history-aware lessons'
259
377
  );
260
378
  check(
261
- /thumbs[\s-]?down/i.test(githubAbout.description),
262
- 'config/github-about.json description must mention thumbs-down feedback'
379
+ /agent governance/i.test(githubAbout.githubDescription),
380
+ 'config/github-about.json githubDescription must mention agent governance'
263
381
  );
264
382
  check(
265
- /history-aware lessons/i.test(githubAbout.description),
266
- 'config/github-about.json description must mention history-aware lessons'
383
+ /pre-action gates|shared lessons|team safeguards/i.test(githubAbout.githubDescription),
384
+ 'config/github-about.json githubDescription must preserve the GitHub repo positioning'
267
385
  );
268
386
  check(
269
387
  productHuntKit.includes(PRODUCTHUNT_URL),