thumbgate 0.9.10 → 0.9.12

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 (115) hide show
  1. package/.claude-plugin/README.md +2 -2
  2. package/.claude-plugin/marketplace.json +4 -2
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +115 -312
  6. package/adapters/README.md +1 -1
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/codex/config.toml +4 -4
  9. package/adapters/mcp/server-stdio.js +61 -1
  10. package/adapters/opencode/opencode.json +4 -2
  11. package/bin/cli.js +156 -8
  12. package/bin/memory.sh +3 -3
  13. package/config/e2e-critical-flows.json +4 -0
  14. package/config/gates/default.json +74 -2
  15. package/config/github-about.json +1 -1
  16. package/config/mcp-allowlists.json +27 -0
  17. package/package.json +22 -5
  18. package/plugins/amp-skill/INSTALL.md +1 -0
  19. package/plugins/amp-skill/SKILL.md +1 -0
  20. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
  21. package/plugins/claude-codex-bridge/.mcp.json +4 -2
  22. package/plugins/claude-skill/INSTALL.md +1 -0
  23. package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
  24. package/plugins/codex-profile/.mcp.json +4 -2
  25. package/plugins/codex-profile/INSTALL.md +1 -1
  26. package/plugins/codex-profile/README.md +1 -1
  27. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
  28. package/plugins/cursor-marketplace/README.md +3 -3
  29. package/plugins/cursor-marketplace/mcp.json +3 -1
  30. package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
  31. package/plugins/gemini-extension/INSTALL.md +3 -3
  32. package/plugins/opencode-profile/INSTALL.md +1 -1
  33. package/public/dashboard.html +15 -8
  34. package/public/index.html +125 -185
  35. package/public/js/buyer-intent.js +252 -0
  36. package/public/pro.html +1085 -0
  37. package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
  38. package/scripts/adk-consolidator.js +14 -2
  39. package/scripts/agent-readiness.js +3 -1
  40. package/scripts/agent-security-hardening.js +4 -4
  41. package/scripts/auto-promote-gates.js +2 -0
  42. package/scripts/auto-wire-hooks.js +105 -17
  43. package/scripts/behavioral-extraction.js +2 -6
  44. package/scripts/billing.js +107 -3
  45. package/scripts/budget-guard.js +2 -2
  46. package/scripts/build-metadata.js +14 -0
  47. package/scripts/context-engine.js +1 -0
  48. package/scripts/deploy-policy.js +3 -17
  49. package/scripts/dpo-optimizer.js +3 -6
  50. package/scripts/ensure-repo-bootstrap.js +129 -0
  51. package/scripts/export-dpo-pairs.js +2 -3
  52. package/scripts/export-kto-pairs.js +3 -4
  53. package/scripts/export-training.js +8 -6
  54. package/scripts/feedback-attribution.js +23 -11
  55. package/scripts/feedback-loop.js +40 -2
  56. package/scripts/feedback-to-rules.js +2 -1
  57. package/scripts/filesystem-search.js +3 -2
  58. package/scripts/gates-engine.js +760 -29
  59. package/scripts/generate-pretool-hook.sh +0 -0
  60. package/scripts/gtm-revenue-loop.js +20 -1
  61. package/scripts/hook-auto-capture.sh +8 -3
  62. package/scripts/hook-runtime.js +81 -0
  63. package/scripts/hook-stop-self-score.sh +3 -3
  64. package/scripts/hook-thumbgate-cache-updater.js +99 -38
  65. package/scripts/hosted-config.js +4 -16
  66. package/scripts/hybrid-feedback-context.js +54 -14
  67. package/scripts/install-mcp.js +13 -3
  68. package/scripts/intent-router.js +2 -2
  69. package/scripts/license.js +52 -14
  70. package/scripts/local-model-profile.js +3 -2
  71. package/scripts/mcp-config.js +62 -7
  72. package/scripts/meta-policy.js +4 -8
  73. package/scripts/money-watcher.js +166 -16
  74. package/scripts/obsidian-export.js +1 -0
  75. package/scripts/operational-integrity.js +480 -0
  76. package/scripts/post-everywhere.js +35 -12
  77. package/scripts/pr-manager.js +14 -11
  78. package/scripts/profile-router.js +2 -0
  79. package/scripts/prompt-dlp.js +1 -0
  80. package/scripts/publish-decision.js +10 -0
  81. package/scripts/published-cli.js +61 -0
  82. package/scripts/risk-scorer.js +3 -2
  83. package/scripts/rlhf_session_start.sh +32 -0
  84. package/scripts/skill-quality-tracker.js +3 -5
  85. package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
  86. package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
  87. package/scripts/social-analytics/engagement-audit.js +202 -0
  88. package/scripts/social-analytics/instagram-thumbgate-post.js +45 -7
  89. package/scripts/social-analytics/install-growth-automation.js +114 -0
  90. package/scripts/social-analytics/load-env.js +46 -0
  91. package/scripts/social-analytics/poll-all.js +23 -23
  92. package/scripts/social-analytics/pollers/plausible.js +2 -4
  93. package/scripts/social-analytics/pollers/zernio.js +3 -0
  94. package/scripts/social-analytics/publish-instagram-thumbgate.js +22 -3
  95. package/scripts/social-analytics/publish-thumbgate-launch.js +322 -0
  96. package/scripts/social-analytics/publishers/reddit.js +7 -12
  97. package/scripts/social-analytics/publishers/zernio.js +301 -22
  98. package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
  99. package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
  100. package/scripts/social-analytics/sync-launch-assets.js +185 -0
  101. package/scripts/social-post-hourly.js +185 -0
  102. package/scripts/social-quality-gate.js +119 -3
  103. package/scripts/social-reply-monitor.js +184 -37
  104. package/scripts/statusline-cache-path.js +27 -0
  105. package/scripts/statusline-local-stats.js +16 -0
  106. package/scripts/statusline-meta.js +22 -0
  107. package/scripts/statusline.sh +40 -33
  108. package/scripts/sync-version.js +24 -3
  109. package/scripts/test-coverage.js +21 -13
  110. package/scripts/tool-registry.js +97 -0
  111. package/scripts/train_from_feedback.py +32 -9
  112. package/scripts/validate-feedback.js +3 -2
  113. package/scripts/vector-store.js +2 -3
  114. package/scripts/verify-obsidian-setup.sh +3 -3
  115. package/src/api/server.js +281 -33
