thumbgate 0.9.13 → 0.9.14

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.
@@ -43,14 +43,139 @@ function dirExists(dirPath) {
43
43
  }
44
44
  }
45
45
 
46
+ function getHomeDir(options = {}) {
47
+ const env = options.env || process.env;
48
+ return options.home || env.HOME || env.USERPROFILE || HOME;
49
+ }
50
+
51
+ function normalizeDir(dirPath) {
52
+ if (!dirPath) return null;
53
+ try {
54
+ return path.resolve(String(dirPath));
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ function isWithinDir(candidate, parent) {
61
+ const normalizedCandidate = normalizeDir(candidate);
62
+ const normalizedParent = normalizeDir(parent);
63
+ if (!normalizedCandidate || !normalizedParent) return false;
64
+ const relative = path.relative(normalizedParent, normalizedCandidate);
65
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
66
+ }
67
+
68
+ function getRuntimeDir(options = {}) {
69
+ return path.join(getHomeDir(options), '.thumbgate', 'runtime');
70
+ }
71
+
72
+ function getActiveProjectStatePath(options = {}) {
73
+ return path.join(getRuntimeDir(options), 'active-project.json');
74
+ }
75
+
76
+ function isTransientProjectDir(dirPath, options = {}) {
77
+ const normalizedDir = normalizeDir(dirPath);
78
+ if (!normalizedDir) return true;
79
+ if (!dirExists(normalizedDir)) return true;
80
+
81
+ const runtimeDir = getRuntimeDir(options);
82
+ if (isWithinDir(normalizedDir, runtimeDir)) return true;
83
+
84
+ return normalizedDir.includes(`${path.sep}.npm${path.sep}_npx${path.sep}`)
85
+ || /thumbgate-published-cli-/i.test(normalizedDir);
86
+ }
87
+
88
+ function readActiveProjectState(options = {}) {
89
+ const statePath = getActiveProjectStatePath(options);
90
+ try {
91
+ const parsed = JSON.parse(fs.readFileSync(statePath, 'utf8'));
92
+ if (!parsed || !parsed.projectDir) return null;
93
+ if (isTransientProjectDir(parsed.projectDir, options)) return null;
94
+ return {
95
+ ...parsed,
96
+ projectDir: normalizeDir(parsed.projectDir),
97
+ };
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function writeActiveProjectState(projectDir, options = {}) {
104
+ const normalizedDir = normalizeDir(projectDir);
105
+ if (isTransientProjectDir(normalizedDir, options)) return null;
106
+
107
+ const payload = {
108
+ projectDir: normalizedDir,
109
+ projectName: path.basename(normalizedDir) || 'default',
110
+ updatedAt: new Date().toISOString(),
111
+ };
112
+
113
+ const statePath = getActiveProjectStatePath(options);
114
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
115
+ fs.writeFileSync(statePath, JSON.stringify(payload, null, 2));
116
+ return payload;
117
+ }
118
+
119
+ function resolveProjectDir(options = {}) {
120
+ const env = options.env || process.env;
121
+ const stored = options.includeStored === false ? null : readActiveProjectState(options);
122
+ const cwdCandidates = uniquePaths([
123
+ options.cwd,
124
+ env.PWD,
125
+ process.cwd(),
126
+ ]);
127
+ const isTransientExecution = cwdCandidates.length > 0
128
+ && cwdCandidates.every((candidate) => isTransientProjectDir(candidate, options));
129
+ const candidates = uniquePaths([
130
+ options.projectDir,
131
+ env.THUMBGATE_PROJECT_DIR,
132
+ env.CLAUDE_PROJECT_DIR,
133
+ isTransientExecution && stored && stored.projectDir,
134
+ env.INIT_CWD,
135
+ ...cwdCandidates,
136
+ !isTransientExecution && stored && stored.projectDir,
137
+ ]);
138
+
139
+ for (const candidate of candidates) {
140
+ if (!candidate) continue;
141
+ if (!isTransientProjectDir(candidate, options)) {
142
+ return normalizeDir(candidate);
143
+ }
144
+ }
145
+
146
+ return normalizeDir(options.cwd || env.PWD || PROJECT_ROOT) || PROJECT_ROOT;
147
+ }
148
+
46
149
  function getProjectName(cwd = process.cwd()) {
47
150
  return path.basename(cwd || PROJECT_ROOT) || 'default';
48
151
  }
49
152
 
153
+ function hasDirectProjectScope(options = {}) {
154
+ const env = options.env || process.env;
155
+ return Boolean(
156
+ options.explicitProjectDir
157
+ || env.THUMBGATE_PROJECT_DIR
158
+ || env.CLAUDE_PROJECT_DIR
159
+ );
160
+ }
161
+
162
+ function hasExplicitProjectScope(options = {}) {
163
+ return Boolean(hasDirectProjectScope(options) || readActiveProjectState(options));
164
+ }
165
+
50
166
  function getExplicitFeedbackDir(options = {}) {
51
167
  const env = options.env || process.env;
52
168
  if (options.feedbackDir) return options.feedbackDir;
53
- if (env.THUMBGATE_FEEDBACK_DIR) return env.THUMBGATE_FEEDBACK_DIR;
169
+ if (options.skipExplicitFeedbackDir) return null;
170
+ // A caller-provided feedback root should stay authoritative over stored
171
+ // active-project state so isolated CLI/test commands do not drift into a
172
+ // different project. Only direct project overrides suppress it.
173
+ if (env.THUMBGATE_FEEDBACK_DIR && !hasDirectProjectScope(options)) {
174
+ return env.THUMBGATE_FEEDBACK_DIR;
175
+ }
176
+ if (hasDirectProjectScope(options)) {
177
+ return null;
178
+ }
54
179
  if (env.RAILWAY_VOLUME_MOUNT_PATH) {
55
180
  return path.join(env.RAILWAY_VOLUME_MOUNT_PATH, 'feedback');
56
181
  }
@@ -58,30 +183,29 @@ function getExplicitFeedbackDir(options = {}) {
58
183
  }
59
184
 
60
185
  function getThumbgateFeedbackDir(options = {}) {
61
- const cwd = options.cwd || process.cwd();
62
- return path.join(cwd, '.thumbgate');
186
+ const projectDir = resolveProjectDir(options);
187
+ return path.join(projectDir, '.thumbgate');
63
188
  }
64
189
 
65
190
  function getFallbackFeedbackDir(options = {}) {
66
191
  const env = options.env || process.env;
67
192
  if (env._TEST_THUMBGATE_FALLBACK_FEEDBACK_DIR) return env._TEST_THUMBGATE_FALLBACK_FEEDBACK_DIR;
68
193
  if (env.THUMBGATE_FALLBACK_FEEDBACK_DIR) return env.THUMBGATE_FALLBACK_FEEDBACK_DIR;
69
- const cwd = options.cwd || process.cwd();
70
- return path.join(cwd, '.thumbgate-compat');
194
+ const projectDir = resolveProjectDir(options);
195
+ return path.join(projectDir, '.thumbgate-compat');
71
196
  }
72
197
 
73
198
  function getLegacyFeedbackDir(options = {}) {
74
199
  const env = options.env || process.env;
75
200
  if (env._TEST_LEGACY_FEEDBACK_DIR) return env._TEST_LEGACY_FEEDBACK_DIR;
76
201
  if (env.THUMBGATE_LEGACY_FEEDBACK_DIR) return env.THUMBGATE_LEGACY_FEEDBACK_DIR;
77
- const cwd = options.cwd || process.cwd();
78
- return path.join(cwd, '.claude', 'memory', 'feedback');
202
+ const projectDir = resolveProjectDir(options);
203
+ return path.join(projectDir, '.claude', 'memory', 'feedback');
79
204
  }
80
205
 
81
206
  function getGlobalFeedbackDir(options = {}) {
82
- const cwd = options.cwd || process.cwd();
83
- const home = options.home || HOME;
84
- return path.join(home, '.thumbgate', 'projects', getProjectName(cwd));
207
+ const projectDir = resolveProjectDir(options);
208
+ return path.join(getHomeDir(options), '.thumbgate', 'projects', getProjectName(projectDir));
85
209
  }
86
210
 
87
211
  function resolveFeedbackDir(options = {}) {
@@ -133,13 +257,21 @@ module.exports = {
133
257
  PROJECT_ROOT,
134
258
  HOME,
135
259
  buildFeedbackPathsFromDir,
260
+ getActiveProjectStatePath,
136
261
  getFeedbackPaths,
137
262
  getGlobalFeedbackDir,
263
+ getHomeDir,
138
264
  getLegacyFeedbackDir,
139
265
  getFallbackFeedbackDir,
266
+ getRuntimeDir,
140
267
  getThumbgateFeedbackDir,
268
+ hasDirectProjectScope,
269
+ hasExplicitProjectScope,
270
+ readActiveProjectState,
141
271
  listFallbackFeedbackDirs,
142
272
  listFeedbackArtifactPaths,
273
+ resolveProjectDir,
143
274
  resolveFallbackArtifactPath,
144
275
  resolveFeedbackDir,
276
+ writeActiveProjectState,
145
277
  };
@@ -29,6 +29,19 @@ const CONSOLIDATED_ARTIFACTS = [
29
29
  'workflow-sprint-leads.jsonl',
30
30
  ];
31
31
 
32
+ function getScopedProjectOptions(options = {}) {
33
+ if (options.feedbackDir) return options;
34
+
35
+ const projectDir = options.projectDir || options.cwd;
36
+ if (!projectDir) return options;
37
+
38
+ return {
39
+ ...options,
40
+ projectDir,
41
+ explicitProjectDir: true,
42
+ };
43
+ }
44
+
32
45
  function ensureParentDir(filePath) {
33
46
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
34
47
  }
@@ -194,11 +207,11 @@ function consolidateArtifact(fileName, options = {}) {
194
207
  }
195
208
 
196
209
  function consolidateFeedbackRoot(options = {}) {
197
- const feedbackDir = options.feedbackDir
198
- || process.env.THUMBGATE_FEEDBACK_DIR
199
- || getThumbgateFeedbackDir(options);
210
+ const scopedOptions = getScopedProjectOptions(options);
211
+ const feedbackDir = scopedOptions.feedbackDir
212
+ || getThumbgateFeedbackDir(scopedOptions);
200
213
  const artifacts = CONSOLIDATED_ARTIFACTS.map((fileName) => consolidateArtifact(fileName, {
201
- ...options,
214
+ ...scopedOptions,
202
215
  feedbackDir,
203
216
  }));
204
217
  const sourceRoots = Array.from(new Set(
@@ -229,6 +242,7 @@ module.exports = {
229
242
  consolidateArtifact,
230
243
  consolidateFeedbackRoot,
231
244
  dedupeJsonlRows,
245
+ getScopedProjectOptions,
232
246
  mergeCheckoutSessionsPayloads,
233
247
  mergeKeyStorePayloads,
234
248
  };
@@ -25,6 +25,7 @@
25
25
  const fs = require('fs');
26
26
  const path = require('path');
27
27
  const { tagUrlsInText } = require('./social-analytics/utm');
28
+ const { isDuplicate, recordPost } = require('./social-analytics/publishers/zernio');
28
29
 
29
30
  // ---------------------------------------------------------------------------
30
31
  // Publisher imports (lazy — only loaded when needed)
@@ -259,9 +260,18 @@ async function postEverywhere(filePath, { platforms, dryRun } = {}) {
259
260
  continue;
260
261
  }
261
262
 
263
+ // Dedup guard: skip platforms where identical content was posted in last 24h
264
+ const dedupContent = [parsed.title, parsed.body].filter(Boolean).join('\n');
265
+ if (!dryRun && isDuplicate(dedupContent, platform)) {
266
+ console.log(`[post-everywhere] ${platform}: SKIPPED — duplicate content within 24h`);
267
+ results[platform] = { skipped: true, reason: 'duplicate_content_24h' };
268
+ continue;
269
+ }
270
+
262
271
  try {
263
272
  console.log(`\n[post-everywhere] Posting to ${platform}...`);
264
273
  results[platform] = await dispatcher(parsed, dryRun);
274
+ if (!dryRun) recordPost(dedupContent, platform);
265
275
  console.log(`[post-everywhere] ${platform}: OK`);
266
276
  } catch (err) {
267
277
  console.error(`[post-everywhere] ${platform}: FAILED — ${err.message}`);
@@ -67,12 +67,30 @@ const HIGH_ROI_QUERY_SEEDS = [
67
67
  source: 'seed',
68
68
  notes: 'Problem-led copy that maps to landing-page positioning.',
69
69
  },
70
+ {
71
+ query: 'cursor prevent repeated mistakes',
72
+ businessValue: 87,
73
+ source: 'seed',
74
+ notes: 'High-intent Cursor workflow page for developers already feeling repeat-failure pain.',
75
+ },
70
76
  {
71
77
  query: 'claude code prevent repeated mistakes',
72
78
  businessValue: 86,
73
79
  source: 'seed',
74
80
  notes: 'High-intent pain query for Claude Code buyers.',
75
81
  },
82
+ {
83
+ query: 'codex cli guardrails',
84
+ businessValue: 84,
85
+ source: 'seed',
86
+ notes: 'Guardrail-focused page for Codex CLI buyers who want prevention, not just memory.',
87
+ },
88
+ {
89
+ query: 'gemini cli feedback memory',
90
+ businessValue: 82,
91
+ source: 'seed',
92
+ notes: 'Integration page for Gemini CLI users who need memory plus enforcement.',
93
+ },
76
94
  ];
77
95
 
78
96
  const PAGE_BLUEPRINTS = [
@@ -225,6 +243,55 @@ const PAGE_BLUEPRINTS = [
225
243
  ],
226
244
  relatedPaths: ['/compare/speclock', '/guides/claude-code-feedback'],
227
245
  },
246
+ {
247
+ query: 'stop ai coding agents from repeating mistakes',
248
+ path: '/guides/stop-repeated-ai-agent-mistakes',
249
+ pageType: 'guide',
250
+ pillar: 'pre-action-gates',
251
+ title: 'How to Stop AI Coding Agents From Repeating Mistakes | ThumbGate',
252
+ heroTitle: 'How to Stop AI Coding Agents From Repeating Mistakes',
253
+ heroSummary: 'If your agent keeps repeating the same bad move, the fix is not more memory alone. The fix is a feedback loop that turns repeated failures into pre-action gates before the next tool call executes.',
254
+ takeaways: [
255
+ 'Repeated mistakes are a workflow problem, not just a context-window problem.',
256
+ 'ThumbGate turns thumbs-down feedback into prevention rules and runtime gates.',
257
+ 'This page is meant to move problem-aware buyers into the Pro path or a concrete install.',
258
+ ],
259
+ sections: [
260
+ {
261
+ heading: 'Why repeated mistakes keep happening',
262
+ paragraphs: [
263
+ 'AI coding agents are fast, but they forget operational pain surprisingly easily. One bad deployment, force-push, or skipped verification step often turns into another because the system remembered the transcript but never enforced the lesson.',
264
+ 'That is why teams feel stuck in a correction loop. They keep teaching the same rule, but the next session still allows the same risky action.',
265
+ ],
266
+ },
267
+ {
268
+ heading: 'What changes when feedback becomes enforcement',
269
+ bullets: [
270
+ 'Thumbs down captures the exact failure you do not want repeated.',
271
+ 'Repeated failures promote into linked prevention rules.',
272
+ 'Pre-action gates intercept the risky tool call before execution.',
273
+ 'Thumbs up reinforces the safe path so the agent learns what good looks like too.',
274
+ ],
275
+ },
276
+ {
277
+ heading: 'What a buyer should do next',
278
+ paragraphs: [
279
+ 'If the pain is already real, do not start with a long architecture project. Start by wiring ThumbGate into the workflow where the agent has already burned time or trust, then watch the next repeat attempt get blocked before damage lands.',
280
+ ],
281
+ },
282
+ ],
283
+ faq: [
284
+ {
285
+ question: 'Is memory alone enough to stop repeated mistakes?',
286
+ answer: 'Usually no. Memory helps retrieval, but ThumbGate adds pre-action gates so the same risky move can be blocked before the next command executes.',
287
+ },
288
+ {
289
+ question: 'Does ThumbGate only punish bad behavior?',
290
+ answer: 'No. Thumbs up reinforces good behavior, so the loop captures safe patterns as well as failures.',
291
+ },
292
+ ],
293
+ relatedPaths: ['/guides/pre-action-gates', '/guides/claude-code-feedback'],
294
+ },
228
295
  {
229
296
  query: 'claude code feedback memory',
230
297
  path: '/guides/claude-code-feedback',
@@ -273,6 +340,150 @@ const PAGE_BLUEPRINTS = [
273
340
  ],
274
341
  relatedPaths: ['/guides/pre-action-gates', '/compare/mem0'],
275
342
  },
343
+ {
344
+ query: 'cursor prevent repeated mistakes',
345
+ path: '/guides/cursor-agent-guardrails',
346
+ pageType: 'integration',
347
+ pillar: 'agent-workflows',
348
+ title: 'Cursor Agent Guardrails | Stop Repeated Mistakes with ThumbGate',
349
+ heroTitle: 'Cursor Guardrails That Block Repeated Mistakes',
350
+ heroSummary: 'Cursor moves fast, which makes repeated mistakes expensive. ThumbGate gives Cursor users a feedback loop that turns thumbs-down corrections into pre-action gates before the next risky step fires.',
351
+ takeaways: [
352
+ 'Cursor users want speed without trusting the agent blindly.',
353
+ 'ThumbGate adds enforcement without forcing a platform switch.',
354
+ 'The page should answer the buyer question in one line: how do I stop Cursor from doing the same bad thing again?',
355
+ ],
356
+ sections: [
357
+ {
358
+ heading: 'The Cursor workflow problem',
359
+ paragraphs: [
360
+ 'Cursor can move from idea to edits quickly, but the failure mode is familiar: the same wrong refactor, risky shell command, or skipped check comes back in the next session because nothing hardened the workflow.',
361
+ ],
362
+ },
363
+ {
364
+ heading: 'How ThumbGate fits into Cursor',
365
+ bullets: [
366
+ 'Capture thumbs-up/down feedback on agent behavior.',
367
+ 'Promote repeated failures into prevention rules.',
368
+ 'Block known-bad commands with pre-action gates before execution.',
369
+ 'Keep the memory and gates local-first so the operator retains control.',
370
+ ],
371
+ },
372
+ {
373
+ heading: 'What makes this different from a rule file',
374
+ paragraphs: [
375
+ 'Static rules help on day one. ThumbGate helps on day two and day twenty because it keeps learning from live corrections instead of relying on a fixed checklist that drifts out of date.',
376
+ ],
377
+ },
378
+ ],
379
+ faq: [
380
+ {
381
+ question: 'Do I need to leave Cursor to use ThumbGate?',
382
+ answer: 'No. ThumbGate is designed to sit alongside existing coding-agent workflows so you can add enforcement without switching tools.',
383
+ },
384
+ {
385
+ question: 'What kind of mistakes can Cursor guardrails stop?',
386
+ answer: 'Repeated failures like risky git actions, destructive scripts, skipped verification, or any other known-bad pattern you have already corrected once.',
387
+ },
388
+ ],
389
+ relatedPaths: ['/guides/stop-repeated-ai-agent-mistakes', '/guides/pre-action-gates'],
390
+ },
391
+ {
392
+ query: 'codex cli guardrails',
393
+ path: '/guides/codex-cli-guardrails',
394
+ pageType: 'integration',
395
+ pillar: 'agent-workflows',
396
+ title: 'Codex CLI Guardrails | Prevent Repeated Mistakes with ThumbGate',
397
+ heroTitle: 'Codex CLI Guardrails That Actually Enforce',
398
+ heroSummary: 'Codex CLI can move quickly through repo tasks, but buyers need more than good intentions. ThumbGate adds a reliability gateway so repeated mistakes become searchable lessons, linked rules, and pre-action enforcement.',
399
+ takeaways: [
400
+ 'Codex CLI buyers are usually looking for safe autonomy, not just more prompts.',
401
+ 'ThumbGate sits in the critical gap between feedback and execution.',
402
+ 'This page should rank for people who want guardrails without giving up CLI speed.',
403
+ ],
404
+ sections: [
405
+ {
406
+ heading: 'What Codex CLI users usually need',
407
+ paragraphs: [
408
+ 'The problem is rarely a single bad command. It is the cost of the same failure pattern showing up across branches, sessions, or rushed workflows. Once that pattern is obvious, the buyer wants a durable control point.',
409
+ ],
410
+ },
411
+ {
412
+ heading: 'What ThumbGate adds',
413
+ bullets: [
414
+ 'Feedback capture with explicit thumbs-up/down signals.',
415
+ 'Searchable lessons and linked prevention rules.',
416
+ 'Pre-action gates that block repeated bad commands before they run.',
417
+ 'Verification evidence that gives teams something concrete to audit.',
418
+ ],
419
+ },
420
+ {
421
+ heading: 'Why this matters for revenue',
422
+ paragraphs: [
423
+ 'Guardrails are easier to buy when the outcome is obvious: less rework, fewer repeated failures, and a visible chain from operator feedback to enforced behavior.',
424
+ ],
425
+ },
426
+ ],
427
+ faq: [
428
+ {
429
+ question: 'Is ThumbGate only for Codex CLI?',
430
+ answer: 'No. Codex CLI is one supported workflow, but the same feedback and enforcement loop also works across Claude Code, Cursor, Gemini, Amp, and OpenCode.',
431
+ },
432
+ {
433
+ question: 'How are Codex CLI guardrails different from prompt instructions?',
434
+ answer: 'Prompt instructions are advisory. ThumbGate pre-action gates intercept the tool call itself and block the known-bad pattern before execution.',
435
+ },
436
+ ],
437
+ relatedPaths: ['/guides/pre-action-gates', '/compare/mem0'],
438
+ },
439
+ {
440
+ query: 'gemini cli feedback memory',
441
+ path: '/guides/gemini-cli-feedback-memory',
442
+ pageType: 'integration',
443
+ pillar: 'agent-workflows',
444
+ title: 'Gemini CLI Feedback Memory | Memory Plus Enforcement with ThumbGate',
445
+ heroTitle: 'Gemini CLI Feedback Memory That Leads to Enforcement',
446
+ heroSummary: 'Gemini CLI users often start by asking for better memory. ThumbGate answers the bigger need: memory that can become prevention rules and pre-action gates when the same mistake shows up twice.',
447
+ takeaways: [
448
+ 'Gemini CLI searchers often begin with memory but buy because of enforcement.',
449
+ 'ThumbGate keeps the local-first memory story while adding runtime blocking.',
450
+ 'The ideal conversion path here is memory query to product proof to Pro page.',
451
+ ],
452
+ sections: [
453
+ {
454
+ heading: 'Why memory is only step one',
455
+ paragraphs: [
456
+ 'Persistent memory helps Gemini CLI recall past context, but it still leaves a blind spot. Remembering that a workflow went badly is different from preventing the next risky action when the same pattern appears again.',
457
+ ],
458
+ },
459
+ {
460
+ heading: 'What ThumbGate adds on top',
461
+ bullets: [
462
+ 'Local-first lessons you can search across sessions.',
463
+ 'Structured thumbs-up/down feedback for reinforcement and correction.',
464
+ 'Prevention rules linked to past failures.',
465
+ 'Pre-action gates that stop repeated mistakes before execution.',
466
+ ],
467
+ },
468
+ {
469
+ heading: 'Who this is really for',
470
+ paragraphs: [
471
+ 'This page is for operators who already know memory matters, but now need a reliability layer that protects live workflows instead of just preserving notes about them.',
472
+ ],
473
+ },
474
+ ],
475
+ faq: [
476
+ {
477
+ question: 'Does ThumbGate replace Gemini CLI memory?',
478
+ answer: 'No. ThumbGate extends the memory story with searchable lessons, rules, and gates so memory becomes operationally useful instead of purely historical.',
479
+ },
480
+ {
481
+ question: 'Can this stay local-first?',
482
+ answer: 'Yes. ThumbGate is built for local-first workflows, which lowers risk for developers who do not want sensitive history pushed into a hosted memory layer.',
483
+ },
484
+ ],
485
+ relatedPaths: ['/compare/mem0', '/guides/stop-repeated-ai-agent-mistakes'],
486
+ },
276
487
  {
277
488
  query: 'claude desktop extension plugin thumbgate',
278
489
  path: '/guides/claude-desktop',
@@ -444,7 +655,7 @@ function classifyIntent(query) {
444
655
  return 'commercial';
445
656
  }
446
657
  if (/\b(what is|how to|guide|best practices|why)\b/.test(normalized)) return 'informational';
447
- if (/\b(guardrails|pre-action gates|feedback|prevent repeated mistakes|memory)\b/.test(normalized)) {
658
+ if (/\b(guardrails|pre-action gates|feedback|prevent repeated mistakes|repeating mistakes|memory)\b/.test(normalized)) {
448
659
  return 'commercial';
449
660
  }
450
661
  return 'informational';
@@ -454,7 +665,7 @@ function inferPillar(query) {
454
665
  const normalized = normalizeText(query).toLowerCase();
455
666
  if (/\b(speclock|mem0|alternative|vs|compare|comparison)\b/.test(normalized)) return 'comparison';
456
667
  if (/\b(thumbs up|thumbs down|feedback|reinforce|mistake)\b/.test(normalized)) return 'feedback-loop';
457
- if (/\b(pre-action gates|guardrails|block|prevent repeated mistakes)\b/.test(normalized)) return 'pre-action-gates';
668
+ if (/\b(pre-action gates|guardrails|block|prevent repeated mistakes|repeating mistakes)\b/.test(normalized)) return 'pre-action-gates';
458
669
  if (/\b(claude code|cursor|codex|gemini|amp|opencode|integration|plugin)\b/.test(normalized)) return 'agent-workflows';
459
670
  return 'ai-agent-reliability';
460
671
  }
@@ -463,6 +674,8 @@ function inferPersona(query) {
463
674
  const normalized = normalizeText(query).toLowerCase();
464
675
  if (normalized.includes('claude code')) return 'claude-code-builder';
465
676
  if (normalized.includes('cursor')) return 'cursor-builder';
677
+ if (normalized.includes('codex')) return 'codex-builder';
678
+ if (normalized.includes('gemini')) return 'gemini-builder';
466
679
  if (/\b(vs|alternative|compare)\b/.test(normalized)) return 'tool-evaluator';
467
680
  if (/\b(guardrails|pre-action gates)\b/.test(normalized)) return 'engineering-lead';
468
681
  return 'ai-engineer';
@@ -628,8 +841,8 @@ function createPageSpec(blueprint, row) {
628
841
  faq: blueprint.faq,
629
842
  relatedPages,
630
843
  cta: {
631
- label: 'Review verification evidence',
632
- href: PRODUCT.verificationUrl,
844
+ label: 'See ThumbGate Pro',
845
+ href: `/pro?utm_source=website&utm_medium=seo_page&utm_campaign=${blueprint.path.split('/').filter(Boolean).join('_')}`,
633
846
  },
634
847
  proofLinks: [
635
848
  { label: 'Verification evidence', href: PRODUCT.verificationUrl },
@@ -2,21 +2,24 @@
2
2
  'use strict';
3
3
 
4
4
  const path = require('path');
5
- const { resolveFeedbackDir } = require('./feedback-paths');
5
+ const {
6
+ listFeedbackArtifactPaths,
7
+ resolveFeedbackDir,
8
+ resolveProjectDir,
9
+ } = require('./feedback-paths');
6
10
 
7
11
  function unique(values = []) {
8
12
  return [...new Set(values.filter(Boolean).map((value) => path.resolve(value)))];
9
13
  }
10
14
 
11
15
  function getStatuslineCacheCandidates(options = {}) {
12
- const cwd = options.cwd || process.cwd();
13
- const home = options.home || process.env.HOME || process.env.USERPROFILE || '';
14
- const feedbackDir = resolveFeedbackDir({ cwd, env: options.env || process.env });
16
+ const env = options.env || process.env;
17
+ const projectDir = resolveProjectDir({ cwd: options.cwd, env });
18
+ const feedbackDir = resolveFeedbackDir({ projectDir, env });
15
19
 
16
20
  return unique([
21
+ ...listFeedbackArtifactPaths('statusline_cache.json', { projectDir, env }),
17
22
  path.join(feedbackDir, 'statusline_cache.json'),
18
- path.join(cwd, '.thumbgate', 'statusline_cache.json'),
19
- home ? path.join(home, '.thumbgate', 'statusline_cache.json') : null,
20
23
  ]);
21
24
  }
22
25