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.
Files changed (53) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +120 -74
  3. package/adapters/README.md +3 -3
  4. package/adapters/amp/skills/rlhf-feedback/SKILL.md +2 -0
  5. package/adapters/chatgpt/INSTALL.md +6 -3
  6. package/adapters/chatgpt/openapi.yaml +5 -2
  7. package/adapters/claude/.mcp.json +3 -3
  8. package/adapters/codex/config.toml +3 -3
  9. package/adapters/gemini/function-declarations.json +2 -2
  10. package/adapters/mcp/server-stdio.js +19 -5
  11. package/bin/cli.js +295 -25
  12. package/openapi/openapi.yaml +5 -2
  13. package/package.json +25 -9
  14. package/scripts/a2ui-engine.js +73 -0
  15. package/scripts/adk-consolidator.js +267 -0
  16. package/scripts/billing.js +192 -681
  17. package/scripts/code-reasoning.js +26 -1
  18. package/scripts/context-engine.js +86 -4
  19. package/scripts/contextfs.js +130 -0
  20. package/scripts/disagreement-mining.js +315 -0
  21. package/scripts/export-kto-pairs.js +310 -0
  22. package/scripts/feedback-ingest-watcher.js +290 -0
  23. package/scripts/feedback-loop.js +153 -8
  24. package/scripts/feedback-quality.js +139 -0
  25. package/scripts/feedback-schema.js +31 -5
  26. package/scripts/feedback-to-memory.js +13 -1
  27. package/scripts/hook-auto-capture.sh +6 -0
  28. package/scripts/hook-stop-self-score.sh +51 -0
  29. package/scripts/install-mcp.js +168 -0
  30. package/scripts/intent-router.js +88 -0
  31. package/scripts/jsonl-watcher.js +151 -0
  32. package/scripts/local-model-profile.js +207 -0
  33. package/scripts/pr-manager.js +112 -0
  34. package/scripts/prove-adapters.js +137 -15
  35. package/scripts/prove-attribution.js +6 -6
  36. package/scripts/prove-automation.js +41 -8
  37. package/scripts/prove-data-quality.js +16 -8
  38. package/scripts/prove-intelligence.js +7 -4
  39. package/scripts/prove-lancedb.js +7 -7
  40. package/scripts/prove-local-intelligence.js +244 -0
  41. package/scripts/prove-loop-closure.js +16 -8
  42. package/scripts/prove-training-export.js +7 -4
  43. package/scripts/prove-workflow-contract.js +116 -0
  44. package/scripts/reminder-engine.js +132 -0
  45. package/scripts/risk-scorer.js +458 -0
  46. package/scripts/rlaif-self-audit.js +7 -1
  47. package/scripts/self-heal.js +24 -4
  48. package/scripts/status-dashboard.js +155 -0
  49. package/scripts/sync-version.js +159 -0
  50. package/scripts/test-coverage.js +76 -0
  51. package/scripts/validate-workflow-contract.js +287 -0
  52. package/scripts/vector-store.js +115 -17
  53. package/src/api/server.js +372 -25
package/src/api/server.js CHANGED
@@ -32,9 +32,11 @@ const {
32
32
  } = require('../../scripts/intent-router');