@@ -19,6 +19,14 @@ const path = require('path');
19
19
 
20
20
  const PROJECT_ROOT = path.join(__dirname, '..');
21
21
 
22
+ function explicitPinnedServeArgs(version) {
23
+ return ['--yes', '--package', `thumbgate@${version}`, 'thumbgate', 'serve'];
24
+ }
25
+
26
+ function explicitLatestServeArgs() {
27
+ return ['--yes', '--package', 'thumbgate@latest', 'thumbgate', 'serve'];
28
+ }
29
+
22
30
  function readJson(relPath) {
23
31
  return JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, relPath), 'utf-8'));
24
32
  }
@@ -191,6 +199,19 @@ function syncVersion(opts) {
191
199
  }
192
200
 
193
201
  // 8. Codex plugin manifest + MCP config
202
+ const codexAdapterConfigPath = 'adapters/codex/config.toml';
203
+ if (fs.existsSync(path.join(PROJECT_ROOT, codexAdapterConfigPath))) {
204
+ const content = fs.readFileSync(path.join(PROJECT_ROOT, codexAdapterConfigPath), 'utf8');
205
+ const updated = content.replace(/thumbgate@(\d+\.\d+\.\d+)/g, `thumbgate@${version}`);
206
+ if (updated !== content) {
207
+ drifted.push({ file: codexAdapterConfigPath, field: 'package-version-string', current: content.match(/thumbgate@\d+\.\d+\.\d+/g)?.join(', ') || null });
208
+ if (!checkOnly) {
209
+ fs.writeFileSync(path.join(PROJECT_ROOT, codexAdapterConfigPath), updated);
210
+ }
211
+ }
212
+ targets.push(codexAdapterConfigPath);
213
+ }
214
+
194
215
  const codexPluginManifestPath = 'plugins/codex-profile/.codex-plugin/plugin.json';
