rlhf-feedback-loop 0.5.0

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 (73) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +308 -0
  4. package/adapters/README.md +8 -0
  5. package/adapters/amp/skills/rlhf-feedback/SKILL.md +20 -0
  6. package/adapters/chatgpt/INSTALL.md +80 -0
  7. package/adapters/chatgpt/openapi.yaml +292 -0
  8. package/adapters/claude/.mcp.json +8 -0
  9. package/adapters/codex/config.toml +4 -0
  10. package/adapters/gemini/function-declarations.json +95 -0
  11. package/adapters/mcp/server-stdio.js +444 -0
  12. package/bin/cli.js +167 -0
  13. package/config/mcp-allowlists.json +29 -0
  14. package/config/policy-bundles/constrained-v1.json +53 -0
  15. package/config/policy-bundles/default-v1.json +80 -0
  16. package/config/rubrics/default-v1.json +52 -0
  17. package/config/subagent-profiles.json +32 -0
  18. package/openapi/openapi.yaml +292 -0
  19. package/package.json +91 -0
  20. package/plugins/amp-skill/INSTALL.md +52 -0
  21. package/plugins/amp-skill/SKILL.md +31 -0
  22. package/plugins/claude-skill/INSTALL.md +55 -0
  23. package/plugins/claude-skill/SKILL.md +46 -0
  24. package/plugins/codex-profile/AGENTS.md +20 -0
  25. package/plugins/codex-profile/INSTALL.md +57 -0
  26. package/plugins/gemini-extension/INSTALL.md +74 -0
  27. package/plugins/gemini-extension/gemini_prompt.txt +10 -0
  28. package/plugins/gemini-extension/tool_contract.json +28 -0
  29. package/scripts/billing.js +471 -0
  30. package/scripts/budget-guard.js +173 -0
  31. package/scripts/code-reasoning.js +307 -0
  32. package/scripts/context-engine.js +547 -0
  33. package/scripts/contextfs.js +513 -0
  34. package/scripts/contract-audit.js +198 -0
  35. package/scripts/dpo-optimizer.js +208 -0
  36. package/scripts/export-dpo-pairs.js +316 -0
  37. package/scripts/export-training.js +448 -0
  38. package/scripts/feedback-attribution.js +313 -0
  39. package/scripts/feedback-inbox-read.js +162 -0
  40. package/scripts/feedback-loop.js +838 -0
  41. package/scripts/feedback-schema.js +300 -0
  42. package/scripts/feedback-to-memory.js +165 -0
  43. package/scripts/feedback-to-rules.js +109 -0
  44. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  45. package/scripts/hybrid-feedback-context.js +676 -0
  46. package/scripts/intent-router.js +164 -0
  47. package/scripts/mcp-policy.js +92 -0
  48. package/scripts/meta-policy.js +194 -0
  49. package/scripts/plan-gate.js +154 -0
  50. package/scripts/prove-adapters.js +364 -0
  51. package/scripts/prove-attribution.js +364 -0
  52. package/scripts/prove-automation.js +393 -0
  53. package/scripts/prove-data-quality.js +219 -0
  54. package/scripts/prove-intelligence.js +256 -0
  55. package/scripts/prove-lancedb.js +370 -0
  56. package/scripts/prove-loop-closure.js +255 -0
  57. package/scripts/prove-rlaif.js +404 -0
  58. package/scripts/prove-subway-upgrades.js +250 -0
  59. package/scripts/prove-training-export.js +324 -0
  60. package/scripts/prove-v2-milestone.js +273 -0
  61. package/scripts/prove-v3-milestone.js +381 -0
  62. package/scripts/rlaif-self-audit.js +123 -0
  63. package/scripts/rubric-engine.js +230 -0
  64. package/scripts/self-heal.js +127 -0
  65. package/scripts/self-healing-check.js +111 -0
  66. package/scripts/skill-quality-tracker.js +284 -0
  67. package/scripts/subagent-profiles.js +79 -0
  68. package/scripts/sync-gh-secrets-from-env.sh +29 -0
  69. package/scripts/thompson-sampling.js +331 -0
  70. package/scripts/train_from_feedback.py +914 -0
  71. package/scripts/validate-feedback.js +580 -0
  72. package/scripts/vector-store.js +100 -0
  73. package/src/api/server.js +497 -0
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const {
5
+ captureFeedback,
6
+ feedbackSummary,
7
+ analyzeFeedback,
8
+ writePreventionRules,
9
+ FEEDBACK_LOG_PATH,
10
+ } = require('../../scripts/feedback-loop');
11
+ const {
12
+ exportDpoFromMemories,
13
+ readJSONL,
14
+ DEFAULT_LOCAL_MEMORY_LOG,
15
+ } = require('../../scripts/export-dpo-pairs');
16
+ const {
17
+ ensureContextFs,
18
+ constructContextPack,
19
+ evaluateContextPack,
20
+ getProvenance,
21
+ } = require('../../scripts/contextfs');
22
+ const {
23
+ buildRubricEvaluation,
24
+ } = require('../../scripts/rubric-engine');
25
+ const {
26
+ listIntents,
27
+ planIntent,
28
+ } = require('../../scripts/intent-router');
29
+ const {
30
+ getActiveMcpProfile,
31
+ getAllowedTools,
32
+ assertToolAllowed,
33
+ } = require('../../scripts/mcp-policy');
34
+
35
+ const SERVER_INFO = {
36
+ name: 'rlhf-feedback-loop-mcp',
37
+ version: '1.1.0',
38
+ };
39
+ const SAFE_DATA_DIR = path.resolve(path.dirname(FEEDBACK_LOG_PATH));
40
+
41
+ function resolveSafePath(inputPath, { mustExist = false } = {}) {
42
+ const allowExternal = process.env.RLHF_ALLOW_EXTERNAL_PATHS === 'true';
43
+ const resolved = path.resolve(String(inputPath || ''));
44
+ const inSafeRoot = resolved === SAFE_DATA_DIR || resolved.startsWith(`${SAFE_DATA_DIR}${path.sep}`);
45
+
46
+ if (!allowExternal && !inSafeRoot) {
47
+ throw new Error(`Path must stay within ${SAFE_DATA_DIR}`);
48
+ }
49
+ if (mustExist && !fs.existsSync(resolved)) {
50
+ throw new Error(`Path does not exist: ${resolved}`);
51
+ }
52
+ return resolved;
53
+ }
54
+
55
+ const TOOLS = [
56
+ {
57
+ name: 'capture_feedback',
58
+ description: 'Capture thumbs up/down feedback and promote actionable memory',
59
+ inputSchema: {
60
+ type: 'object',
61
+ required: ['signal', 'context'],
62
+ properties: {
63
+ signal: { type: 'string', enum: ['up', 'down'] },
64
+ context: { type: 'string' },
65
+ whatWentWrong: { type: 'string' },
66
+ whatToChange: { type: 'string' },
67
+ whatWorked: { type: 'string' },
68
+ tags: { type: 'array', items: { type: 'string' } },
69
+ skill: { type: 'string' },
70
+ rubricScores: {
71
+ type: 'array',
72
+ items: {
73
+ type: 'object',
74
+ properties: {
75
+ criterion: { type: 'string' },
76
+ score: { type: 'number' },
77
+ evidence: { type: 'string' },
78
+ judge: { type: 'string' },
79
+ },
80
+ },
81
+ },
82
+ guardrails: {
83
+ type: 'object',
84
+ properties: {
85
+ testsPassed: { type: 'boolean' },
86
+ pathSafety: { type: 'boolean' },
87
+ budgetCompliant: { type: 'boolean' },
88
+ },
89
+ },
90
+ },
91
+ },
92
+ },
93
+ {
94
+ name: 'feedback_summary',
95
+ description: 'Get summary of recent feedback',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {
99
+ recent: { type: 'number' },
100
+ },
101
+ },
102
+ },
103
+ {
104
+ name: 'feedback_stats',
105
+ description: 'Get feedback stats and recommendations',
106
+ inputSchema: {
107
+ type: 'object',
108
+ properties: {},
109
+ },
110
+ },
111
+ {
112
+ name: 'list_intents',
113
+ description: 'List available intent plans and whether each requires human approval in the active profile',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ mcpProfile: { type: 'string' },
118
+ bundleId: { type: 'string' },
119
+ },
120
+ },
121
+ },
122
+ {
123
+ name: 'plan_intent',
124
+ description: 'Generate an intent execution plan with policy checkpoints',
125
+ inputSchema: {
126
+ type: 'object',
127
+ required: ['intentId'],
128
+ properties: {
129
+ intentId: { type: 'string' },
130
+ context: { type: 'string' },
131
+ mcpProfile: { type: 'string' },
132
+ bundleId: { type: 'string' },
133
+ approved: { type: 'boolean' },
134
+ },
135
+ },
136
+ },
137
+ {
138
+ name: 'prevention_rules',
139
+ description: 'Generate prevention rules from repeated mistake patterns',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ minOccurrences: { type: 'number' },
144
+ outputPath: { type: 'string' },
145
+ },
146
+ },
147
+ },
148
+ {
149
+ name: 'export_dpo_pairs',
150
+ description: 'Export DPO preference pairs from local memory log',
151
+ inputSchema: {
152
+ type: 'object',
153
+ properties: {
154
+ memoryLogPath: { type: 'string' },
155
+ },
156
+ },
157
+ },
158
+ {
159
+ name: 'construct_context_pack',
160
+ description: 'Construct a bounded context pack from contextfs',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ query: { type: 'string' },
165
+ maxItems: { type: 'number' },
166
+ maxChars: { type: 'number' },
167
+ namespaces: { type: 'array', items: { type: 'string' } },
168
+ },
169
+ },
170
+ },
171
+ {
172
+ name: 'evaluate_context_pack',
173
+ description: 'Record evaluation outcome for a context pack',
174
+ inputSchema: {
175
+ type: 'object',
176
+ required: ['packId', 'outcome'],
177
+ properties: {
178
+ packId: { type: 'string' },
179
+ outcome: { type: 'string' },
180
+ signal: { type: 'string' },
181
+ notes: { type: 'string' },
182
+ rubricScores: {
183
+ type: 'array',
184
+ items: {
185
+ type: 'object',
186
+ properties: {
187
+ criterion: { type: 'string' },
188
+ score: { type: 'number' },
189
+ evidence: { type: 'string' },
190
+ judge: { type: 'string' },
191
+ },
192
+ },
193
+ },
194
+ guardrails: {
195
+ type: 'object',
196
+ properties: {
197
+ testsPassed: { type: 'boolean' },
198
+ pathSafety: { type: 'boolean' },
199
+ budgetCompliant: { type: 'boolean' },
200
+ },
201
+ },
202
+ },
203
+ },
204
+ },
205
+ {
206
+ name: 'context_provenance',
207
+ description: 'Get recent context/provenance events',
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ limit: { type: 'number' },
212
+ },
213
+ },
214
+ },
215
+ ];
216
+
217
+ function toText(result) {
218
+ if (typeof result === 'string') return result;
219
+ return JSON.stringify(result, null, 2);
220
+ }
221
+
222
+ function parseOptionalObject(input, name) {
223
+ if (input == null) return {};
224
+ if (typeof input === 'object' && !Array.isArray(input)) return input;
225
+ if (typeof input === 'string') {
226
+ const trimmed = input.trim();
227
+ if (!trimmed) return {};
228
+ const parsed = JSON.parse(trimmed);
229
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
230
+ throw new Error(`${name} must be an object`);
231
+ }
232
+ return parsed;
233
+ }
234
+ throw new Error(`${name} must be an object`);
235
+ }
236
+
237
+ async function callTool(name, args = {}) {
238
+ assertToolAllowed(name, getActiveMcpProfile());
239
+
240
+ if (name === 'capture_feedback') {
241
+ const result = captureFeedback({
242
+ signal: args.signal,
243
+ context: args.context,
244
+ whatWentWrong: args.whatWentWrong,
245
+ whatToChange: args.whatToChange,
246
+ whatWorked: args.whatWorked,
247
+ rubricScores: args.rubricScores,
248
+ guardrails: parseOptionalObject(args.guardrails, 'guardrails'),
249
+ tags: args.tags || [],
250
+ skill: args.skill,
251
+ });
252
+ return { content: [{ type: 'text', text: toText(result) }] };
253
+ }
254
+
255
+ if (name === 'feedback_summary') {
256
+ const recent = Number(args.recent || 20);
257
+ const summary = feedbackSummary(Number.isFinite(recent) ? recent : 20);
258
+ return { content: [{ type: 'text', text: summary }] };
259
+ }
260
+
261
+ if (name === 'feedback_stats') {
262
+ return { content: [{ type: 'text', text: toText(analyzeFeedback()) }] };
263
+ }
264
+
265
+ if (name === 'list_intents') {
266
+ const result = listIntents({
267
+ mcpProfile: args.mcpProfile,
268
+ bundleId: args.bundleId,
269
+ });
270
+ return { content: [{ type: 'text', text: toText(result) }] };
271
+ }
272
+
273
+ if (name === 'plan_intent') {
274
+ const result = planIntent({
275
+ intentId: args.intentId,
276
+ context: args.context || '',
277
+ mcpProfile: args.mcpProfile,
278
+ bundleId: args.bundleId,
279
+ approved: args.approved === true,
280
+ });
281
+ return { content: [{ type: 'text', text: toText(result) }] };
282
+ }
283
+
284
+ if (name === 'prevention_rules') {
285
+ const minOccurrences = Number(args.minOccurrences || 2);
286
+ const outputPath = args.outputPath ? resolveSafePath(args.outputPath) : undefined;
287
+ const result = writePreventionRules(outputPath, Number.isFinite(minOccurrences) ? minOccurrences : 2);
288
+ return { content: [{ type: 'text', text: toText(result) }] };
289
+ }
290
+
291
+ if (name === 'export_dpo_pairs') {
292
+ const memoryLogPath = args.memoryLogPath
293
+ ? resolveSafePath(args.memoryLogPath, { mustExist: true })
294
+ : DEFAULT_LOCAL_MEMORY_LOG;
295
+ const memories = readJSONL(memoryLogPath);
296
+ const result = exportDpoFromMemories(memories);
297
+ return {
298
+ content: [{
299
+ type: 'text',
300
+ text: toText({
301
+ pairs: result.pairs.length,
302
+ errors: result.errors.length,
303
+ learnings: result.learnings.length,
304
+ }),
305
+ }],
306
+ };
307
+ }
308
+
309
+ if (name === 'construct_context_pack') {
310
+ ensureContextFs();
311
+ const result = constructContextPack({
312
+ query: args.query || '',
313
+ maxItems: Number(args.maxItems || 8),
314
+ maxChars: Number(args.maxChars || 6000),
315
+ namespaces: Array.isArray(args.namespaces) ? args.namespaces : [],
316
+ });
317
+ return { content: [{ type: 'text', text: toText(result) }] };
318
+ }
319
+
320
+ if (name === 'evaluate_context_pack') {
321
+ if (!args.packId || !args.outcome) {
322
+ throw new Error('packId and outcome are required');
323
+ }
324
+ const result = evaluateContextPack({
325
+ packId: args.packId,
326
+ outcome: args.outcome,
327
+ signal: args.signal || null,
328
+ notes: args.notes || '',
329
+ rubricEvaluation: args.rubricScores || args.guardrails
330
+ ? buildRubricEvaluation({
331
+ rubricScores: args.rubricScores,
332
+ guardrails: parseOptionalObject(args.guardrails, 'guardrails'),
333
+ })
334
+ : null,
335
+ });
336
+ return { content: [{ type: 'text', text: toText(result) }] };
337
+ }
338
+
339
+ if (name === 'context_provenance') {
340
+ const limit = Number(args.limit || 50);
341
+ const result = getProvenance(Number.isFinite(limit) ? limit : 50);
342
+ return { content: [{ type: 'text', text: toText(result) }] };
343
+ }
344
+
345
+ throw new Error(`Unknown tool: ${name}`);
346
+ }
347
+
348
+ async function handleRequest(message) {
349
+ if (message.method === 'initialize') {
350
+ return {
351
+ protocolVersion: '2024-11-05',
352
+ capabilities: {
353
+ tools: {},
354
+ },
355
+ serverInfo: SERVER_INFO,
356
+ };
357
+ }
358
+
359
+ if (message.method === 'tools/list') {
360
+ const profile = getActiveMcpProfile();
361
+ const allowed = new Set(getAllowedTools(profile));
362
+ const tools = TOOLS.filter((tool) => allowed.has(tool.name));
363
+ return { tools };
364
+ }
365
+
366
+ if (message.method === 'tools/call') {
367
+ const name = message.params && message.params.name;
368
+ const args = (message.params && message.params.arguments) || {};
369
+ return callTool(name, args);
370
+ }
371
+
372
+ throw new Error(`Unsupported method: ${message.method}`);
373
+ }
374
+
375
+ function writeMessage(payload) {
376
+ const json = JSON.stringify(payload);
377
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
378
+ }
379
+
380
+ let buffer = Buffer.alloc(0);
381
+
382
+ function tryReadMessage() {
383
+ const headerEnd = buffer.indexOf('\r\n\r\n');
384
+ if (headerEnd === -1) return null;
385
+
386
+ const headerRaw = buffer.slice(0, headerEnd).toString('utf8');
387
+ const match = headerRaw.match(/Content-Length:\s*(\d+)/i);
388
+ if (!match) {
389
+ buffer = buffer.slice(headerEnd + 4);
390
+ return null;
391
+ }
392
+
393
+ const length = Number(match[1]);
394
+ const totalSize = headerEnd + 4 + length;
395
+ if (buffer.length < totalSize) return null;
396
+
397
+ const body = buffer.slice(headerEnd + 4, totalSize).toString('utf8');
398
+ buffer = buffer.slice(totalSize);
399
+
400
+ return JSON.parse(body);
401
+ }
402
+
403
+ async function onData(chunk) {
404
+ buffer = Buffer.concat([buffer, chunk]);
405
+
406
+ while (true) {
407
+ const message = tryReadMessage();
408
+ if (!message) return;
409
+
410
+ if (!Object.prototype.hasOwnProperty.call(message, 'id')) {
411
+ continue;
412
+ }
413
+
414
+ try {
415
+ const result = await handleRequest(message);
416
+ writeMessage({ jsonrpc: '2.0', id: message.id, result });
417
+ } catch (err) {
418
+ writeMessage({
419
+ jsonrpc: '2.0',
420
+ id: message.id,
421
+ error: {
422
+ code: -32603,
423
+ message: err.message || 'Internal error',
424
+ },
425
+ });
426
+ }
427
+ }
428
+ }
429
+
430
+ module.exports = {
431
+ TOOLS,
432
+ handleRequest,
433
+ callTool,
434
+ resolveSafePath,
435
+ SAFE_DATA_DIR,
436
+ };
437
+
438
+ if (require.main === module) {
439
+ process.stdin.on('data', (chunk) => {
440
+ onData(chunk).catch((err) => {
441
+ writeMessage({ jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message } });
442
+ });
443
+ });
444
+ }
package/bin/cli.js ADDED
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * rlhf-feedback-loop CLI
4
+ *
5
+ * Usage:
6
+ * npx rlhf-feedback-loop init
7
+ *
8
+ * Creates a .rlhf/ directory with config and capture script for local use.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ const COMMAND = process.argv[2];
17
+ const CWD = process.cwd();
18
+
19
+ function init() {
20
+ const rlhfDir = path.join(CWD, '.rlhf');
21
+
22
+ // Create directory
23
+ if (!fs.existsSync(rlhfDir)) {
24
+ fs.mkdirSync(rlhfDir, { recursive: true });
25
+ console.log('Created .rlhf/');
26
+ } else {
27
+ console.log('.rlhf/ already exists — updating config');
28
+ }
29
+
30
+ // Write config.json
31
+ const config = {
32
+ version: '0.5.0',
33
+ apiUrl: process.env.RLHF_API_URL || 'http://localhost:3000',
34
+ logPath: '.rlhf/feedback-log.jsonl',
35
+ memoryPath: '.rlhf/memory-log.jsonl',
36
+ createdAt: new Date().toISOString(),
37
+ };
38
+
39
+ const configPath = path.join(rlhfDir, 'config.json');
40
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
41
+ console.log('Wrote .rlhf/config.json');
42
+
43
+ // Copy capture-feedback script (inline minimal version for standalone use)
44
+ const captureScript = `#!/usr/bin/env node
45
+ /**
46
+ * Standalone feedback capture script — created by npx rlhf-feedback-loop init
47
+ * Full version: https://github.com/IgorGanapolsky/rlhf-feedback-loop
48
+ *
49
+ * Usage:
50
+ * node .rlhf/capture-feedback.js --feedback=up --context="that worked great" --tags="testing"
51
+ * node .rlhf/capture-feedback.js --feedback=down --context="missed edge case" --what-went-wrong="..." --what-to-change="..."
52
+ */
53
+
54
+ 'use strict';
55
+
56
+ const fs = require('fs');
57
+ const path = require('path');
58
+ const os = require('os');
59
+
60
+ const CONFIG_PATH = path.join(__dirname, 'config.json');
61
+ const config = fs.existsSync(CONFIG_PATH) ? JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) : {};
62
+ const LOG_PATH = path.join(process.cwd(), config.logPath || '.rlhf/feedback-log.jsonl');
63
+
64
+ function parseArgs(argv) {
65
+ const args = {};
66
+ argv.forEach((arg) => {
67
+ if (!arg.startsWith('--')) return;
68
+ const [key, ...rest] = arg.slice(2).split('=');
69
+ args[key] = rest.length ? rest.join('=') : true;
70
+ });
71
+ return args;
72
+ }
73
+
74
+ const args = parseArgs(process.argv.slice(2));
75
+ const signal = args.feedback || args.signal;
76
+
77
+ if (!signal) {
78
+ console.error('Error: --feedback=up or --feedback=down required');
79
+ console.error('Usage: node .rlhf/capture-feedback.js --feedback=up --context="..."');
80
+ process.exit(1);
81
+ }
82
+
83
+ const normalized = ['up', 'thumbs_up', 'positive'].includes(signal) ? 'up' : 'down';
84
+
85
+ const entry = {
86
+ id: \`fb-\${Date.now()}-\${Math.random().toString(36).slice(2, 7)}\`,
87
+ signal: normalized,
88
+ context: args.context || '',
89
+ whatWentWrong: args['what-went-wrong'] || undefined,
90
+ whatToChange: args['what-to-change'] || undefined,
91
+ whatWorked: args['what-worked'] || undefined,
92
+ tags: args.tags ? args.tags.split(',').map((t) => t.trim()) : [],
93
+ timestamp: new Date().toISOString(),
94
+ hostname: os.hostname(),
95
+ };
96
+
97
+ // Remove undefined fields
98
+ Object.keys(entry).forEach((k) => entry[k] === undefined && delete entry[k]);
99
+
100
+ // Ensure log directory exists
101
+ const logDir = path.dirname(LOG_PATH);
102
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
103
+
104
+ fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + '\\n');
105
+ console.log(\`Feedback captured [\${normalized}]: \${entry.id}\`);
106
+ console.log(\`Logged to: \${LOG_PATH}\`);
107
+ `;
108
+
109
+ const scriptPath = path.join(rlhfDir, 'capture-feedback.js');
110
+ fs.writeFileSync(scriptPath, captureScript);
111
+ // Make executable
112
+ try {
113
+ fs.chmodSync(scriptPath, '755');
114
+ } catch (_) {
115
+ // chmod may not be available on all platforms — not fatal
116
+ }
117
+ console.log('Wrote .rlhf/capture-feedback.js');
118
+
119
+ // Add .rlhf/feedback-log.jsonl to .gitignore if it exists
120
+ const gitignorePath = path.join(CWD, '.gitignore');
121
+ if (fs.existsSync(gitignorePath)) {
122
+ const gitignore = fs.readFileSync(gitignorePath, 'utf8');
123
+ const entries = ['.rlhf/feedback-log.jsonl', '.rlhf/memory-log.jsonl'];
124
+ const missing = entries.filter((e) => !gitignore.includes(e));
125
+ if (missing.length > 0) {
126
+ fs.appendFileSync(gitignorePath, '\n# RLHF local feedback data\n' + missing.join('\n') + '\n');
127
+ console.log('Updated .gitignore with RLHF data paths');
128
+ }
129
+ }
130
+
131
+ console.log('');
132
+ console.log('Setup complete! Run:');
133
+ console.log(" node .rlhf/capture-feedback.js --feedback=up --context='test'");
134
+ console.log('');
135
+ console.log('Full docs: https://github.com/IgorGanapolsky/rlhf-feedback-loop');
136
+ }
137
+
138
+ function help() {
139
+ console.log('rlhf-feedback-loop CLI');
140
+ console.log('');
141
+ console.log('Commands:');
142
+ console.log(' init Scaffold .rlhf/ config and capture script in current directory');
143
+ console.log(' help Show this help message');
144
+ console.log('');
145
+ console.log('Examples:');
146
+ console.log(' npx rlhf-feedback-loop init');
147
+ console.log(' node .rlhf/capture-feedback.js --feedback=up --context="great result"');
148
+ }
149
+
150
+ switch (COMMAND) {
151
+ case 'init':
152
+ init();
153
+ break;
154
+ case 'help':
155
+ case '--help':
156
+ case '-h':
157
+ help();
158
+ break;
159
+ default:
160
+ if (COMMAND) {
161
+ console.error(`Unknown command: ${COMMAND}`);
162
+ console.error('Run: npx rlhf-feedback-loop help');
163
+ process.exit(1);
164
+ } else {
165
+ help();
166
+ }
167
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "version": 1,
3
+ "profiles": {
4
+ "default": [
5
+ "capture_feedback",
6
+ "feedback_summary",
7
+ "feedback_stats",
8
+ "list_intents",
9
+ "plan_intent",
10
+ "prevention_rules",
11
+ "export_dpo_pairs",
12
+ "construct_context_pack",
13
+ "evaluate_context_pack",
14
+ "context_provenance"
15
+ ],
16
+ "readonly": [
17
+ "feedback_summary",
18
+ "feedback_stats",
19
+ "list_intents",
20
+ "plan_intent",
21
+ "context_provenance"
22
+ ],
23
+ "locked": [
24
+ "feedback_summary",
25
+ "list_intents",
26
+ "plan_intent"
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "bundleId": "constrained-v1",
3
+ "version": 1,
4
+ "description": "Conservative execution bundle for locked and review-heavy environments.",
5
+ "defaultMcpProfile": "locked",
6
+ "approval": {
7
+ "requiredRisks": ["medium", "high", "critical"],
8
+ "profileOverrides": {
9
+ "default": ["medium", "high", "critical"],
10
+ "readonly": ["medium", "high", "critical"],
11
+ "locked": ["low", "medium", "high", "critical"]
12
+ }
13
+ },
14
+ "intents": [
15
+ {
16
+ "id": "capture_feedback_loop",
17
+ "description": "Capture user outcome and update memory artifacts.",
18
+ "risk": "low",
19
+ "actions": [
20
+ {
21
+ "kind": "mcp_tool",
22
+ "name": "capture_feedback"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "id": "improve_response_quality",
28
+ "description": "Summarize recent failures and regenerate prevention rules.",
29
+ "risk": "medium",
30
+ "actions": [
31
+ {
32
+ "kind": "mcp_tool",
33
+ "name": "feedback_summary"
34
+ },
35
+ {
36
+ "kind": "mcp_tool",
37
+ "name": "prevention_rules"
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ "id": "publish_dpo_training_data",
43
+ "description": "Export DPO preference pairs for model improvement pipelines.",
44
+ "risk": "high",
45
+ "actions": [
46
+ {
47
+ "kind": "mcp_tool",
48
+ "name": "export_dpo_pairs"
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ }