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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const REPO_ROOT = path.resolve(process.argv[2] || process.cwd());
|
|
8
|
+
const RLHF_ENTRY = {
|
|
9
|
+
command: 'npx',
|
|
10
|
+
args: ['-y', 'thumbgate@latest', 'serve'],
|
|
11
|
+
};
|
|
12
|
+
const LEGACY_SERVER_NAMES = ['thumbgate', 'rlhf_feedback_loop'];
|
|
13
|
+
const INFO_EXCLUDE_ENTRIES = ['.rlhf/', '.thumbgate/', '.mcp.json'];
|
|
14
|
+
|
|
15
|
+
function readJson(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeJsonIfChanged(filePath, value) {
|
|
24
|
+
const next = JSON.stringify(value, null, 2) + '\n';
|
|
25
|
+
let current = null;
|
|
26
|
+
try {
|
|
27
|
+
current = fs.readFileSync(filePath, 'utf8');
|
|
28
|
+
} catch {
|
|
29
|
+
current = null;
|
|
30
|
+
}
|
|
31
|
+
if (current === next) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
35
|
+
fs.writeFileSync(filePath, next);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function mergeRlhfEntry(entry = {}) {
|
|
40
|
+
return {
|
|
41
|
+
...entry,
|
|
42
|
+
command: RLHF_ENTRY.command,
|
|
43
|
+
args: RLHF_ENTRY.args.slice(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureMcpJson(repoRoot) {
|
|
48
|
+
const filePath = path.join(repoRoot, '.mcp.json');
|
|
49
|
+
const existing = readJson(filePath);
|
|
50
|
+
const config = existing && typeof existing === 'object' ? existing : {};
|
|
51
|
+
config.mcpServers = config.mcpServers && typeof config.mcpServers === 'object' ? config.mcpServers : {};
|
|
52
|
+
config.mcpServers.rlhf = mergeRlhfEntry(config.mcpServers.rlhf);
|
|
53
|
+
for (const legacyName of LEGACY_SERVER_NAMES) {
|
|
54
|
+
delete config.mcpServers[legacyName];
|
|
55
|
+
}
|
|
56
|
+
return writeJsonIfChanged(filePath, config);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ensureClaudeSettings(repoRoot) {
|
|
60
|
+
const filePath = path.join(repoRoot, '.claude', 'settings.json');
|
|
61
|
+
const existing = readJson(filePath);
|
|
62
|
+
if (!existing || typeof existing !== 'object') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const hasRelevantServer =
|
|
66
|
+
Boolean(existing.mcpServers && existing.mcpServers.rlhf) ||
|
|
67
|
+
LEGACY_SERVER_NAMES.some((name) => Boolean(existing.mcpServers && existing.mcpServers[name]));
|
|
68
|
+
if (!hasRelevantServer) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
existing.mcpServers = existing.mcpServers && typeof existing.mcpServers === 'object' ? existing.mcpServers : {};
|
|
72
|
+
existing.mcpServers.rlhf = mergeRlhfEntry(existing.mcpServers.rlhf);
|
|
73
|
+
for (const legacyName of LEGACY_SERVER_NAMES) {
|
|
74
|
+
delete existing.mcpServers[legacyName];
|
|
75
|
+
}
|
|
76
|
+
return writeJsonIfChanged(filePath, existing);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function ensureInfoExclude(repoRoot) {
|
|
80
|
+
const excludePath = path.join(repoRoot, '.git', 'info', 'exclude');
|
|
81
|
+
let current = '';
|
|
82
|
+
try {
|
|
83
|
+
current = fs.readFileSync(excludePath, 'utf8');
|
|
84
|
+
} catch {
|
|
85
|
+
current = '';
|
|
86
|
+
}
|
|
87
|
+
const lines = new Set(
|
|
88
|
+
current
|
|
89
|
+
.split(/\r?\n/)
|
|
90
|
+
.map((line) => line.trim())
|
|
91
|
+
.filter(Boolean)
|
|
92
|
+
);
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (const entry of INFO_EXCLUDE_ENTRIES) {
|
|
95
|
+
if (!lines.has(entry)) {
|
|
96
|
+
lines.add(entry);
|
|
97
|
+
changed = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!changed) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const next = `${Array.from(lines).sort().join('\n')}\n`;
|
|
104
|
+
fs.mkdirSync(path.dirname(excludePath), { recursive: true });
|
|
105
|
+
fs.writeFileSync(excludePath, next);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ensureRlhfDir(repoRoot) {
|
|
110
|
+
const rlhfDir = path.join(repoRoot, '.rlhf');
|
|
111
|
+
if (fs.existsSync(rlhfDir)) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
fs.mkdirSync(rlhfDir, { recursive: true });
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function main() {
|
|
119
|
+
const results = {
|
|
120
|
+
repoRoot: REPO_ROOT,
|
|
121
|
+
createdRlhfDir: ensureRlhfDir(REPO_ROOT),
|
|
122
|
+
updatedMcpJson: ensureMcpJson(REPO_ROOT),
|
|
123
|
+
updatedClaudeSettings: ensureClaudeSettings(REPO_ROOT),
|
|
124
|
+
updatedInfoExclude: ensureInfoExclude(REPO_ROOT),
|
|
125
|
+
};
|
|
126
|
+
process.stdout.write(`${JSON.stringify(results)}\n`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main();
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { traceForDpoPair, aggregateTraces } = require('./code-reasoning');
|
|
11
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
-
const FEEDBACK_DIR = process.env.THUMBGATE_FEEDBACK_DIR || path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
|
|
14
|
-
const DEFAULT_LOCAL_MEMORY_LOG = path.join(FEEDBACK_DIR, 'memory-log.jsonl');
|
|
13
|
+
const DEFAULT_LOCAL_MEMORY_LOG = path.join(resolveFeedbackDir(), 'memory-log.jsonl');
|
|
15
14
|
|
|
16
15
|
function readJSONL(filePath) {
|
|
17
16
|
if (!fs.existsSync(filePath)) return [];
|
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const DEFAULT_FEEDBACK_LOG = path.join(FEEDBACK_DIR, 'feedback-log.jsonl');
|
|
19
|
-
const DEFAULT_MEMORY_LOG = path.join(FEEDBACK_DIR, 'memory-log.jsonl');
|
|
17
|
+
const DEFAULT_FEEDBACK_LOG = path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
|
|
18
|
+
const DEFAULT_MEMORY_LOG = path.join(resolveFeedbackDir(), 'memory-log.jsonl');
|
|
20
19
|
|
|
21
20
|
function readJSONL(filePath) {
|
|
22
21
|
if (!fs.existsSync(filePath)) return [];
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
19
20
|
|
|
20
|
-
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
21
|
-
const FEEDBACK_DIR = process.env.THUMBGATE_FEEDBACK_DIR
|
|
22
|
-
|| path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
|
|
23
21
|
const SEQUENCE_WINDOW = 10;
|
|
24
22
|
|
|
23
|
+
function getFeedbackDir() {
|
|
24
|
+
return resolveFeedbackDir();
|
|
25
|
+
}
|
|
26
|
+
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
26
28
|
// Utility helpers
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
@@ -173,7 +175,7 @@ function buildPreferencePairs(feedbackEntries) {
|
|
|
173
175
|
* @returns {{ outputPath: string, pairCount: number, sequenceCount: number }}
|
|
174
176
|
*/
|
|
175
177
|
function exportPyTorchJSON(feedbackDir, outputPath) {
|
|
176
|
-
const fbDir = feedbackDir ||
|
|
178
|
+
const fbDir = feedbackDir || getFeedbackDir();
|
|
177
179
|
const feedbackPath = path.join(fbDir, 'feedback-log.jsonl');
|
|
178
180
|
const sequencePath = path.join(fbDir, 'feedback-sequences.jsonl');
|
|
179
181
|
const exportDir = path.join(fbDir, 'training-data');
|
|
@@ -246,7 +248,7 @@ function escapeCsvField(value) {
|
|
|
246
248
|
* @returns {{ outputPath: string, rowCount: number }}
|
|
247
249
|
*/
|
|
248
250
|
function exportCSV(feedbackDir, outputPath) {
|
|
249
|
-
const fbDir = feedbackDir ||
|
|
251
|
+
const fbDir = feedbackDir || getFeedbackDir();
|
|
250
252
|
const feedbackPath = path.join(fbDir, 'feedback-log.jsonl');
|
|
251
253
|
const exportDir = path.join(fbDir, 'training-data');
|
|
252
254
|
|
|
@@ -288,7 +290,7 @@ function exportCSV(feedbackDir, outputPath) {
|
|
|
288
290
|
* @returns {{ outputPath: string, report: object }}
|
|
289
291
|
*/
|
|
290
292
|
function exportActionAnalysis(feedbackDir, outputPath) {
|
|
291
|
-
const fbDir = feedbackDir ||
|
|
293
|
+
const fbDir = feedbackDir || getFeedbackDir();
|
|
292
294
|
const feedbackPath = path.join(fbDir, 'feedback-log.jsonl');
|
|
293
295
|
const sequencePath = path.join(fbDir, 'feedback-sequences.jsonl');
|
|
294
296
|
const exportDir = path.join(fbDir, 'training-data');
|
|
@@ -3,14 +3,24 @@
|
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
7
|
+
|
|
8
|
+
function getAttributionPaths(options = {}) {
|
|
9
|
+
const feedbackDir = resolveFeedbackDir({
|
|
10
|
+
cwd: options.cwd,
|
|
11
|
+
env: options.env,
|
|
12
|
+
feedbackDir: options.feedbackDir,
|
|
13
|
+
home: options.home,
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
feedbackDir,
|
|
17
|
+
actionLog: path.join(feedbackDir, 'action-log.jsonl'),
|
|
18
|
+
attributions: path.join(feedbackDir, 'feedback-attributions.jsonl'),
|
|
19
|
+
attributedFeedback: path.join(feedbackDir, 'attributed-feedback.jsonl'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
6
22
|
|
|
7
|
-
|
|
8
|
-
const ROOT = path.join(__dirname, '..');
|
|
9
|
-
const PATHS = {
|
|
10
|
-
actionLog: path.join(ROOT, '.claude', 'memory', 'feedback', 'action-log.jsonl'),
|
|
11
|
-
attributions: path.join(ROOT, '.claude', 'memory', 'feedback', 'feedback-attributions.jsonl'),
|
|
12
|
-
attributedFeedback: path.join(ROOT, '.claude', 'memory', 'feedback', 'attributed-feedback.jsonl'),
|
|
13
|
-
};
|
|
23
|
+
const PATHS = getAttributionPaths();
|
|
14
24
|
|
|
15
25
|
const STOPWORDS = new Set([
|
|
16
26
|
'about', 'after', 'again', 'allow', 'already', 'always', 'because', 'before', 'being', 'between',
|
|
@@ -155,7 +165,7 @@ function scoreCandidate(action, feedbackNormalized, feedbackTokens, nowMs) {
|
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
function recordAction(toolName, toolInput, opts = {}) {
|
|
158
|
-
const actionLogPath = opts.actionLogPath || process.env.THUMBGATE_ACTION_LOG ||
|
|
168
|
+
const actionLogPath = opts.actionLogPath || process.env.THUMBGATE_ACTION_LOG || getAttributionPaths(opts).actionLog;
|
|
159
169
|
const tool = String(toolName || 'unknown');
|
|
160
170
|
const inputSummary = summarizeToolInput(tool, toolInput);
|
|
161
171
|
const normalized = normalize(inputSummary);
|
|
@@ -175,9 +185,10 @@ function recordAction(toolName, toolInput, opts = {}) {
|
|
|
175
185
|
|
|
176
186
|
function attributeFeedback(signal, feedbackContext, opts = {}) {
|
|
177
187
|
const sig = String(signal || '').toLowerCase().trim();
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
const
|
|
188
|
+
const paths = getAttributionPaths(opts);
|
|
189
|
+
const actionLogPath = opts.actionLogPath || process.env.THUMBGATE_ACTION_LOG || paths.actionLog;
|
|
190
|
+
const attributionsPath = opts.attributionsPath || process.env.THUMBGATE_FEEDBACK_ATTRIBUTIONS || paths.attributions;
|
|
191
|
+
const attributedFeedbackPath = opts.attributedFeedbackPath || process.env.THUMBGATE_ATTRIBUTED_FEEDBACK || paths.attributedFeedback;
|
|
181
192
|
|
|
182
193
|
if (sig !== 'negative' && sig !== 'positive') {
|
|
183
194
|
return { ok: true, skipped: true, reason: 'signal_not_supported' };
|
|
@@ -298,6 +309,7 @@ if (require.main === module) {
|
|
|
298
309
|
|
|
299
310
|
module.exports = {
|
|
300
311
|
PATHS,
|
|
312
|
+
getAttributionPaths,
|
|
301
313
|
readJsonl,
|
|
302
314
|
appendJsonl,
|
|
303
315
|
normalize,
|
package/scripts/feedback-loop.js
CHANGED
|
@@ -105,8 +105,7 @@ const pendingBackgroundSideEffects = new Set();
|
|
|
105
105
|
*/
|
|
106
106
|
function updateStatuslineWithLesson({ accepted, signal, memoryId, feedbackId, lesson, turnCount }) {
|
|
107
107
|
try {
|
|
108
|
-
const
|
|
109
|
-
const cachePath = path.join(cacheDir, '.thumbgate', 'statusline_cache.json');
|
|
108
|
+
const cachePath = path.join(getFeedbackPaths().FEEDBACK_DIR, 'statusline_cache.json');
|
|
110
109
|
let cache = {};
|
|
111
110
|
try {
|
|
112
111
|
cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
@@ -138,6 +137,12 @@ function updateStatuslineWithLesson({ accepted, signal, memoryId, feedbackId, le
|
|
|
138
137
|
cache.updated_at = String(Math.floor(Date.now() / 1000));
|
|
139
138
|
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
140
139
|
fs.writeFileSync(cachePath, JSON.stringify(cache));
|
|
140
|
+
try {
|
|
141
|
+
const { refreshStatuslineCache } = require('./hook-thumbgate-cache-updater');
|
|
142
|
+
refreshStatuslineCache(analyzeFeedback(), cachePath);
|
|
143
|
+
} catch {
|
|
144
|
+
/* keep lesson refresh best-effort */
|
|
145
|
+
}
|
|
141
146
|
} catch { /* statusline update is best-effort */ }
|
|
142
147
|
}
|
|
143
148
|
|
|
@@ -615,6 +620,35 @@ function enrichFeedbackContext(feedbackEvent, params) {
|
|
|
615
620
|
? params.filePaths.split(',').map((f) => f.trim()).filter(Boolean)
|
|
616
621
|
: [];
|
|
617
622
|
const errorType = params.errorType || null;
|
|
623
|
+
const protectedFiles = filePaths.filter((filePath) => /(^|\/)(agents\.md|claude(\.local)?\.md|gemini\.md|readme\.md|\.gitignore|skill\.md)$|^\.husky\/|^config\/gates\//i.test(filePath));
|
|
624
|
+
const combinedText = [
|
|
625
|
+
feedbackEvent.context || '',
|
|
626
|
+
feedbackEvent.whatWentWrong || '',
|
|
627
|
+
feedbackEvent.whatToChange || '',
|
|
628
|
+
...(Array.isArray(feedbackEvent.tags) ? feedbackEvent.tags : []),
|
|
629
|
+
].join(' ').toLowerCase();
|
|
630
|
+
const includesPhrase = (phrase) => combinedText.includes(phrase);
|
|
631
|
+
const includesOrderedTerms = (firstTerm, secondTerm) => {
|
|
632
|
+
const firstIndex = combinedText.indexOf(firstTerm);
|
|
633
|
+
if (firstIndex === -1) return false;
|
|
634
|
+
return combinedText.indexOf(secondTerm, firstIndex + firstTerm.length) !== -1;
|
|
635
|
+
};
|
|
636
|
+
const enforcement = {
|
|
637
|
+
scopeViolation: includesPhrase('scope creep')
|
|
638
|
+
|| includesPhrase('out of scope')
|
|
639
|
+
|| includesOrderedTerms('outside', 'scope')
|
|
640
|
+
|| includesPhrase('wrong files')
|
|
641
|
+
|| includesPhrase('unrelated files'),
|
|
642
|
+
approvalFailure: includesPhrase('without approval')
|
|
643
|
+
|| includesPhrase('missing approval')
|
|
644
|
+
|| includesPhrase('approval required')
|
|
645
|
+
|| includesPhrase('permission required'),
|
|
646
|
+
protectedFileViolation: protectedFiles.length > 0
|
|
647
|
+
|| includesPhrase('protected file')
|
|
648
|
+
|| includesPhrase('policy file')
|
|
649
|
+
|| includesPhrase('hook file'),
|
|
650
|
+
protectedFiles,
|
|
651
|
+
};
|
|
618
652
|
|
|
619
653
|
return {
|
|
620
654
|
...feedbackEvent,
|
|
@@ -623,6 +657,7 @@ function enrichFeedbackContext(feedbackEvent, params) {
|
|
|
623
657
|
filePaths,
|
|
624
658
|
errorType,
|
|
625
659
|
outcomeCategory,
|
|
660
|
+
enforcement,
|
|
626
661
|
},
|
|
627
662
|
};
|
|
628
663
|
} catch (_err) {
|
|
@@ -812,6 +847,9 @@ function inferLessonFromConversation(conversationWindow, signal) {
|
|
|
812
847
|
const tags = [];
|
|
813
848
|
if (filePaths.length > 0) tags.push('has-file-context');
|
|
814
849
|
if (errorPatterns.length > 0) tags.push('has-error-context');
|
|
850
|
+
if (filePaths.some((filePath) => /(^|\/)(agents\.md|claude(\.local)?\.md|gemini\.md|readme\.md|\.gitignore|skill\.md)$|^\.husky\/|^config\/gates\//i.test(filePath))) {
|
|
851
|
+
tags.push('protected-file-context');
|
|
852
|
+
}
|
|
815
853
|
|
|
816
854
|
return {
|
|
817
855
|
lesson,
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getAutoGatesPath } = require('./auto-promote-gates');
|
|
6
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
6
7
|
|
|
7
|
-
const DEFAULT_LOG = path.join(
|
|
8
|
+
const DEFAULT_LOG = path.join(resolveFeedbackDir(), 'feedback-log.jsonl');
|
|
8
9
|
const NEG = new Set(['negative', 'negative_strong', 'down', 'thumbs_down']);
|
|
9
10
|
const POS = new Set(['positive', 'positive_strong', 'up', 'thumbs_up']);
|
|
10
11
|
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
const fs = require('node:fs');
|
|
20
20
|
const path = require('node:path');
|
|
21
21
|
const crypto = require('node:crypto');
|
|
22
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
22
23
|
|
|
23
24
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
24
|
-
const DEFAULT_FEEDBACK_DIR =
|
|
25
|
+
const DEFAULT_FEEDBACK_DIR = resolveFeedbackDir();
|
|
25
26
|
const DEFAULT_CONTEXTFS_DIR = path.join(DEFAULT_FEEDBACK_DIR, 'contextfs');
|
|
26
27
|
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
@@ -29,7 +30,7 @@ const DEFAULT_CONTEXTFS_DIR = path.join(DEFAULT_FEEDBACK_DIR, 'contextfs');
|
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
|
|
31
32
|
function getFeedbackDir() {
|
|
32
|
-
return
|
|
33
|
+
return resolveFeedbackDir();
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function getContextFsDir() {
|