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.
- package/.claude-plugin/README.md +2 -2
- package/.claude-plugin/marketplace.json +4 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +115 -312
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +4 -4
- package/adapters/mcp/server-stdio.js +61 -1
- package/adapters/opencode/opencode.json +4 -2
- package/bin/cli.js +156 -8
- package/bin/memory.sh +3 -3
- package/config/e2e-critical-flows.json +4 -0
- package/config/gates/default.json +74 -2
- package/config/github-about.json +1 -1
- package/config/mcp-allowlists.json +27 -0
- package/package.json +22 -5
- package/plugins/amp-skill/INSTALL.md +1 -0
- package/plugins/amp-skill/SKILL.md +1 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +4 -2
- package/plugins/claude-skill/INSTALL.md +1 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +4 -2
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +3 -3
- package/plugins/cursor-marketplace/mcp.json +3 -1
- package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
- package/plugins/gemini-extension/INSTALL.md +3 -3
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/dashboard.html +15 -8
- package/public/index.html +125 -185
- package/public/js/buyer-intent.js +252 -0
- package/public/pro.html +1085 -0
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/adk-consolidator.js +14 -2
- package/scripts/agent-readiness.js +3 -1
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/auto-promote-gates.js +2 -0
- package/scripts/auto-wire-hooks.js +105 -17
- package/scripts/behavioral-extraction.js +2 -6
- package/scripts/billing.js +107 -3
- package/scripts/budget-guard.js +2 -2
- package/scripts/build-metadata.js +14 -0
- package/scripts/context-engine.js +1 -0
- package/scripts/deploy-policy.js +3 -17
- package/scripts/dpo-optimizer.js +3 -6
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/export-dpo-pairs.js +2 -3
- package/scripts/export-kto-pairs.js +3 -4
- package/scripts/export-training.js +8 -6
- package/scripts/feedback-attribution.js +23 -11
- package/scripts/feedback-loop.js +40 -2
- package/scripts/feedback-to-rules.js +2 -1
- package/scripts/filesystem-search.js +3 -2
- package/scripts/gates-engine.js +760 -29
- package/scripts/generate-pretool-hook.sh +0 -0
- package/scripts/gtm-revenue-loop.js +20 -1
- package/scripts/hook-auto-capture.sh +8 -3
- package/scripts/hook-runtime.js +81 -0
- package/scripts/hook-stop-self-score.sh +3 -3
- package/scripts/hook-thumbgate-cache-updater.js +99 -38
- package/scripts/hosted-config.js +4 -16
- package/scripts/hybrid-feedback-context.js +54 -14
- package/scripts/install-mcp.js +13 -3
- package/scripts/intent-router.js +2 -2
- package/scripts/license.js +52 -14
- package/scripts/local-model-profile.js +3 -2
- package/scripts/mcp-config.js +62 -7
- package/scripts/meta-policy.js +4 -8
- package/scripts/money-watcher.js +166 -16
- package/scripts/obsidian-export.js +1 -0
- package/scripts/operational-integrity.js +480 -0
- package/scripts/post-everywhere.js +35 -12
- package/scripts/pr-manager.js +14 -11
- package/scripts/profile-router.js +2 -0
- package/scripts/prompt-dlp.js +1 -0
- package/scripts/publish-decision.js +10 -0
- package/scripts/published-cli.js +61 -0
- package/scripts/risk-scorer.js +3 -2
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/skill-quality-tracker.js +3 -5
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
- package/scripts/social-analytics/engagement-audit.js +202 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +45 -7
- package/scripts/social-analytics/install-growth-automation.js +114 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/poll-all.js +23 -23
- package/scripts/social-analytics/pollers/plausible.js +2 -4
- package/scripts/social-analytics/pollers/zernio.js +3 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +22 -3
- package/scripts/social-analytics/publish-thumbgate-launch.js +322 -0
- package/scripts/social-analytics/publishers/reddit.js +7 -12
- package/scripts/social-analytics/publishers/zernio.js +301 -22
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
- package/scripts/social-analytics/sync-launch-assets.js +185 -0
- package/scripts/social-post-hourly.js +185 -0
- package/scripts/social-quality-gate.js +119 -3
- package/scripts/social-reply-monitor.js +184 -37
- package/scripts/statusline-cache-path.js +27 -0
- package/scripts/statusline-local-stats.js +16 -0
- package/scripts/statusline-meta.js +22 -0
- package/scripts/statusline.sh +40 -33
- package/scripts/sync-version.js +24 -3
- package/scripts/test-coverage.js +21 -13
- package/scripts/tool-registry.js +97 -0
- package/scripts/train_from_feedback.py +32 -9
- package/scripts/validate-feedback.js +3 -2
- package/scripts/vector-store.js +2 -3
- package/scripts/verify-obsidian-setup.sh +3 -3
- package/src/api/server.js +281 -33
package/scripts/sync-version.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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) });
|
package/scripts/test-coverage.js
CHANGED
|
@@ -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
|
|
43
|
-
spawn
|
|
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 (
|
|
50
|
-
|
|
55
|
+
if (spawn === spawnSync) {
|
|
56
|
+
cachedCoverageFilterSupport = supported;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
return help.includes('--test-coverage-include') && help.includes('--test-coverage-exclude');
|
|
59
|
+
return supported;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
function buildCoverageArgs(files, {
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
93
|
+
args: buildCoverageArgs(files, { spawn, supportsFilters }),
|
|
86
94
|
};
|
|
87
95
|
}
|
|
88
96
|
|
|
89
|
-
const args = buildCoverageArgs(files, {
|
|
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
|
};
|
package/scripts/tool-registry.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
157
|
+
# Resolve and verify path stays within trusted local ThumbGate roots (CodeQL S2083)
|
|
136
158
|
resolved = MODEL_FILE.resolve()
|
|
137
|
-
|
|
138
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
35
|
+
const DEFAULT_FEEDBACK_DIR = resolveFeedbackDir();
|
|
35
36
|
|
|
36
37
|
function getFeedbackDir() {
|
|
37
|
-
return
|
|
38
|
+
return resolveFeedbackDir();
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
function getFeedbackPaths() {
|
package/scripts/vector-store.js
CHANGED
|
@@ -8,8 +8,7 @@ const {
|
|
|
8
8
|
resolveFeedbackDir,
|
|
9
9
|
} = require('./local-model-profile');
|
|
10
10
|
|
|
11
|
-
const
|
|
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(
|
|
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 ".
|
|
138
|
-
check_path_documented ".
|
|
139
|
-
check_path_documented ".
|
|
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
|