33
33
  const {
34
34
  createCheckoutSession,
35
+ getCheckoutSessionStatus,
35
36
  provisionApiKey,
36
37
  validateApiKey,
37
38
  recordUsage,
39
+ rotateApiKey,
38
40
  handleWebhook,
39
41
  verifyWebhookSignature,
40
42
  verifyGithubWebhookSignature,
@@ -42,6 +44,8 @@ const {
42
44
  getFunnelAnalytics,
43
45
  } = require('../../scripts/billing');
44
46
 
47
+ const LANDING_PAGE_PATH = path.resolve(__dirname, '../../docs/landing-page.html');
48
+
45
49
  function getSafeDataDir() {
46
50
  const { FEEDBACK_LOG_PATH } = getFeedbackPaths();
47
51
  return path.resolve(path.dirname(FEEDBACK_LOG_PATH));
@@ -70,6 +74,284 @@ function sendText(res, statusCode, text) {
70
74
  res.end(text);
71
75
  }
72
76
 
77
+ function sendHtml(res, statusCode, html) {
78
+ res.writeHead(statusCode, {
79
+ 'Content-Type': 'text/html; charset=utf-8',
80
+ 'Content-Length': Buffer.byteLength(html),
81
+ });
82
+ res.end(html);
83
+ }
84
+
85
+ function getPublicOrigin(req) {
86
+ const proto = String(req.headers['x-forwarded-proto'] || '').split(',')[0].trim() || 'http';
87
+ const host = String(req.headers['x-forwarded-host'] || req.headers.host || '').split(',')[0].trim() || 'localhost';
88
+ return `${proto}://${host}`;
89
+ }
90
+
91
+ function wantsJson(req, parsed) {
92
+ if (parsed.searchParams.get('format') === 'json') {
93
+ return true;
94
+ }
95
+
96
+ const accept = String(req.headers.accept || '');
97
+ return accept.includes('application/json') && !accept.includes('text/html');
98
+ }
99
+
100
+ function fillTemplate(template, replacements) {
101
+ let output = template;
102
+ for (const [token, value] of Object.entries(replacements)) {
103
+ output = output.split(token).join(String(value));
104
+ }
105
+ return output;
106
+ }
107
+
108
+ function loadLandingPageHtml(origin) {
109
+ const template = fs.readFileSync(LANDING_PAGE_PATH, 'utf-8');
110
+ return fillTemplate(template, {
111
+ '__PACKAGE_VERSION__': pkg.version,
112
+ '__APP_ORIGIN__': origin,
113
+ '__CHECKOUT_ENDPOINT__': '/v1/billing/checkout',
114
+ '__CHECKOUT_FALLBACK_URL__': 'https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02',
115
+ '__VERIFICATION_URL__': 'https://github.com/IgorGanapolsky/mcp-memory-gateway/blob/main/docs/VERIFICATION_EVIDENCE.md',
116
+ '__COMPATIBILITY_REPORT_URL__': 'https://github.com/IgorGanapolsky/mcp-memory-gateway/blob/main/proof/compatibility/report.json',
117
+ '__AUTOMATION_REPORT_URL__': 'https://github.com/IgorGanapolsky/mcp-memory-gateway/blob/main/proof/automation/report.json',
118
+ '__GTM_PLAN_URL__': 'https://github.com/IgorGanapolsky/mcp-memory-gateway/blob/main/docs/GO_TO_MARKET_REVENUE_WEDGE_2026-03.md',
119
+ '__GITHUB_URL__': 'https://github.com/IgorGanapolsky/mcp-memory-gateway',
120
+ });
121
+ }
122
+
123
+ function renderCheckoutSuccessPage() {
124
+ return `<!DOCTYPE html>
125
+ <html lang="en">
126
+ <head>
127
+ <meta charset="UTF-8" />
128
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
129
+ <title>Cloud Pro Activated</title>
130
+ <style>
131
+ :root {
132
+ --bg: #f6f1e8;
133
+ --ink: #1d1b18;
134
+ --muted: #625a4d;
135
+ --line: #d7cfbf;
136
+ --accent: #b85c2d;
137
+ --accent-dark: #8f451f;
138
+ --card: #fffdf9;
139
+ --success: #2f7d4b;
140
+ --radius: 14px;
141
+ }
142
+ * { box-sizing: border-box; }
143
+ body {
144
+ margin: 0;
145
+ font-family: Georgia, 'Times New Roman', serif;
146
+ background: linear-gradient(180deg, #fcfaf5 0%, var(--bg) 100%);
147
+ color: var(--ink);
148
+ line-height: 1.6;
149
+ }
150
+ main {
151
+ max-width: 860px;
152
+ margin: 0 auto;
153
+ padding: 48px 20px 80px;
154
+ }
155
+ .eyebrow {
156
+ display: inline-block;
157
+ padding: 6px 12px;
158
+ border-radius: 999px;
159
+ background: #efe3d5;
160
+ color: var(--accent-dark);
161
+ font-size: 12px;
162
+ letter-spacing: 0.08em;
163
+ text-transform: uppercase;
164
+ font-weight: 700;
165
+ }
166
+ h1 {
167
+ margin: 18px 0 12px;
168
+ font-size: clamp(32px, 6vw, 56px);
169
+ line-height: 1.05;
170
+ letter-spacing: -0.04em;
171
+ }
172
+ p.lead {
173
+ max-width: 700px;
174
+ font-size: 19px;
175
+ color: var(--muted);
176
+ margin: 0 0 28px;
177
+ }
178
+ .card {
179
+ background: var(--card);
180
+ border: 1px solid var(--line);
181
+ border-radius: var(--radius);
182
+ padding: 24px;
183
+ margin-top: 22px;
184
+ box-shadow: 0 10px 30px rgba(29, 27, 24, 0.08);
185
+ }
186
+ .status {
187
+ color: var(--success);
188
+ font-weight: 700;
189
+ margin-bottom: 8px;
190
+ }
191
+ pre {
192
+ white-space: pre-wrap;
193
+ word-break: break-word;
194
+ background: #171411;
195
+ color: #f5efe6;
196
+ padding: 16px;
197
+ border-radius: 12px;
198
+ overflow-x: auto;
199
+ font-family: 'SFMono-Regular', Consolas, monospace;
200
+ font-size: 13px;
201
+ }
202
+ .actions {
203
+ display: flex;
204
+ gap: 12px;
205
+ flex-wrap: wrap;
206
+ margin-top: 18px;
207
+ }
208
+ a.button {
209
+ display: inline-block;
210
+ text-decoration: none;
211
+ background: var(--accent);
212
+ color: white;
213
+ padding: 12px 18px;
214
+ border-radius: 10px;
215
+ font-weight: 700;
216
+ }
217
+ a.button.secondary {
218
+ background: transparent;
219
+ color: var(--ink);
220
+ border: 1px solid var(--line);
221
+ }
222
+ .muted {
223
+ color: var(--muted);
224
+ font-size: 14px;
225
+ }
226
+ </style>
227
+ </head>
228
+ <body>
229
+ <main>
230
+ <span class="eyebrow">Cloud Pro</span>
231
+ <h1>Your hosted API key is ready.</h1>
232
+ <p class="lead">This page verifies your Stripe session, provisions the key if needed, and gives you a copy-paste onboarding snippet for the hosted API.</p>
233
+
234
+ <div class="card">
235
+ <div class="status" id="status">Verifying payment and provisioning your key...</div>
236
+ <p class="muted" id="summary">Do not close this tab until the key appears.</p>
237
+ <pre id="key-block">Waiting for checkout session...</pre>
238
+ </div>
239
+
240
+ <div class="card">
241
+ <h2>Next steps</h2>
242
+ <ol>
243
+ <li>Copy the environment block below into your workflow runner.</li>
244
+ <li>Use the curl example to confirm the hosted API captures an event.</li>
245
+ <li>Keep your key private and rotate by repurchasing or contacting support if needed.</li>
246
+ </ol>
247
+ <pre id="env-block">Waiting for provisioning...</pre>
248
+ <pre id="curl-block">Waiting for provisioning...</pre>
249
+ <div class="actions">
250
+ <a class="button" href="/">Back to landing page</a>
251
+ <a class="button secondary" href="https://github.com/IgorGanapolsky/rlhf-feedback-loop/blob/main/docs/VERIFICATION_EVIDENCE.md" target="_blank" rel="noreferrer">Verification evidence</a>
252
+ </div>
253
+ </div>
254
+ </main>
255
+
256
+ <script>
257
+ const params = new URLSearchParams(window.location.search);
258
+ const sessionId = params.get('session_id');
259
+ const statusEl = document.getElementById('status');
260
+ const summaryEl = document.getElementById('summary');
261
+ const keyBlock = document.getElementById('key-block');
262
+ const envBlock = document.getElementById('env-block');
263
+ const curlBlock = document.getElementById('curl-block');
264
+
265
+ async function run() {
266
+ if (!sessionId) {
267
+ statusEl.textContent = 'Missing checkout session.';
268
+ summaryEl.textContent = 'Open the landing page and start a new checkout.';
269
+ keyBlock.textContent = 'No session_id was provided in the URL.';
270
+ return;
271
+ }
272
+
273
+ try {
274
+ const res = await fetch('/v1/billing/session?sessionId=' + encodeURIComponent(sessionId));
275
+ const body = await res.json();
276
+ if (!res.ok) {
277
+ throw new Error(body.error || 'Unable to load checkout session.');
278
+ }
279
+
280
+ if (!body.paid) {
281
+ statusEl.textContent = 'Payment is still processing.';
282
+ summaryEl.textContent = 'Refresh this page in a few seconds if Stripe has already confirmed payment.';
283
+ keyBlock.textContent = JSON.stringify(body, null, 2);
284
+ return;
285
+ }
286
+
287
+ statusEl.textContent = 'Cloud Pro activated.';
288
+ summaryEl.textContent = 'Your API key is ready. Copy the snippets below into your workflow project.';
289
+ keyBlock.textContent = body.apiKey || 'Provisioned, but no key was returned.';
290
+ envBlock.textContent = body.nextSteps && body.nextSteps.env ? body.nextSteps.env : 'Environment snippet unavailable.';
291
+ curlBlock.textContent = body.nextSteps && body.nextSteps.curl ? body.nextSteps.curl : 'curl snippet unavailable.';
292
+ } catch (err) {
293
+ statusEl.textContent = 'Provisioning lookup failed.';
294
+ summaryEl.textContent = 'You can retry this page. If it keeps failing, inspect the hosted API logs.';
295
+ keyBlock.textContent = err && err.message ? err.message : 'Unknown error';
296
+ }
297
+ }
298
+
299
+ run();
300
+ </script>
301
+ </body>
302
+ </html>`;
303
+ }
304
+
305
+ function renderCheckoutCancelledPage() {
306
+ return `<!DOCTYPE html>
307
+ <html lang="en">
308
+ <head>
309
+ <meta charset="UTF-8" />
310
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
311
+ <title>Checkout Cancelled</title>
312
+ <style>
313
+ body {
314
+ margin: 0;
315
+ font-family: Georgia, 'Times New Roman', serif;
316
+ background: #f6f1e8;
317
+ color: #1d1b18;
318
+ }
319
+ main {
320
+ max-width: 720px;
321
+ margin: 0 auto;
322
+ padding: 64px 20px 80px;
323
+ }
324
+ h1 {
325
+ font-size: clamp(32px, 6vw, 52px);
326
+ line-height: 1.05;
327
+ margin: 0 0 14px;
328
+ }
329
+ p {
330
+ font-size: 18px;
331
+ color: #625a4d;
332
+ margin: 0 0 20px;
333
+ }
334
+ a {
335
+ display: inline-block;
336
+ text-decoration: none;
337
+ background: #b85c2d;
338
+ color: white;
339
+ padding: 12px 18px;
340
+ border-radius: 10px;
341
+ font-weight: 700;
342
+ }
343
+ </style>
344
+ </head>
345
+ <body>
346
+ <main>
347
+ <h1>Checkout cancelled.</h1>
348
+ <p>No charge was made. You can return to the landing page and restart checkout whenever you are ready.</p>
349
+ <a href="/">Return to Cloud Pro</a>
350
+ </main>
351
+ </body>
352
+ </html>`;
353
+ }
354
+
73
355
  function parseJsonBody(req, maxBytes = 1024 * 1024) {
74
356
  return new Promise((resolve, reject) => {
75
357
  let total = 0;
@@ -193,16 +475,36 @@ function createApiServer() {
193
475
  return http.createServer(async (req, res) => {
194
476
  const parsed = new URL(req.url, 'http://localhost');
195
477
  const pathname = parsed.pathname;
478
+ const publicOrigin = getPublicOrigin(req);
196
479
 
197
480
  // Public endpoints — no auth required
198
481
  if (req.method === 'GET' && pathname === '/') {
199
- sendJson(res, 200, {
200
- name: 'rlhf-feedback-loop',
201
- version: pkg.version,
202
- status: 'ok',
203
- docs: 'https://github.com/IgorGanapolsky/rlhf-feedback-loop',
204
- endpoints: ['/health', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/dpo/export'],
205
- });
482
+ if (wantsJson(req, parsed)) {
483
+ sendJson(res, 200, {
484
+ name: 'rlhf-feedback-loop',
485
+ version: pkg.version,
486
+ status: 'ok',
487
+ docs: 'https://github.com/IgorGanapolsky/rlhf-feedback-loop',
488
+ endpoints: ['/health', '/v1/feedback/capture', '/v1/feedback/stats', '/v1/dpo/export'],
489
+ });
490
+ return;
491
+ }
492
+
493
+ try {
494
+ sendHtml(res, 200, loadLandingPageHtml(publicOrigin));
495
+ } catch (err) {
496
+ sendText(res, 500, err.message || 'Landing page unavailable');
497
+ }
498
+ return;
499
+ }
500
+
501
+ if (req.method === 'GET' && pathname === '/success') {
502
+ sendHtml(res, 200, renderCheckoutSuccessPage());
503
+ return;
504
+ }
505
+
506
+ if (req.method === 'GET' && pathname === '/cancel') {
507
+ sendHtml(res, 200, renderCheckoutCancelledPage());
206
508
  return;
207
509
  }
208
510
 
@@ -213,7 +515,7 @@ function createApiServer() {
213
515
  version: pkg.version,
214
516
  tools: [
215
517
  { name: 'recall', description: 'Recall relevant past feedback for current task' },
216
- { name: 'capture_feedback', description: 'Capture thumbs up/down feedback' },
518
+ { name: 'capture_feedback', description: 'Capture an up/down signal plus one line of why' },
217
519
  { name: 'feedback_stats', description: 'Feedback analytics' },
218
520
  { name: 'feedback_summary', description: 'Human-readable feedback summary' },
219
521
  { name: 'prevention_rules', description: 'Generate prevention rules from failures' },
@@ -260,21 +562,8 @@ function createApiServer() {
260
562
  });
261
563
 
262
564
  const sig = req.headers['stripe-signature'] || '';
263
- if (!verifyWebhookSignature(rawBody, sig)) {
264
- sendJson(res, 400, { error: 'Invalid webhook signature' });
265
- return;
266
- }
267
-
268
- let event;
269
- try {
270
- event = JSON.parse(rawBody.toString('utf-8'));
271
- } catch {
272
- sendJson(res, 400, { error: 'Invalid JSON in webhook body' });
273
- return;
274
- }
275
-
276
- const result = handleWebhook(event);
277
- sendJson(res, 200, result);
565
+ const result = await handleWebhook(rawBody, sig);
566
+ sendJson(res, result.handled ? 200 : 400, result);
278
567
  } catch (err) {
279
568
  if (err.statusCode) {
280
569
  sendJson(res, err.statusCode, { error: err.message });
@@ -326,8 +615,8 @@ function createApiServer() {
326
615
  try {
327
616
  const body = await parseJsonBody(req);
328
617
  const result = await createCheckoutSession({
329
- successUrl: body.successUrl,
330
- cancelUrl: body.cancelUrl,
618
+ successUrl: body.successUrl || `${publicOrigin}/success?session_id={CHECKOUT_SESSION_ID}`,
619
+ cancelUrl: body.cancelUrl || `${publicOrigin}/cancel`,
331
620
  customerEmail: body.customerEmail,
332
621
  installId: body.installId,
333
622
  metadata: body.metadata,
@@ -343,6 +632,36 @@ function createApiServer() {
343
632
  return;
344
633
  }
345
634
 
635
+ if (req.method === 'GET' && pathname === '/v1/billing/session') {
636
+ try {
637
+ const sessionId = parsed.searchParams.get('sessionId');
638
+ if (!sessionId) {
639
+ throw createHttpError(400, 'sessionId is required');
640
+ }
641
+
642
+ const result = await getCheckoutSessionStatus(sessionId);
643
+ if (!result.found) {
644
+ throw createHttpError(404, 'Checkout session not found');
645
+ }
646
+
647
+ sendJson(res, 200, {
648
+ ...result,
649
+ apiBaseUrl: publicOrigin,
650
+ nextSteps: {
651
+ env: `RLHF_API_KEY=${result.apiKey || ''}\nRLHF_API_BASE_URL=${publicOrigin}`,
652
+ curl: `curl -X POST ${publicOrigin}/v1/feedback/capture \\\n -H 'Authorization: Bearer ${result.apiKey || ''}' \\\n -H 'Content-Type: application/json' \\\n -d '{"signal":"down","context":"example","whatWentWrong":"example","whatToChange":"example"}'`,
653
+ },
654
+ });
655
+ } catch (err) {
656
+ if (err.statusCode) {
657
+ sendJson(res, err.statusCode, { error: err.message });
658
+ } else {
659
+ sendJson(res, 500, { error: err.message || 'Internal Server Error' });
660
+ }
661
+ }
662
+ return;
663
+ }
664
+
346
665
  if (!isAuthorized(req, expectedApiKey)) {
347
666
  sendJson(res, 401, { error: 'Unauthorized' });
348
667
  return;
@@ -553,6 +872,34 @@ function createApiServer() {
553
872
  return;
554
873
  }
555
874
 
875
+ // POST /v1/billing/rotate-key — rotate the authenticated key, preserving subscription
876
+ if (req.method === 'POST' && pathname === '/v1/billing/rotate-key') {
877
+ const currentKey = extractBearerToken(req);
878
+ if (!currentKey) {
879
+ sendJson(res, 401, { error: 'Unauthorized' });
880
+ return;
881
+ }
882
+ const validation = validateApiKey(currentKey);
883
+ if (!validation.valid) {
884
+ sendJson(res, 400, { error: 'Key not found or already disabled' });
885
+ return;
886
+ }
887
+ try {
888
+ const result = rotateApiKey(currentKey);
889
+ if (!result.rotated) {
890
+ sendJson(res, 400, { error: result.reason || 'Key rotation failed' });
891
+ return;
892
+ }
893
+ sendJson(res, 200, {
894
+ newKey: result.newKey,
895
+ message: 'Key rotated. Update your configuration.',
896
+ });
897
+ } catch (err) {
898
+ sendJson(res, 500, { error: err.message || 'Internal Server Error' });
899
+ }
900
+ return;
901
+ }
902
+
556
903
  // GET /v1/analytics/funnel — aggregate acquisition/activation/paid funnel metrics
557
904
  if (req.method === 'GET' && pathname === '/v1/analytics/funnel') {
558
905
  const summary = getFunnelAnalytics();