195
216
  if (fs.existsSync(path.join(PROJECT_ROOT, codexPluginManifestPath))) {
196
217
  const codexPlugin = readJson(codexPluginManifestPath);
@@ -211,7 +232,7 @@ function syncVersion(opts) {
211
232
  if (fs.existsSync(path.join(PROJECT_ROOT, codexPluginConfigPath))) {
212
233
  const codexPluginConfig = readJson(codexPluginConfigPath);
213
234
  const server = codexPluginConfig.mcpServers && codexPluginConfig.mcpServers.thumbgate;
214
- const expectedArgs = ['-y', `thumbgate@${version}`, 'serve'];
235
+ const expectedArgs = explicitPinnedServeArgs(version);
215
236
  const currentArgs = server && Array.isArray(server.args) ? server.args : [];
216
237
  if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
217
238
  drifted.push({ file: codexPluginConfigPath, field: 'mcpServers.thumbgate.args', current: JSON.stringify(currentArgs) });
@@ -243,7 +264,7 @@ function syncVersion(opts) {
243
264
  if (fs.existsSync(path.join(PROJECT_ROOT, claudeCodexBridgeConfigPath))) {
244
265
  const bridgeConfig = readJson(claudeCodexBridgeConfigPath);
245
266
  const server = bridgeConfig.mcpServers && bridgeConfig.mcpServers.thumbgate;
246
- const expectedArgs = ['-y', `thumbgate@${version}`, 'serve'];
267
+ const expectedArgs = explicitPinnedServeArgs(version);
247
268
  const currentArgs = server && Array.isArray(server.args) ? server.args : [];
248
269
  if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
249
270
  drifted.push({ file: claudeCodexBridgeConfigPath, field: 'mcpServers.thumbgate.args', current: JSON.stringify(currentArgs) });
@@ -260,7 +281,7 @@ function syncVersion(opts) {
260
281
  if (fs.existsSync(path.join(PROJECT_ROOT, cursorPluginConfigPath))) {
261
282
  const cursorPluginConfig = readJson(cursorPluginConfigPath);
262
283
  const server = cursorPluginConfig.mcpServers && cursorPluginConfig.mcpServers.thumbgate;
263
- const expectedArgs = ['-y', 'thumbgate@latest', 'serve'];
284
+ const expectedArgs = explicitLatestServeArgs();
264
285
  const currentArgs = server && Array.isArray(server.args) ? server.args : [];
265
286
  if (server && server.command === 'npx' && JSON.stringify(currentArgs) !== JSON.stringify(expectedArgs)) {
266
287
  drifted.push({ file: cursorPluginConfigPath, field: 'mcpServers.thumbgate.args', current: JSON.stringify(currentArgs) });
@@ -15,7 +15,9 @@ const COVERAGE_INCLUDE_GLOBS = [
15
15
  ];
16
16
  const COVERAGE_EXCLUDE_GLOBS = [
17
17
  'tests/**/*.js',
18
+ 'scripts/social-reply-monitor.js',
18
19
  ];
20
+ let cachedCoverageFilterSupport;
19
21
 
20
22
  function findCoverageTestFiles({
21
23
  dir = TESTS_DIR,
@@ -39,29 +41,35 @@ function findCoverageTestFiles({
39
41
  return files.sort();
40
42
  }
41
43
 
42
- function supportsCoveragePatternFlags({
43
- spawn = spawnSync,
44
- } = {}) {
44
+ function detectCoverageFilterSupport({ spawn = spawnSync } = {}) {
45
+ if (spawn === spawnSync && cachedCoverageFilterSupport !== undefined) {
46
+ return cachedCoverageFilterSupport;
47
+ }
48
+
45
49
  const result = spawn(process.execPath, ['--help'], {
46
50
  encoding: 'utf8',
47
51
  });
52
+ const helpText = `${result.stdout || ''}\n${result.stderr || ''}`;
53
+ const supported = helpText.includes('--test-coverage-include') && helpText.includes('--test-coverage-exclude');
48
54
 
49
- if (result.error) {
50
- return false;
55
+ if (spawn === spawnSync) {
56
+ cachedCoverageFilterSupport = supported;
51
57
  }
52
58
 
53
- const help = `${result.stdout || ''}\n${result.stderr || ''}`;
54
- return help.includes('--test-coverage-include') && help.includes('--test-coverage-exclude');
59
+ return supported;
55
60
  }
56
61
 
57
- function buildCoverageArgs(files, { supportsPatternFlags = true } = {}) {
62
+ function buildCoverageArgs(files, { spawn = spawnSync, supportsFilters } = {}) {
58
63
  const args = [
59
64
  '--test',
60
65
  '--test-concurrency=1',
61
66
  '--experimental-test-coverage',
62
67
  ];
63
68
 
64
- if (supportsPatternFlags) {
69
+ const useFilterFlags = supportsFilters === undefined
70
+ ? detectCoverageFilterSupport({ spawn })
71
+ : supportsFilters;
72
+ if (useFilterFlags) {
65
73
  args.push(
66
74
  ...COVERAGE_INCLUDE_GLOBS.flatMap((pattern) => ['--test-coverage-include', pattern]),
67
75
  ...COVERAGE_EXCLUDE_GLOBS.flatMap((pattern) => ['--test-coverage-exclude', pattern]),
@@ -76,17 +84,17 @@ function runCoverage({
76
84
  files = findCoverageTestFiles(),
77
85
  cwd = PROJECT_ROOT,
78
86
  spawn = spawnSync,
79
- supportsPatternFlags = supportsCoveragePatternFlags({ spawn }),
87
+ supportsFilters,
80
88
  } = {}) {
81
89
  if (files.length === 0) {
82
90
  return {
83
91
  exitCode: 1,
84
92
  error: 'No test files found for coverage run.',
85
- args: buildCoverageArgs(files, { supportsPatternFlags }),
93
+ args: buildCoverageArgs(files, { spawn, supportsFilters }),
86
94
  };
87
95
  }
88
96
 
89
- const args = buildCoverageArgs(files, { supportsPatternFlags });
97
+ const args = buildCoverageArgs(files, { spawn, supportsFilters });
90
98
  const result = spawn(process.execPath, args, {
91
99
  cwd,
92
100
  env: process.env,
@@ -113,8 +121,8 @@ module.exports = {
113
121
  COVERAGE_INCLUDE_GLOBS,
114
122
  PROJECT_ROOT,
115
123
  TESTS_DIR,
124
+ detectCoverageFilterSupport,
116
125
  findCoverageTestFiles,
117
126
  buildCoverageArgs,
118
127
  runCoverage,
119
- supportsCoveragePatternFlags,
120
128
  };
@@ -512,6 +512,89 @@ const TOOLS = [
512
512
  },
513
513
  },
514
514
  }),
515
+ destructiveTool({
516
+ name: 'set_task_scope',
517
+ description: 'Declare or clear the current task scope so ThumbGate can compare affected files and diffs against the approved path set.',
518
+ inputSchema: {
519
+ type: 'object',
520
+ properties: {
521
+ taskId: { type: 'string', description: 'Optional stable task identifier (ticket, issue, or work item id)' },
522
+ summary: { type: 'string', description: 'Short summary of the task being worked' },
523
+ allowedPaths: {
524
+ type: 'array',
525
+ items: { type: 'string' },
526
+ description: 'Glob patterns that define the allowed file scope for this task',
527
+ },
528
+ protectedPaths: {
529
+ type: 'array',
530
+ items: { type: 'string' },
531
+ description: 'Optional protected-file globs that require explicit approval before editing or publishing',
532
+ },
533
+ repoPath: { type: 'string', description: 'Optional repo root used when evaluating git diff scope' },
534
+ localOnly: { type: 'boolean', description: 'When true, also marks the task as local-only' },
535
+ clear: { type: 'boolean', description: 'Clear the current task scope instead of setting one' },
536
+ },
537
+ },
538
+ }),
539
+ readOnlyTool({
540
+ name: 'get_scope_state',
541
+ description: 'Return the active task scope and any unexpired protected-file approvals.',
542
+ inputSchema: {
543
+ type: 'object',
544
+ properties: {},
545
+ },
546
+ }),
547
+ destructiveTool({
548
+ name: 'set_branch_governance',
549
+ description: 'Declare or clear branch and release governance so PR, merge, release, and publish actions can be evaluated against explicit workflow state.',
550
+ inputSchema: {
551
+ type: 'object',
552
+ properties: {
553
+ branchName: { type: 'string', description: 'Optional branch name the governance applies to' },
554
+ baseBranch: { type: 'string', description: 'Protected base branch for merge and release operations (defaults to main)' },
555
+ prRequired: { type: 'boolean', description: 'Whether this lane must go through a pull request (defaults to true)' },
556
+ prNumber: { type: 'string', description: 'Optional pull request number once a PR exists' },
557
+ prUrl: { type: 'string', description: 'Optional pull request URL once a PR exists' },
558
+ queueRequired: { type: 'boolean', description: 'Whether the target branch requires a merge queue' },
559
+ localOnly: { type: 'boolean', description: 'When true, PR, merge, release, and publish actions are blocked for this lane' },
560
+ releaseVersion: { type: 'string', description: 'Expected package version for release or publish actions' },
561
+ releaseEvidence: { type: 'string', description: 'Optional evidence or release plan note for the governed version' },
562
+ releaseSensitiveGlobs: {
563
+ type: 'array',
564
+ items: { type: 'string' },
565
+ description: 'Optional custom globs that define release-sensitive files for this branch lane',
566
+ },
567
+ clear: { type: 'boolean', description: 'Clear the current branch governance state instead of setting it' },
568
+ },
569
+ },
570
+ }),
571
+ readOnlyTool({
572
+ name: 'get_branch_governance',
573
+ description: 'Return the active branch and release governance state.',
574
+ inputSchema: {
575
+ type: 'object',
576
+ properties: {},
577
+ },
578
+ }),
579
+ destructiveTool({
580
+ name: 'approve_protected_action',
581
+ description: 'Grant a time-limited approval for edits or publish actions that touch protected files.',
582
+ inputSchema: {
583
+ type: 'object',
584
+ required: ['pathGlobs', 'reason'],
585
+ properties: {
586
+ pathGlobs: {
587
+ type: 'array',
588
+ items: { type: 'string' },
589
+ description: 'Protected-file globs covered by this approval',
590
+ },
591
+ reason: { type: 'string', description: 'Why this protected-file action is approved' },
592
+ evidence: { type: 'string', description: 'Optional supporting evidence or approval note' },
593
+ taskId: { type: 'string', description: 'Optional task id this approval is tied to' },
594
+ ttlMs: { type: 'number', description: 'Optional approval lifetime in milliseconds (defaults to 1 hour, max 24 hours)' },
595
+ },
596
+ },
597
+ }),
515
598
  destructiveTool({
516
599
  name: 'track_action',
517
600
  description: 'Record a verification action in the current session (for example figma_verified or tests_passed). Session actions expire after one hour.',
@@ -535,6 +618,20 @@ const TOOLS = [
535
618
  },
536
619
  },
537
620
  }),
621
+ readOnlyTool({
622
+ name: 'check_operational_integrity',
623
+ description: 'Evaluate whether the current repo state is safe for PR, merge, release, and publish operations.',
624
+ inputSchema: {
625
+ type: 'object',
626
+ properties: {
627
+ repoPath: { type: 'string', description: 'Optional repository path to inspect' },
628
+ baseBranch: { type: 'string', description: 'Protected base branch to compare against (defaults to main)' },
629
+ command: { type: 'string', description: 'Optional git, PR, or publish command to evaluate against the current governance state' },
630
+ requirePrForReleaseSensitive: { type: 'boolean', description: 'When true, release-sensitive changes on non-base branches require an open PR' },
631
+ requireVersionNotBehindBase: { type: 'boolean', description: 'When true, release-sensitive changes cannot lag behind the base branch package version' },
632
+ },
633
+ },
634
+ }),
538
635
  destructiveTool({
539
636
  name: 'register_claim_gate',
540
637
  description: 'Register a custom claim verification rule in local runtime state without editing tracked repo config.',
@@ -15,7 +15,7 @@ Usage:
15
15
  python train_from_feedback.py --dpo-train # DPO batch optimization (Feb 2026)
16
16
  python train_from_feedback.py --config config.json # Use custom categories
17
17
 
18
- This script only reads and writes local feedback artifacts under .claude/memory/feedback.
18
+ This script only reads and writes local feedback artifacts under the active ThumbGate feedback directory.
19
19
  Those runtime outputs are git-ignored even though this utility is intentionally versioned.
20
20
  """
21
21
 
@@ -23,15 +23,37 @@ import json
23
23
  import math
24
24
  import random
25
25
  import argparse
26
+ import os
26
27
  from datetime import datetime
27
28
  from pathlib import Path
28
29
  from typing import Dict, List, Any, Optional, Tuple
29
30
 
30
31
  # Configuration
31
32
  PROJECT_ROOT = Path(__file__).parent.parent
32
- FEEDBACK_LOG = PROJECT_ROOT / ".claude" / "memory" / "feedback" / "feedback-log.jsonl"
33
- MODEL_FILE = PROJECT_ROOT / ".claude" / "memory" / "feedback" / "feedback_model.json"
34
- SNAPSHOTS_DIR = PROJECT_ROOT / ".claude" / "memory" / "feedback" / "model_snapshots"
33
+
34
+ def resolve_feedback_dir() -> Path:
35
+ env_dir = os.environ.get("THUMBGATE_FEEDBACK_DIR")
36
+ if env_dir:
37
+ return Path(env_dir)
38
+
39
+ local_thumbgate = PROJECT_ROOT / ".thumbgate"
40
+ if local_thumbgate.exists():
41
+ return local_thumbgate
42
+
43
+ local_rlhf = PROJECT_ROOT / ".rlhf"
44
+ if local_rlhf.exists():
45
+ return local_rlhf
46
+
47
+ local_legacy = PROJECT_ROOT / ".claude" / "memory" / "feedback"
48
+ if local_legacy.exists():
49
+ return local_legacy
50
+
51
+ return Path.home() / ".thumbgate" / "projects" / PROJECT_ROOT.name
52
+
53
+ FEEDBACK_DIR = resolve_feedback_dir()
54
+ FEEDBACK_LOG = FEEDBACK_DIR / "feedback-log.jsonl"
55
+ MODEL_FILE = FEEDBACK_DIR / "feedback_model.json"
56
+ SNAPSHOTS_DIR = FEEDBACK_DIR / "model_snapshots"
35
57
 
36
58
  # Default categories (overridden by --config)
37
59
  DEFAULT_CATEGORIES = {
@@ -132,10 +154,11 @@ def create_initial_model(categories: Dict) -> Dict:
132
154
 
133
155
  def save_model(model: Dict):
134
156
  """Save model to disk."""
135
- # Resolve and verify path stays within project root (CodeQL S2083)
157
+ # Resolve and verify path stays within trusted local ThumbGate roots (CodeQL S2083)
136
158
  resolved = MODEL_FILE.resolve()
137
- if not str(resolved).startswith(str(PROJECT_ROOT.resolve())):
138
- raise ValueError(f"Model path escapes project root: {resolved}")
159
+ allowed_roots = [PROJECT_ROOT.resolve(), FEEDBACK_DIR.resolve()]
160
+ if not any(str(resolved).startswith(str(root)) for root in allowed_roots):
161
+ raise ValueError(f"Model path escapes allowed ThumbGate roots: {resolved}")
139
162
  resolved.parent.mkdir(parents=True, exist_ok=True)
140
163
  model["updated"] = datetime.now().isoformat()
141
164
  resolved.write_text(json.dumps(model, indent=2))
@@ -344,7 +367,7 @@ def save_snapshot(model: Dict) -> Path:
344
367
  # Based on: Meta-Policy Reflexion (arXiv:2509.03990)
345
368
  # ============================================
346
369
 
347
- META_POLICY_FILE = PROJECT_ROOT / ".claude" / "memory" / "feedback" / "meta_policy_rules.json"
370
+ META_POLICY_FILE = FEEDBACK_DIR / "meta_policy_rules.json"
348
371
 
349
372
 
350
373
  def extract_meta_policy_rules(min_occurrences: int = 3) -> List[Dict[str, Any]]:
@@ -508,7 +531,7 @@ def load_meta_policy_rules() -> List[Dict[str, Any]]:
508
531
  # Reference: Rafailov et al. 2023 (arXiv:2305.18290)
509
532
  # ============================================
510
533
 
511
- DPO_MODEL_FILE = PROJECT_ROOT / ".claude" / "memory" / "feedback" / "dpo_model.json"
534
+ DPO_MODEL_FILE = FEEDBACK_DIR / "dpo_model.json"
512
535
  DPO_BETA = 0.1 # Temperature parameter (lower = more aggressive preference following)
513
536
 
514
537
 
@@ -26,15 +26,16 @@
26
26
 
27
27
  const fs = require('fs');
28
28
  const path = require('path');
29
+ const { resolveFeedbackDir } = require('./feedback-paths');
29
30
 
30
31
  // =============================================================================
31
32
  // PATH RESOLUTION
32
33
  // =============================================================================
33
34
 
34
- const DEFAULT_FEEDBACK_DIR = path.join(__dirname, '..', '.claude', 'memory', 'feedback');
35
+ const DEFAULT_FEEDBACK_DIR = resolveFeedbackDir();
35
36
 
36
37
  function getFeedbackDir() {
37
- return process.env.THUMBGATE_FEEDBACK_DIR || DEFAULT_FEEDBACK_DIR;
38
+ return resolveFeedbackDir();
38
39
  }
39
40
 
40
41
  function getFeedbackPaths() {
@@ -8,8 +8,7 @@ const {
8
8
  resolveFeedbackDir,
9
9
  } = require('./local-model-profile');
10
10
 
11
- const PROJECT_ROOT = path.join(__dirname, '..');
12
- const DEFAULT_FEEDBACK_DIR = path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
11
+ const DEFAULT_FEEDBACK_DIR = resolveFeedbackDir();
13
12
  const DEFAULT_LANCE_DIR = path.join(DEFAULT_FEEDBACK_DIR, 'lancedb');
14
13
 
15
14
  // Module-level cache — prevents re-importing on every upsertFeedback() call
@@ -29,7 +28,7 @@ async function getLanceDB() {
29
28
  }
30
29
 
31
30
  function getFeedbackDir() {
32
- return resolveFeedbackDir(process.env.THUMBGATE_FEEDBACK_DIR || DEFAULT_FEEDBACK_DIR);
31
+ return resolveFeedbackDir();
33
32
  }
34
33
 
35
34
  function getLanceDir() {
@@ -134,9 +134,9 @@ check_path_documented() {
134
134
  fi
135
135
  }
136
136
 
137
- check_path_documented ".claude/memory/feedback/memory-log.jsonl" "memory-log.jsonl"
138
- check_path_documented ".claude/memory/feedback/prevention-rules.md" "prevention-rules.md"
139
- check_path_documented ".claude/memory/feedback/feedback-log.jsonl" "feedback-log.jsonl"
137
+ check_path_documented ".thumbgate/memory-log.jsonl" "memory-log.jsonl"
138
+ check_path_documented ".thumbgate/prevention-rules.md" "prevention-rules.md"
139
+ check_path_documented ".thumbgate/feedback-log.jsonl" "feedback-log.jsonl"
140
140
 
141
141
  # primer.md must exist (it is committed)
142
142
  if [ -f "$REPO_ROOT/primer.md" ]; then