rlhf-feedback-loop 0.6.10 → 0.6.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/CHANGELOG.md +10 -0
- package/README.md +120 -74
- package/adapters/README.md +3 -3
- package/adapters/amp/skills/rlhf-feedback/SKILL.md +2 -0
- package/adapters/chatgpt/INSTALL.md +6 -3
- package/adapters/chatgpt/openapi.yaml +5 -2
- package/adapters/claude/.mcp.json +3 -3
- package/adapters/codex/config.toml +3 -3
- package/adapters/gemini/function-declarations.json +2 -2
- package/adapters/mcp/server-stdio.js +19 -5
- package/bin/cli.js +295 -25
- package/openapi/openapi.yaml +5 -2
- package/package.json +25 -9
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/adk-consolidator.js +267 -0
- package/scripts/billing.js +192 -681
- package/scripts/code-reasoning.js +26 -1
- package/scripts/context-engine.js +86 -4
- package/scripts/contextfs.js +130 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/export-kto-pairs.js +310 -0
- package/scripts/feedback-ingest-watcher.js +290 -0
- package/scripts/feedback-loop.js +153 -8
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-schema.js +31 -5
- package/scripts/feedback-to-memory.js +13 -1
- package/scripts/hook-auto-capture.sh +6 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/install-mcp.js +168 -0
- package/scripts/intent-router.js +88 -0
- package/scripts/jsonl-watcher.js +151 -0
- package/scripts/local-model-profile.js +207 -0
- package/scripts/pr-manager.js +112 -0
- package/scripts/prove-adapters.js +137 -15
- package/scripts/prove-attribution.js +6 -6
- package/scripts/prove-automation.js +41 -8
- package/scripts/prove-data-quality.js +16 -8
- package/scripts/prove-intelligence.js +7 -4
- package/scripts/prove-lancedb.js +7 -7
- package/scripts/prove-local-intelligence.js +244 -0
- package/scripts/prove-loop-closure.js +16 -8
- package/scripts/prove-training-export.js +7 -4
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/risk-scorer.js +458 -0
- package/scripts/rlaif-self-audit.js +7 -1
- package/scripts/self-heal.js +24 -4
- package/scripts/status-dashboard.js +155 -0
- package/scripts/sync-version.js +159 -0
- package/scripts/test-coverage.js +76 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +115 -17
- package/src/api/server.js +372 -25
package/bin/cli.js
CHANGED
|
@@ -39,23 +39,146 @@ function pkgVersion() {
|
|
|
39
39
|
// --- Platform auto-detection helpers ---
|
|
40
40
|
|
|
41
41
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const MCP_SERVER_NAME = 'rlhf';
|
|
43
|
+
const LEGACY_MCP_SERVER_NAMES = ['rlhf', 'rlhf-feedback-loop', 'rlhf_feedback_loop'];
|
|
44
|
+
const PORTABLE_MCP_COMMAND = 'npx';
|
|
45
|
+
const LOCAL_MCP_COMMAND = 'node';
|
|
46
|
+
|
|
47
|
+
function portableMcpArgs() {
|
|
48
|
+
return ['-y', `rlhf-feedback-loop@${pkgVersion()}`, 'serve'];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function localServerEntryPath() {
|
|
52
|
+
return path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldUseLocalServerEntry() {
|
|
56
|
+
return fs.existsSync(path.join(PKG_ROOT, '.git'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function portableMcpEntry() {
|
|
60
|
+
return {
|
|
61
|
+
command: PORTABLE_MCP_COMMAND,
|
|
62
|
+
args: portableMcpArgs(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function localMcpEntry() {
|
|
67
|
+
return {
|
|
68
|
+
command: LOCAL_MCP_COMMAND,
|
|
69
|
+
args: [localServerEntryPath()],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function mcpEntriesMatch(entry, expectedEntry) {
|
|
74
|
+
return Boolean(
|
|
75
|
+
entry &&
|
|
76
|
+
expectedEntry &&
|
|
77
|
+
entry.command === expectedEntry.command &&
|
|
78
|
+
Array.isArray(entry.args) &&
|
|
79
|
+
Array.isArray(expectedEntry.args) &&
|
|
80
|
+
entry.args.length === expectedEntry.args.length &&
|
|
81
|
+
entry.args.every((arg, index) => arg === expectedEntry.args[index])
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function escapeRegExp(value) {
|
|
86
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatTomlStringArray(values) {
|
|
90
|
+
return `[${values.map((value) => JSON.stringify(value)).join(', ')}]`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function canonicalMcpEntry() {
|
|
94
|
+
return shouldUseLocalServerEntry() ? localMcpEntry() : portableMcpEntry();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function mcpSectionBlock(name = MCP_SERVER_NAME) {
|
|
98
|
+
const entry = canonicalMcpEntry();
|
|
99
|
+
return `[mcp_servers.${name}]\ncommand = "${entry.command}"\nargs = ${formatTomlStringArray(entry.args)}\n`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function upsertCodexServerConfig(content) {
|
|
103
|
+
const canonicalBlock = mcpSectionBlock();
|
|
104
|
+
const sections = LEGACY_MCP_SERVER_NAMES.map((name) => ({
|
|
105
|
+
name,
|
|
106
|
+
regex: new RegExp(`^\\[mcp_servers\\.${escapeRegExp(name)}\\]\\n[\\s\\S]*?(?=^\\[|$)`, 'm'),
|
|
107
|
+
}));
|
|
108
|
+
const matches = sections
|
|
109
|
+
.map((section) => ({ ...section, match: content.match(section.regex) }))
|
|
110
|
+
.filter((section) => section.match);
|
|
111
|
+
|
|
112
|
+
if (matches.length === 0) {
|
|
113
|
+
const prefix = content.trimEnd();
|
|
114
|
+
return {
|
|
115
|
+
changed: true,
|
|
116
|
+
content: `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let nextContent = content;
|
|
121
|
+
let changed = false;
|
|
122
|
+
let canonicalPresent = false;
|
|
123
|
+
|
|
124
|
+
for (const section of matches) {
|
|
125
|
+
const normalized = canonicalBlock;
|
|
126
|
+
const current = section.match[0];
|
|
127
|
+
|
|
128
|
+
if (section.name === MCP_SERVER_NAME) {
|
|
129
|
+
canonicalPresent = true;
|
|
130
|
+
if (current !== normalized) {
|
|
131
|
+
nextContent = nextContent.replace(section.regex, normalized);
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
nextContent = nextContent.replace(section.regex, '');
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (!canonicalPresent) {
|
|
142
|
+
const prefix = nextContent.trimEnd();
|
|
143
|
+
nextContent = `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}`;
|
|
144
|
+
changed = true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
changed,
|
|
149
|
+
content: nextContent.endsWith('\n') ? nextContent : `${nextContent}\n`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
46
152
|
|
|
47
153
|
function mergeMcpJson(filePath, label) {
|
|
154
|
+
const canonicalEntry = canonicalMcpEntry();
|
|
48
155
|
if (!fs.existsSync(filePath)) {
|
|
49
156
|
const dir = path.dirname(filePath);
|
|
50
157
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
51
|
-
fs.writeFileSync(filePath, JSON.stringify({ mcpServers: {
|
|
158
|
+
fs.writeFileSync(filePath, JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: canonicalEntry } }, null, 2) + '\n');
|
|
52
159
|
console.log(` ${label}: wrote ${path.relative(CWD, filePath)}`);
|
|
53
160
|
return true;
|
|
54
161
|
}
|
|
55
162
|
const existing = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
56
|
-
if (existing.mcpServers && existing.mcpServers['rlhf-feedback-loop']) return false;
|
|
57
163
|
existing.mcpServers = existing.mcpServers || {};
|
|
58
|
-
|
|
164
|
+
|
|
165
|
+
let changed = false;
|
|
166
|
+
const currentEntry = existing.mcpServers[MCP_SERVER_NAME];
|
|
167
|
+
if (!mcpEntriesMatch(currentEntry, canonicalEntry)) {
|
|
168
|
+
existing.mcpServers[MCP_SERVER_NAME] = canonicalEntry;
|
|
169
|
+
changed = true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const legacyName of LEGACY_MCP_SERVER_NAMES) {
|
|
173
|
+
if (legacyName === MCP_SERVER_NAME) continue;
|
|
174
|
+
if (Object.prototype.hasOwnProperty.call(existing.mcpServers, legacyName)) {
|
|
175
|
+
delete existing.mcpServers[legacyName];
|
|
176
|
+
changed = true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!changed) return false;
|
|
181
|
+
|
|
59
182
|
fs.writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\n');
|
|
60
183
|
console.log(` ${label}: updated ${path.relative(CWD, filePath)}`);
|
|
61
184
|
return true;
|
|
@@ -73,12 +196,37 @@ function whichExists(cmd) {
|
|
|
73
196
|
}
|
|
74
197
|
|
|
75
198
|
function setupClaude() {
|
|
76
|
-
|
|
199
|
+
const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code');
|
|
200
|
+
|
|
201
|
+
// Upsert Stop hook into .claude/settings.json for autonomous self-scoring
|
|
202
|
+
const settingsPath = path.join(CWD, '.claude', 'settings.json');
|
|
203
|
+
const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
|
|
204
|
+
|
|
205
|
+
let settings = { hooks: {} };
|
|
206
|
+
if (fs.existsSync(settingsPath)) {
|
|
207
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
|
|
208
|
+
}
|
|
209
|
+
settings.hooks = settings.hooks || {};
|
|
210
|
+
|
|
211
|
+
const stopAlreadyPresent = (settings.hooks.Stop || [])
|
|
212
|
+
.some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
|
|
213
|
+
|
|
214
|
+
let hooksChanged = false;
|
|
215
|
+
if (!stopAlreadyPresent) {
|
|
216
|
+
settings.hooks.Stop = settings.hooks.Stop || [];
|
|
217
|
+
settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
|
|
218
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
219
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
220
|
+
console.log(' Claude Code: installed Stop hook in .claude/settings.json');
|
|
221
|
+
hooksChanged = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return mcpChanged || hooksChanged;
|
|
77
225
|
}
|
|
78
226
|
|
|
79
227
|
function setupCodex() {
|
|
80
228
|
const configPath = path.join(HOME, '.codex', 'config.toml');
|
|
81
|
-
const block =
|
|
229
|
+
const block = mcpSectionBlock();
|
|
82
230
|
if (!fs.existsSync(configPath)) {
|
|
83
231
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
84
232
|
fs.writeFileSync(configPath, block);
|
|
@@ -86,8 +234,9 @@ function setupCodex() {
|
|
|
86
234
|
return true;
|
|
87
235
|
}
|
|
88
236
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
89
|
-
|
|
90
|
-
|
|
237
|
+
const updated = upsertCodexServerConfig(content);
|
|
238
|
+
if (!updated.changed) return false;
|
|
239
|
+
fs.writeFileSync(configPath, updated.content);
|
|
91
240
|
console.log(' Codex: appended MCP server to ~/.codex/config.toml');
|
|
92
241
|
return true;
|
|
93
242
|
}
|
|
@@ -96,9 +245,24 @@ function setupGemini() {
|
|
|
96
245
|
const settingsPath = path.join(HOME, '.gemini', 'settings.json');
|
|
97
246
|
if (fs.existsSync(settingsPath)) {
|
|
98
247
|
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
99
|
-
if (settings.mcpServers && settings.mcpServers['rlhf-feedback-loop']) return false;
|
|
100
248
|
settings.mcpServers = settings.mcpServers || {};
|
|
101
|
-
|
|
249
|
+
let changed = false;
|
|
250
|
+
const canonicalEntry = canonicalMcpEntry();
|
|
251
|
+
|
|
252
|
+
if (!mcpEntriesMatch(settings.mcpServers[MCP_SERVER_NAME], canonicalEntry)) {
|
|
253
|
+
settings.mcpServers[MCP_SERVER_NAME] = canonicalEntry;
|
|
254
|
+
changed = true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const legacyName of LEGACY_MCP_SERVER_NAMES) {
|
|
258
|
+
if (legacyName === MCP_SERVER_NAME) continue;
|
|
259
|
+
if (Object.prototype.hasOwnProperty.call(settings.mcpServers, legacyName)) {
|
|
260
|
+
delete settings.mcpServers[legacyName];
|
|
261
|
+
changed = true;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!changed) return false;
|
|
102
266
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
103
267
|
console.log(' Gemini: updated ~/.gemini/settings.json');
|
|
104
268
|
return true;
|
|
@@ -320,6 +484,65 @@ function summary() {
|
|
|
320
484
|
console.log(feedbackSummary(Number(args.recent || 20)));
|
|
321
485
|
}
|
|
322
486
|
|
|
487
|
+
function modelFit() {
|
|
488
|
+
const { writeModelFitReport } = require(path.join(PKG_ROOT, 'scripts', 'local-model-profile'));
|
|
489
|
+
const { reportPath, report } = writeModelFitReport();
|
|
490
|
+
console.log(JSON.stringify({ reportPath, report }, null, 2));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function risk() {
|
|
494
|
+
const args = parseArgs(process.argv.slice(3));
|
|
495
|
+
const riskScorer = require(path.join(PKG_ROOT, 'scripts', 'risk-scorer'));
|
|
496
|
+
|
|
497
|
+
if (args.context || args.tags || args.skill || args.domain || args['rubric-scores'] || args.guardrails) {
|
|
498
|
+
const { inferDomain } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
499
|
+
const { buildRubricEvaluation } = require(path.join(PKG_ROOT, 'scripts', 'rubric-engine'));
|
|
500
|
+
const historyRows = riskScorer.readJSONL(riskScorer.sequencePathFor());
|
|
501
|
+
const tags = String(args.tags || '')
|
|
502
|
+
.split(',')
|
|
503
|
+
.map((tag) => tag.trim())
|
|
504
|
+
.filter(Boolean);
|
|
505
|
+
|
|
506
|
+
let rubric = null;
|
|
507
|
+
if (args['rubric-scores'] || args.guardrails) {
|
|
508
|
+
const evaluation = buildRubricEvaluation({
|
|
509
|
+
rubricScores: args['rubric-scores'],
|
|
510
|
+
guardrails: args.guardrails,
|
|
511
|
+
});
|
|
512
|
+
rubric = {
|
|
513
|
+
rubricId: evaluation.rubricId,
|
|
514
|
+
weightedScore: evaluation.weightedScore,
|
|
515
|
+
failingCriteria: evaluation.failingCriteria,
|
|
516
|
+
failingGuardrails: evaluation.failingGuardrails,
|
|
517
|
+
judgeDisagreements: evaluation.judgeDisagreements,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const candidate = riskScorer.buildRiskCandidate({
|
|
522
|
+
context: args.context || '',
|
|
523
|
+
tags,
|
|
524
|
+
skill: args.skill || null,
|
|
525
|
+
domain: args.domain || inferDomain(tags, args.context || ''),
|
|
526
|
+
rubric,
|
|
527
|
+
filePathCount: Number(args['file-count'] || 0),
|
|
528
|
+
errorType: args['error-type'] || null,
|
|
529
|
+
}, historyRows);
|
|
530
|
+
const model = riskScorer.loadRiskModel() || riskScorer.trainAndPersistRiskModel().model;
|
|
531
|
+
console.log(JSON.stringify({
|
|
532
|
+
prediction: riskScorer.predictRisk(model, candidate),
|
|
533
|
+
candidate,
|
|
534
|
+
}, null, 2));
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const { model, modelPath } = riskScorer.trainAndPersistRiskModel();
|
|
539
|
+
console.log(JSON.stringify({
|
|
540
|
+
modelPath,
|
|
541
|
+
metrics: model.metrics,
|
|
542
|
+
summary: riskScorer.getRiskSummary(),
|
|
543
|
+
}, null, 2));
|
|
544
|
+
}
|
|
545
|
+
|
|
323
546
|
function exportDpo() {
|
|
324
547
|
const extraArgs = process.argv.slice(3).join(' ');
|
|
325
548
|
try {
|
|
@@ -359,7 +582,7 @@ function prove() {
|
|
|
359
582
|
const script = path.join(PKG_ROOT, 'scripts', `prove-${target}.js`);
|
|
360
583
|
if (!fs.existsSync(script)) {
|
|
361
584
|
console.error(`Unknown proof target: ${target}`);
|
|
362
|
-
console.error('Available: adapters, automation, attribution, lancedb, data-quality, intelligence, loop-closure, training-export');
|
|
585
|
+
console.error('Available: adapters, automation, attribution, lancedb, data-quality, intelligence, local-intelligence, loop-closure, training-export');
|
|
363
586
|
process.exit(1);
|
|
364
587
|
}
|
|
365
588
|
try {
|
|
@@ -369,21 +592,39 @@ function prove() {
|
|
|
369
592
|
}
|
|
370
593
|
}
|
|
371
594
|
|
|
372
|
-
function
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
595
|
+
function watchCmd() {
|
|
596
|
+
const args = parseArgs(process.argv.slice(3));
|
|
597
|
+
const { watch, once } = require(path.join(PKG_ROOT, 'scripts', 'jsonl-watcher'));
|
|
598
|
+
const sourceFilter = args.source || undefined;
|
|
599
|
+
if (args.once) {
|
|
600
|
+
once(sourceFilter);
|
|
601
|
+
} else {
|
|
602
|
+
watch(sourceFilter);
|
|
381
603
|
}
|
|
604
|
+
}
|
|
382
605
|
|
|
606
|
+
function status() {
|
|
607
|
+
const statusDashboard = require(path.join(PKG_ROOT, 'scripts', 'status-dashboard'));
|
|
608
|
+
const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
609
|
+
const { FEEDBACK_DIR } = getFeedbackPaths();
|
|
610
|
+
const data = statusDashboard.generateStatus(FEEDBACK_DIR);
|
|
611
|
+
// printDashboard writes directly to stdout when run as main;
|
|
612
|
+
// for CLI we call the same renderer
|
|
613
|
+
statusDashboard.printDashboard
|
|
614
|
+
? statusDashboard.printDashboard(data)
|
|
615
|
+
: console.log(JSON.stringify(data, null, 2));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function serve() {
|
|
383
619
|
// Start MCP server over stdio
|
|
384
620
|
const mcpServer = path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
|
|
385
621
|
const { startStdioServer } = require(mcpServer);
|
|
386
622
|
startStdioServer();
|
|
623
|
+
// Start watcher as a background daemon alongside MCP server
|
|
624
|
+
try {
|
|
625
|
+
const { watch } = require(path.join(PKG_ROOT, 'scripts', 'jsonl-watcher'));
|
|
626
|
+
watch();
|
|
627
|
+
} catch (_) { /* watcher is non-critical */ }
|
|
387
628
|
}
|
|
388
629
|
|
|
389
630
|
function install() {
|
|
@@ -392,7 +633,8 @@ function install() {
|
|
|
392
633
|
setupClaude(),
|
|
393
634
|
setupCodex(),
|
|
394
635
|
setupGemini(),
|
|
395
|
-
setupCursor()
|
|
636
|
+
setupCursor(),
|
|
637
|
+
setupAmp()
|
|
396
638
|
];
|
|
397
639
|
const success = results.some(r => r === true);
|
|
398
640
|
if (success) {
|
|
@@ -403,6 +645,12 @@ function install() {
|
|
|
403
645
|
}
|
|
404
646
|
}
|
|
405
647
|
|
|
648
|
+
function installMcp() {
|
|
649
|
+
const { installMcp: doInstall, parseFlags } = require(path.join(PKG_ROOT, 'scripts', 'install-mcp'));
|
|
650
|
+
const flags = parseFlags(process.argv.slice(3));
|
|
651
|
+
doInstall(flags);
|
|
652
|
+
}
|
|
653
|
+
|
|
406
654
|
function startApi() {
|
|
407
655
|
const serverPath = path.join(PKG_ROOT, 'src', 'api', 'server.js');
|
|
408
656
|
try {
|
|
@@ -418,21 +666,28 @@ function help() {
|
|
|
418
666
|
console.log('');
|
|
419
667
|
console.log('Commands:');
|
|
420
668
|
console.log(' init Scaffold .rlhf/ config + MCP server in current project');
|
|
669
|
+
console.log(' install-mcp Install RLHF MCP server into Claude Code settings (--project for local)');
|
|
421
670
|
console.log(' serve Start MCP server (stdio) — for claude/codex/gemini mcp add');
|
|
422
671
|
console.log(' capture [flags] Capture feedback (--feedback=up|down --context="..." --tags="...")');
|
|
423
672
|
console.log(' stats Show feedback analytics + Revenue-at-Risk');
|
|
424
673
|
console.log(' summary Human-readable feedback summary');
|
|
674
|
+
console.log(' model-fit Detect the current local embedding profile and write evidence report');
|
|
675
|
+
console.log(' risk [flags] Train or query the boosted local risk scorer');
|
|
425
676
|
console.log(' export-dpo Export DPO training pairs (prompt/chosen/rejected JSONL)');
|
|
426
677
|
console.log(' rules Generate prevention rules from repeated failures');
|
|
427
678
|
console.log(' self-heal Run self-healing check and auto-fix');
|
|
428
679
|
console.log(' pro Upgrade to Cloud Pro ($10/mo)');
|
|
429
|
-
console.log(' prove [--target=X] Run proof harness (adapters|automation|attribution|lancedb|...)');
|
|
680
|
+
console.log(' prove [--target=X] Run proof harness (adapters|automation|attribution|lancedb|local-intelligence|...)');
|
|
681
|
+
console.log(' watch [flags] Watch .rlhf/ for external signals and ingest through pipeline (--once, --source=X)');
|
|
682
|
+
console.log(' status Show learning curve dashboard — approval trend + failure domains');
|
|
430
683
|
console.log(' start-api Start the RLHF HTTPS API server');
|
|
431
684
|
console.log(' help Show this help message');
|
|
432
685
|
console.log('');
|
|
433
686
|
console.log('Examples:');
|
|
434
687
|
console.log(' npx rlhf-feedback-loop init');
|
|
435
688
|
console.log(' npx rlhf-feedback-loop stats');
|
|
689
|
+
console.log(' npx rlhf-feedback-loop model-fit');
|
|
690
|
+
console.log(' npx rlhf-feedback-loop risk');
|
|
436
691
|
console.log(' npx rlhf-feedback-loop pro');
|
|
437
692
|
}
|
|
438
693
|
|
|
@@ -443,6 +698,9 @@ switch (COMMAND) {
|
|
|
443
698
|
case 'install':
|
|
444
699
|
install();
|
|
445
700
|
break;
|
|
701
|
+
case 'install-mcp':
|
|
702
|
+
installMcp();
|
|
703
|
+
break;
|
|
446
704
|
case 'serve':
|
|
447
705
|
case 'mcp':
|
|
448
706
|
serve();
|
|
@@ -457,6 +715,12 @@ switch (COMMAND) {
|
|
|
457
715
|
case 'summary':
|
|
458
716
|
summary();
|
|
459
717
|
break;
|
|
718
|
+
case 'model-fit':
|
|
719
|
+
modelFit();
|
|
720
|
+
break;
|
|
721
|
+
case 'risk':
|
|
722
|
+
risk();
|
|
723
|
+
break;
|
|
460
724
|
case 'export-dpo':
|
|
461
725
|
case 'dpo':
|
|
462
726
|
exportDpo();
|
|
@@ -473,6 +737,12 @@ switch (COMMAND) {
|
|
|
473
737
|
case 'prove':
|
|
474
738
|
prove();
|
|
475
739
|
break;
|
|
740
|
+
case 'watch':
|
|
741
|
+
watchCmd();
|
|
742
|
+
break;
|
|
743
|
+
case 'status':
|
|
744
|
+
status();
|
|
745
|
+
break;
|
|
476
746
|
case 'start-api':
|
|
477
747
|
startApi();
|
|
478
748
|
break;
|
package/openapi/openapi.yaml
CHANGED
|
@@ -5,6 +5,8 @@ info:
|
|
|
5
5
|
description: |
|
|
6
6
|
Production API for feedback capture, schema-validated memory promotion,
|
|
7
7
|
prevention rule generation, and DPO export.
|
|
8
|
+
Bare up/down signals are logged immediately, but the API returns
|
|
9
|
+
clarification_required until one sentence explains what worked or failed.
|
|
8
10
|
servers:
|
|
9
11
|
- url: https://rlhf-feedback-loop-710216278770.us-central1.run.app
|
|
10
12
|
security:
|
|
@@ -32,13 +34,14 @@ components:
|
|
|
32
34
|
type: string
|
|
33
35
|
CaptureFeedbackRequest:
|
|
34
36
|
type: object
|
|
35
|
-
required: [signal
|
|
37
|
+
required: [signal]
|
|
36
38
|
properties:
|
|
37
39
|
signal:
|
|
38
40
|
type: string
|
|
39
41
|
enum: [up, down, positive, negative]
|
|
40
42
|
context:
|
|
41
43
|
type: string
|
|
44
|
+
description: One-sentence reason describing what worked or failed
|
|
42
45
|
whatWentWrong:
|
|
43
46
|
type: string
|
|
44
47
|
whatToChange:
|
|
@@ -155,7 +158,7 @@ paths:
|
|
|
155
158
|
'200':
|
|
156
159
|
description: Feedback accepted and promoted to memory
|
|
157
160
|
'422':
|
|
158
|
-
description: Feedback
|
|
161
|
+
description: Feedback logged only; clarification required or promotion rejected
|
|
159
162
|
'401':
|
|
160
163
|
description: Unauthorized
|
|
161
164
|
/v1/feedback/stats:
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rlhf-feedback-loop",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "
|
|
5
|
-
"homepage": "https://
|
|
3
|
+
"version": "0.6.12",
|
|
4
|
+
"description": "Feedback-Driven Development (FDD) for AI agents — capture preference signals, steer behavior via Thompson Sampling, and export KTO/DPO training pairs for downstream fine-tuning.",
|
|
5
|
+
"homepage": "https://rlhf-feedback-loop-production.up.railway.app",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/IgorGanapolsky/rlhf-feedback-loop.git"
|
|
@@ -28,38 +28,45 @@
|
|
|
28
28
|
],
|
|
29
29
|
"type": "commonjs",
|
|
30
30
|
"scripts": {
|
|
31
|
-
"test": "npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:billing && npm run test:cli",
|
|
31
|
+
"test": "npm run test:schema && npm run test:loop && npm run test:dpo && npm run test:kto && npm run test:api && npm run test:proof && npm run test:e2e && npm run test:rlaif && npm run test:attribution && npm run test:quality && npm run test:intelligence && npm run test:training-export && npm run test:deployment && npm run test:workflow && npm run test:billing && npm run test:cli && npm run test:watcher",
|
|
32
32
|
"test:e2e": "node --test tests/e2e-pipeline.test.js",
|
|
33
33
|
"test:schema": "node scripts/feedback-schema.js --test",
|
|
34
34
|
"test:loop": "node scripts/feedback-loop.js --test",
|
|
35
35
|
"test:dpo": "node scripts/export-dpo-pairs.js --test",
|
|
36
|
-
"test:
|
|
37
|
-
"test:
|
|
36
|
+
"test:kto": "node --test tests/export-kto.test.js",
|
|
37
|
+
"test:api": "node --test tests/api-server.test.js tests/api-auth-config.test.js tests/mcp-server.test.js tests/adapters.test.js tests/openapi-parity.test.js tests/budget-guard.test.js tests/contextfs.test.js tests/mcp-policy.test.js tests/subagent-profiles.test.js tests/intent-router.test.js tests/rubric-engine.test.js tests/self-healing-check.test.js tests/self-heal.test.js tests/feedback-schema.test.js tests/thompson-sampling.test.js tests/feedback-sequences.test.js tests/diversity-tracking.test.js tests/vector-store.test.js tests/feedback-attribution.test.js tests/hybrid-feedback-context.test.js tests/loop-closure.test.js tests/code-reasoning.test.js tests/feedback-loop.test.js tests/feedback-inbox-read.test.js tests/feedback-to-memory.test.js tests/test-coverage.test.js tests/version-metadata.test.js tests/local-model-profile.test.js tests/risk-scorer.test.js tests/context-compaction.test.js tests/reminder-engine.test.js",
|
|
38
|
+
"test:proof": "node --test --test-concurrency=1 tests/prove-adapters.test.js tests/prove-automation.test.js tests/prove-attribution.test.js tests/prove-lancedb.test.js tests/prove-data-quality.test.js tests/prove-intelligence.test.js tests/prove-loop-closure.test.js tests/prove-subway-upgrades.test.js tests/prove-training-export.test.js tests/prove-local-intelligence.test.js tests/prove-workflow-contract.test.js",
|
|
38
39
|
"test:rlaif": "node --test tests/rlaif-self-audit.test.js tests/dpo-optimizer.test.js tests/meta-policy.test.js",
|
|
39
40
|
"test:attribution": "node --test tests/feedback-attribution.test.js tests/hybrid-feedback-context.test.js",
|
|
40
41
|
"test:quality": "node --test tests/validate-feedback.test.js",
|
|
41
42
|
"test:intelligence": "node --test tests/intelligence.test.js",
|
|
42
43
|
"test:training-export": "node --test tests/training-export.test.js",
|
|
43
44
|
"test:deployment": "node --test tests/deployment.test.js",
|
|
45
|
+
"test:workflow": "node --test tests/workflow-contract.test.js",
|
|
44
46
|
"test:billing": "node --test tests/billing.test.js",
|
|
45
|
-
"test:cli": "node --test tests/cli.test.js",
|
|
47
|
+
"test:cli": "node --test tests/cli.test.js tests/feedback-normalize.test.js tests/install-mcp.test.js",
|
|
48
|
+
"test:coverage": "node scripts/test-coverage.js",
|
|
46
49
|
"start:api": "node src/api/server.js",
|
|
47
50
|
"start:mcp": "node adapters/mcp/server-stdio.js",
|
|
51
|
+
"mcp:install": "node scripts/install-mcp.js",
|
|
48
52
|
"feedback:capture": "node .claude/scripts/feedback/capture-feedback.js",
|
|
49
53
|
"feedback:stats": "node .claude/scripts/feedback/capture-feedback.js --stats",
|
|
50
54
|
"feedback:summary": "node .claude/scripts/feedback/capture-feedback.js --summary",
|
|
51
55
|
"feedback:rules": "node .claude/scripts/feedback/capture-feedback.js --rules",
|
|
52
56
|
"feedback:export:dpo": "node scripts/export-dpo-pairs.js --from-local --output .claude/memory/feedback/dpo-pairs.jsonl",
|
|
57
|
+
"feedback:export:kto": "node scripts/export-kto-pairs.js --from-local --output .claude/memory/feedback/kto-pairs.jsonl",
|
|
53
58
|
"budget:status": "node scripts/budget-guard.js --status",
|
|
54
59
|
"diagrams:paperbanana": "bash scripts/generate-paperbanana-diagrams.sh",
|
|
55
60
|
"prove:adapters": "node scripts/prove-adapters.js",
|
|
56
61
|
"prove:automation": "node scripts/prove-automation.js",
|
|
62
|
+
"prove:workflow-contract": "node scripts/prove-workflow-contract.js",
|
|
57
63
|
"prove:lancedb": "node scripts/prove-lancedb.js",
|
|
58
64
|
"prove:rlaif": "node scripts/prove-rlaif.js",
|
|
59
65
|
"prove:attribution": "node scripts/prove-attribution.js",
|
|
60
66
|
"prove:data-quality": "node scripts/prove-data-quality.js",
|
|
61
67
|
"prove:loop-closure": "node scripts/prove-loop-closure.js",
|
|
62
68
|
"prove:intelligence": "node scripts/prove-intelligence.js",
|
|
69
|
+
"prove:local-intelligence": "node scripts/prove-local-intelligence.js",
|
|
63
70
|
"prove:training-export": "node scripts/prove-training-export.js",
|
|
64
71
|
"prove:v2-milestone": "node scripts/prove-v2-milestone.js",
|
|
65
72
|
"feedback:export:pytorch": "node scripts/export-training.js --pytorch",
|
|
@@ -75,7 +82,15 @@
|
|
|
75
82
|
"ml:train": "python3 scripts/train_from_feedback.py --train",
|
|
76
83
|
"ml:incremental": "python3 scripts/train_from_feedback.py --incremental",
|
|
77
84
|
"ml:reliability": "python3 scripts/train_from_feedback.py --reliability",
|
|
78
|
-
"ml:sample": "python3 scripts/train_from_feedback.py --sample"
|
|
85
|
+
"ml:sample": "python3 scripts/train_from_feedback.py --sample",
|
|
86
|
+
"ml:model-fit": "node scripts/local-model-profile.js",
|
|
87
|
+
"ml:risk": "node scripts/risk-scorer.js",
|
|
88
|
+
"adk:consolidate": "node scripts/adk-consolidator.js",
|
|
89
|
+
"adk:watch": "node scripts/adk-consolidator.js --watch",
|
|
90
|
+
"pr:manage": "node scripts/pr-manager.js",
|
|
91
|
+
"watch": "node scripts/jsonl-watcher.js",
|
|
92
|
+
"status": "node scripts/status-dashboard.js",
|
|
93
|
+
"test:watcher": "node --test tests/jsonl-watcher.test.js"
|
|
79
94
|
},
|
|
80
95
|
"keywords": [
|
|
81
96
|
"rlhf",
|
|
@@ -115,10 +130,11 @@
|
|
|
115
130
|
"node": ">=18.18.0"
|
|
116
131
|
},
|
|
117
132
|
"dependencies": {
|
|
133
|
+
"@google/genai": "^1.44.0",
|
|
118
134
|
"@huggingface/transformers": "^3.8.1",
|
|
119
135
|
"@lancedb/lancedb": "^0.26.2",
|
|
120
136
|
"apache-arrow": "^18.1.0",
|
|
121
137
|
"stripe": "^20.4.1"
|
|
122
138
|
},
|
|
123
|
-
"mcpName": "io.github.
|
|
139
|
+
"mcpName": "io.github.igorganapolsky/rlhf-feedback-loop"
|
|
124
140
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2UI (Agent-to-User Interface) Component Definitions
|
|
3
|
+
*
|
|
4
|
+
* Standardized schema for dynamic UI components generated by the agent.
|
|
5
|
+
* These are intended to be rendered in the Feedback Studio or as MCP Artifacts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const COMPONENT_TYPES = {
|
|
14
|
+
REASONING_TRACE: 'reasoning-trace', // Graph/List showing how dots were connected
|
|
15
|
+
RULE_PROPOSAL: 'rule-proposal', // Interactive card to approve/refine a rule
|
|
16
|
+
CONFLICT_VETO: 'conflict-veto', // UI to resolve contradicting memories
|
|
17
|
+
METRIC_DYNAMIC: 'metric-dynamic' // Real-time custom metrics based on current task
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generate a Reasoning Trace component (A2UI)
|
|
22
|
+
* @param {string} summary - High-level synthesis
|
|
23
|
+
* @param {Array} sources - List of source logs/memories
|
|
24
|
+
* @param {Array} connections - Semantic links found
|
|
25
|
+
*/
|
|
26
|
+
function createReasoningTrace(summary, sources, connections) {
|
|
27
|
+
return {
|
|
28
|
+
type: COMPONENT_TYPES.REASONING_TRACE,
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
data: {
|
|
31
|
+
summary,
|
|
32
|
+
sources: sources.map(s => ({
|
|
33
|
+
id: s.id,
|
|
34
|
+
text: s.context || s.content,
|
|
35
|
+
signal: s.signal || 'neutral'
|
|
36
|
+
})),
|
|
37
|
+
graph: connections.map(c => ({
|
|
38
|
+
from: c.sourceId,
|
|
39
|
+
to: c.targetId,
|
|
40
|
+
label: c.relation
|
|
41
|
+
}))
|
|
42
|
+
},
|
|
43
|
+
actions: [
|
|
44
|
+
{ id: 'view-logs', label: 'View Raw Logs', type: 'primary' }
|
|
45
|
+
]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a Rule Proposal component (A2UI)
|
|
51
|
+
*/
|
|
52
|
+
function createRuleProposal(pattern, suggestedRule, severity) {
|
|
53
|
+
return {
|
|
54
|
+
type: COMPONENT_TYPES.RULE_PROPOSAL,
|
|
55
|
+
version: '1.0.0',
|
|
56
|
+
data: {
|
|
57
|
+
pattern,
|
|
58
|
+
suggestedRule,
|
|
59
|
+
severity
|
|
60
|
+
},
|
|
61
|
+
actions: [
|
|
62
|
+
{ id: 'approve', label: 'Approve ALWAYS/NEVER', type: 'success' },
|
|
63
|
+
{ id: 'refine', label: 'Tweak Wording', type: 'secondary' },
|
|
64
|
+
{ id: 'veto', label: 'Veto Rule', type: 'danger' }
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
COMPONENT_TYPES,
|
|
71
|
+
createReasoningTrace,
|
|
72
|
+
createRuleProposal
|
|
73
|
+
};
|