rlhf-feedback-loop 0.6.2 → 0.6.3

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/README.md CHANGED
@@ -23,6 +23,8 @@ One command. Pick your platform:
23
23
  | **Claude** | `claude mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
24
24
  | **Codex** | `codex mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
25
25
  | **Gemini** | `gemini mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
26
+ | **Amp** | `cp node_modules/rlhf-feedback-loop/plugins/amp-skill/SKILL.md .amp/skills/rlhf-feedback/SKILL.md` |
27
+ | **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
26
28
  | **All at once** | `npx add-mcp rlhf-feedback-loop` |
27
29
 
28
30
  That's it. Your agent can now capture feedback, recall past learnings mid-conversation, and block repeated mistakes.
package/bin/cli.js CHANGED
@@ -210,19 +210,39 @@ function capture() {
210
210
  return;
211
211
  }
212
212
 
213
- // Normalize signal with fuzzy matching (uses the full engine's normalize)
214
- const captureScript = require(path.join(PKG_ROOT, '.claude', 'scripts', 'feedback', 'capture-feedback.js'));
215
- // The capture-feedback.js runs as main when required directly, so we call via subprocess
216
- const scriptArgs = process.argv.slice(3).join(' ');
217
- try {
218
- const output = execSync(
219
- `node "${path.join(PKG_ROOT, '.claude', 'scripts', 'feedback', 'capture-feedback.js')}" ${scriptArgs}`,
220
- { encoding: 'utf8', stdio: 'pipe', cwd: CWD }
221
- );
222
- process.stdout.write(output);
223
- } catch (err) {
224
- process.stderr.write(err.stderr || err.stdout || err.message);
225
- process.exit(err.status || 1);
213
+ const signal = (args.feedback || '').toLowerCase();
214
+ const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some(v => signal.includes(v)) ? 'up'
215
+ : ['down', 'thumbsdown', 'thumbs_down', 'negative'].some(v => signal.includes(v)) ? 'down'
216
+ : signal;
217
+
218
+ if (normalized !== 'up' && normalized !== 'down') {
219
+ console.error('Missing or unrecognized --feedback=up|down');
220
+ process.exit(1);
221
+ }
222
+
223
+ const result = captureFeedback({
224
+ signal: normalized,
225
+ context: args.context || '',
226
+ whatWentWrong: args['what-went-wrong'],
227
+ whatToChange: args['what-to-change'],
228
+ whatWorked: args['what-worked'],
229
+ tags: args.tags,
230
+ });
231
+
232
+ if (result.accepted) {
233
+ const ev = result.feedbackEvent;
234
+ const mem = result.memoryRecord;
235
+ console.log(`\nRLHF Feedback Captured [${normalized.toUpperCase()}]`);
236
+ console.log('─'.repeat(50));
237
+ console.log(` Feedback ID : ${ev.id}`);
238
+ console.log(` Signal : ${ev.signal} (${ev.actionType})`);
239
+ console.log(` Memory ID : ${mem.id}`);
240
+ console.log(` Storage : JSONL log + LanceDB vector index\n`);
241
+ } else {
242
+ console.log(`\nRLHF Feedback Recorded [${normalized.toUpperCase()}] — not promoted`);
243
+ console.log('─'.repeat(50));
244
+ console.log(` Reason : ${result.reason}\n`);
245
+ process.exit(2);
226
246
  }
227
247
  }
228
248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlhf-feedback-loop",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Make your AI agent learn from mistakes. Capture thumbs up/down feedback, block repeated failures, export DPO training data. Works with ChatGPT, Claude, Codex, Gemini, Amp.",
5
5
  "homepage": "https://github.com/IgorGanapolsky/rlhf-feedback-loop#readme",
6
6
  "repository": {
@@ -0,0 +1,69 @@
1
+ #!/bin/bash
2
+ # Claude Code UserPromptSubmit hook — auto-captures thumbs up/down feedback
3
+ # Triggered on every user message. Only acts on feedback signals.
4
+ # Shows full verbose output with storage paths, memory IDs, and stats.
5
+
6
+ PROMPT="$CLAUDE_USER_PROMPT"
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ CAPTURE="$SCRIPT_DIR/../.claude/scripts/feedback/capture-feedback.js"
9
+ FEEDBACK_LOG="$SCRIPT_DIR/../.claude/memory/feedback/feedback-log.jsonl"
10
+ MEMORY_LOG="$SCRIPT_DIR/../.claude/memory/feedback/memory-log.jsonl"
11
+
12
+ # Normalize to lowercase for matching
13
+ LOWER=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]')
14
+
15
+ capture_and_report() {
16
+ local SIGNAL="$1"
17
+
18
+ # Capture feedback (verbose output already shows IDs, signal, storage)
19
+ node "$CAPTURE" --feedback="$SIGNAL" --context="$PROMPT" --tags="auto-capture,hook"
20
+
21
+ # Show storage proof
22
+ echo ""
23
+ echo "Storage Proof:"
24
+ echo " Feedback log : $FEEDBACK_LOG ($(wc -l < "$FEEDBACK_LOG" 2>/dev/null || echo 0) entries)"
25
+ echo " Memory log : $MEMORY_LOG ($(wc -l < "$MEMORY_LOG" 2>/dev/null || echo 0) entries)"
26
+ echo " LanceDB : $SCRIPT_DIR/../.claude/memory/feedback/lancedb/"
27
+ echo ""
28
+
29
+ # Show last entry written
30
+ echo "Last Entry Written:"
31
+ tail -1 "$FEEDBACK_LOG" 2>/dev/null | node -e "
32
+ const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
33
+ console.log(' ID :', d.id);
34
+ console.log(' Signal :', d.signal, '(' + d.actionType + ')');
35
+ console.log(' Context :', (d.context||'').slice(0,80));
36
+ console.log(' Tags :', (d.tags||[]).join(', '));
37
+ console.log(' Timestamp :', d.timestamp);
38
+ console.log(' Domain :', (d.richContext||{}).domain || 'general');
39
+ " 2>/dev/null
40
+
41
+ # Show cumulative stats
42
+ echo ""
43
+ echo "Cumulative Stats:"
44
+ node -e "
45
+ const fs = require('fs');
46
+ const lines = fs.readFileSync('$FEEDBACK_LOG','utf8').trim().split('\n').filter(Boolean);
47
+ const entries = lines.map(l => { try { return JSON.parse(l); } catch(e) { return null; } }).filter(Boolean);
48
+ const pos = entries.filter(e => e.signal === 'positive').length;
49
+ const neg = entries.filter(e => e.signal === 'negative').length;
50
+ const promoted = entries.filter(e => e.actionType === 'store-learning' || e.actionType === 'store-mistake').length;
51
+ console.log(' Total feedback :', entries.length);
52
+ console.log(' Positive (up) :', pos);
53
+ console.log(' Negative (down) :', neg);
54
+ console.log(' Promoted to mem :', promoted);
55
+ console.log(' Ratio :', pos > 0 ? (pos/(pos+neg)*100).toFixed(0) + '% positive' : 'n/a');
56
+ " 2>/dev/null
57
+ }
58
+
59
+ # Check for thumbs up signals
60
+ if echo "$LOWER" | grep -qE '(thumbs? ?up|that worked|looks good|nice work|perfect|good job)'; then
61
+ capture_and_report "up"
62
+ exit 0
63
+ fi
64
+
65
+ # Check for thumbs down signals
66
+ if echo "$LOWER" | grep -qE '(thumbs? ?down|that failed|that was wrong|fix this)'; then
67
+ capture_and_report "down"
68
+ exit 0
69
+ fi
package/src/api/server.js CHANGED
@@ -183,7 +183,18 @@ function createApiServer() {
183
183
  const parsed = new URL(req.url, 'http://localhost');
184
184
  const pathname = parsed.pathname;
185
185
 
186
- // Health check is unauthenticated required for Railway/load-balancer probes
186
+ // Public endpoints no auth required
187
+ if (req.method === 'GET' && pathname === '/') {
188
+ sendJson(res, 200, {
189
+ name: 'rlhf-feedback-loop',
190
+ version: pkg.version,
191
+ status: 'ok',
192
+ docs: 'https://github.com/IgorGanapolsky/rlhf-feedback-loop',
193
+ endpoints: ['/health', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/dpo/export'],
194
+ });
195
+ return;
196
+ }
197
+
187
198
  if (req.method === 'GET' && pathname === '/health') {
188
199
  sendJson(res, 200, {
189
200
  status: 'ok',
@@ -409,10 +420,6 @@ function createApiServer() {
409
420
  return;
410
421
  }
411
422
 
412
- if (req.method === 'GET' && pathname === '/') {
413
- sendText(res, 200, 'RLHF Feedback Loop API is running.');
414
- return;
415
- }
416
423
 
417
424
  // ----------------------------------------------------------------
418
425
  // Billing routes