lynkr 9.7.3 → 9.9.1

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 (80) hide show
  1. package/README.md +63 -25
  2. package/bin/cli.js +16 -1
  3. package/bin/lynkr-init.js +44 -1
  4. package/bin/lynkr-usage.js +78 -0
  5. package/bin/wrap.js +60 -35
  6. package/config/difficulty-anchors.json +22 -0
  7. package/package.json +23 -2
  8. package/scripts/audit-log-reader.js +399 -0
  9. package/scripts/build-eval-set.js +256 -0
  10. package/scripts/calibrate-thresholds.js +38 -157
  11. package/scripts/compact-dictionary.js +204 -0
  12. package/scripts/mine-difficulty-anchors.js +288 -0
  13. package/scripts/test-deduplication.js +448 -0
  14. package/scripts/validate-difficulty-classifier.js +123 -0
  15. package/scripts/validate-intent-anchors.js +186 -0
  16. package/scripts/ws7-anchor-replay.js +108 -0
  17. package/skills/lynkr/SKILL.md +195 -0
  18. package/src/api/middleware/loop-guard.js +87 -0
  19. package/src/api/middleware/request-logging.js +5 -64
  20. package/src/api/middleware/session.js +0 -0
  21. package/src/api/openai-router.js +120 -101
  22. package/src/api/providers-handler.js +27 -2
  23. package/src/api/router.js +467 -125
  24. package/src/budget/index.js +2 -19
  25. package/src/cache/semantic.js +9 -0
  26. package/src/clients/databricks.js +455 -142
  27. package/src/clients/gpt-utils.js +11 -105
  28. package/src/clients/openai-format.js +10 -3
  29. package/src/clients/openrouter-utils.js +49 -24
  30. package/src/clients/prompt-cache-injection.js +1 -0
  31. package/src/clients/provider-capabilities.js +1 -1
  32. package/src/clients/responses-format.js +34 -3
  33. package/src/clients/routing.js +15 -0
  34. package/src/config/index.js +36 -2
  35. package/src/context/gcf.js +275 -0
  36. package/src/context/tool-result-compressor.js +932 -47
  37. package/src/dashboard/api.js +1 -0
  38. package/src/logger/index.js +14 -1
  39. package/src/memory/search.js +12 -40
  40. package/src/memory/tools.js +3 -24
  41. package/src/orchestrator/bypass.js +4 -2
  42. package/src/orchestrator/index.js +120 -85
  43. package/src/routing/affinity-store.js +194 -0
  44. package/src/routing/agentic-detector.js +36 -6
  45. package/src/routing/bandit.js +25 -6
  46. package/src/routing/calibration.js +212 -0
  47. package/src/routing/classifier-setup.js +207 -0
  48. package/src/routing/client-profiles.js +292 -0
  49. package/src/routing/complexity-analyzer.js +88 -15
  50. package/src/routing/deescalator.js +148 -0
  51. package/src/routing/degradation.js +109 -0
  52. package/src/routing/difficulty-classifier.js +219 -0
  53. package/src/routing/feedback.js +157 -0
  54. package/src/routing/index.js +931 -90
  55. package/src/routing/intent-score.js +441 -0
  56. package/src/routing/interaction.js +3 -0
  57. package/src/routing/knn-router.js +70 -21
  58. package/src/routing/model-registry.js +28 -7
  59. package/src/routing/model-tiers.js +25 -2
  60. package/src/routing/reward-pipeline.js +68 -2
  61. package/src/routing/risk-analyzer.js +30 -1
  62. package/src/routing/risk-classifier.js +6 -2
  63. package/src/routing/session-affinity.js +162 -34
  64. package/src/routing/telemetry.js +286 -13
  65. package/src/routing/verifier.js +267 -0
  66. package/src/server.js +86 -21
  67. package/src/sessions/cleanup.js +17 -0
  68. package/src/tools/index.js +1 -15
  69. package/src/tools/smart-selection.js +10 -0
  70. package/src/tools/web-client.js +3 -3
  71. package/.eslintrc.cjs +0 -12
  72. package/benchmark-configs/litellm_config.yaml +0 -86
  73. package/benchmark-configs/lynkr.env +0 -48
  74. package/benchmark-configs/portkey-config.json +0 -60
  75. package/benchmark-configs/portkey-docker.sh +0 -23
  76. package/benchmark-tier-routing.js +0 -449
  77. package/funding.json +0 -110
  78. package/src/api/middleware/validation.js +0 -261
  79. package/src/routing/drift-monitor.js +0 -113
  80. package/src/workers/helpers.js +0 -185
@@ -0,0 +1,399 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * LLM Audit Log Reader
5
+ *
6
+ * Utility to read and reconstruct audit log entries from deduplicated logs.
7
+ * Resolves hash references from the dictionary file and outputs full content.
8
+ *
9
+ * Usage:
10
+ * node scripts/audit-log-reader.js [options]
11
+ *
12
+ * Options:
13
+ * --log-file <path> Path to audit log file (default: logs/llm-audit.log)
14
+ * --dict-file <path> Path to dictionary file (default: logs/llm-audit-dictionary.jsonl)
15
+ * --full Output full restored entries (resolve all references)
16
+ * --filter <type=value> Filter by field (e.g., type=llm_request, provider=anthropic)
17
+ * --correlation-id <id> Filter by correlation ID
18
+ * --last <n> Show only last N entries
19
+ * --stats Show deduplication statistics
20
+ * --verify Verify all references can be resolved
21
+ * --help Show this help message
22
+ *
23
+ * Examples:
24
+ * # Show all entries with full content
25
+ * node scripts/audit-log-reader.js --full
26
+ *
27
+ * # Show only requests
28
+ * node scripts/audit-log-reader.js --filter type=llm_request
29
+ *
30
+ * # Show last 5 entries
31
+ * node scripts/audit-log-reader.js --last 5 --full
32
+ *
33
+ * # Show deduplication statistics
34
+ * node scripts/audit-log-reader.js --stats
35
+ *
36
+ * # Verify all references resolve
37
+ * node scripts/audit-log-reader.js --verify
38
+ */
39
+
40
+ const fs = require("fs");
41
+ const path = require("path");
42
+ const readline = require("readline");
43
+ const { ContentDeduplicator } = require("../src/logger/deduplicator");
44
+
45
+ // Default file paths
46
+ const DEFAULT_LOG_FILE = path.join(process.cwd(), "logs", "llm-audit.log");
47
+ const DEFAULT_DICT_FILE = path.join(process.cwd(), "logs", "llm-audit-dictionary.jsonl");
48
+
49
+ /**
50
+ * Parse command line arguments
51
+ */
52
+ function parseArgs() {
53
+ const args = process.argv.slice(2);
54
+ const options = {
55
+ logFile: DEFAULT_LOG_FILE,
56
+ dictFile: DEFAULT_DICT_FILE,
57
+ full: false,
58
+ filter: null,
59
+ correlationId: null,
60
+ last: null,
61
+ stats: false,
62
+ verify: false,
63
+ help: false,
64
+ };
65
+
66
+ for (let i = 0; i < args.length; i++) {
67
+ const arg = args[i];
68
+ switch (arg) {
69
+ case "--log-file":
70
+ options.logFile = args[++i];
71
+ break;
72
+ case "--dict-file":
73
+ options.dictFile = args[++i];
74
+ break;
75
+ case "--full":
76
+ options.full = true;
77
+ break;
78
+ case "--filter":
79
+ const filterArg = args[++i];
80
+ const [key, value] = filterArg.split("=");
81
+ options.filter = { key, value };
82
+ break;
83
+ case "--correlation-id":
84
+ options.correlationId = args[++i];
85
+ break;
86
+ case "--last":
87
+ options.last = Number.parseInt(args[++i], 10);
88
+ break;
89
+ case "--stats":
90
+ options.stats = true;
91
+ break;
92
+ case "--verify":
93
+ options.verify = true;
94
+ break;
95
+ case "--help":
96
+ options.help = true;
97
+ break;
98
+ default:
99
+ console.error(`Unknown option: ${arg}`);
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ return options;
105
+ }
106
+
107
+ /**
108
+ * Show help message
109
+ */
110
+ function showHelp() {
111
+ const helpText = `
112
+ LLM Audit Log Reader
113
+
114
+ Usage: node scripts/audit-log-reader.js [options]
115
+
116
+ Options:
117
+ --log-file <path> Path to audit log file (default: logs/llm-audit.log)
118
+ --dict-file <path> Path to dictionary file (default: logs/llm-audit-dictionary.jsonl)
119
+ --full Output full restored entries (resolve all references)
120
+ --filter <type=value> Filter by field (e.g., type=llm_request, provider=anthropic)
121
+ --correlation-id <id> Filter by correlation ID
122
+ --last <n> Show only last N entries
123
+ --stats Show deduplication statistics
124
+ --verify Verify all references can be resolved
125
+ --help Show this help message
126
+
127
+ Examples:
128
+ # Show all entries with full content
129
+ node scripts/audit-log-reader.js --full
130
+
131
+ # Show only requests
132
+ node scripts/audit-log-reader.js --filter type=llm_request
133
+
134
+ # Show last 5 entries
135
+ node scripts/audit-log-reader.js --last 5 --full
136
+
137
+ # Show deduplication statistics
138
+ node scripts/audit-log-reader.js --stats
139
+
140
+ # Verify all references resolve
141
+ node scripts/audit-log-reader.js --verify
142
+ `;
143
+ console.log(helpText);
144
+ }
145
+
146
+ /**
147
+ * Read and process log entries
148
+ */
149
+ async function readLogEntries(options) {
150
+ const { logFile, dictFile, full, filter, correlationId, last } = options;
151
+
152
+ // Check if log file exists
153
+ if (!fs.existsSync(logFile)) {
154
+ console.error(`Log file not found: ${logFile}`);
155
+ process.exit(1);
156
+ }
157
+
158
+ // Initialize deduplicator if needed
159
+ let deduplicator = null;
160
+ if (full && fs.existsSync(dictFile)) {
161
+ deduplicator = new ContentDeduplicator(dictFile);
162
+ }
163
+
164
+ const entries = [];
165
+
166
+ // Read log file line by line
167
+ const fileStream = fs.createReadStream(logFile);
168
+ const rl = readline.createInterface({
169
+ input: fileStream,
170
+ crlfDelay: Infinity,
171
+ });
172
+
173
+ for await (const line of rl) {
174
+ if (!line.trim()) continue;
175
+
176
+ try {
177
+ let entry = JSON.parse(line);
178
+
179
+ // Apply filters
180
+ if (filter && entry[filter.key] !== filter.value) {
181
+ continue;
182
+ }
183
+ if (correlationId && entry.correlationId !== correlationId) {
184
+ continue;
185
+ }
186
+
187
+ // Restore full content if requested
188
+ if (full && deduplicator) {
189
+ entry = deduplicator.restoreEntry(entry);
190
+ }
191
+
192
+ entries.push(entry);
193
+ } catch (err) {
194
+ console.error("Malformed log entry:", err.message);
195
+ }
196
+ }
197
+
198
+ // Apply last N filter
199
+ const output = last ? entries.slice(-last) : entries;
200
+
201
+ // Output as JSONL
202
+ for (const entry of output) {
203
+ console.log(JSON.stringify(entry, null, 2));
204
+ }
205
+
206
+ return entries.length;
207
+ }
208
+
209
+ /**
210
+ * Show deduplication statistics
211
+ */
212
+ async function showStats(options) {
213
+ const { logFile, dictFile } = options;
214
+
215
+ if (!fs.existsSync(logFile)) {
216
+ console.error(`Log file not found: ${logFile}`);
217
+ process.exit(1);
218
+ }
219
+
220
+ if (!fs.existsSync(dictFile)) {
221
+ console.log("No dictionary file found. Deduplication may not be enabled.");
222
+ return;
223
+ }
224
+
225
+ // Get file sizes
226
+ const logStats = fs.statSync(logFile);
227
+ const dictStats = fs.statSync(dictFile);
228
+
229
+ console.log("\n=== LLM Audit Log Deduplication Statistics ===\n");
230
+ console.log(`Log file: ${logFile}`);
231
+ console.log(` Size: ${formatBytes(logStats.size)}`);
232
+ console.log(` Lines: ${await countLines(logFile)}`);
233
+ console.log();
234
+ console.log(`Dictionary file: ${dictFile}`);
235
+ console.log(` Size: ${formatBytes(dictStats.size)}`);
236
+ console.log(` Entries: ${await countLines(dictFile)}`);
237
+ console.log();
238
+ console.log(`Total size: ${formatBytes(logStats.size + dictStats.size)}`);
239
+ console.log();
240
+
241
+ // Count reference occurrences in log
242
+ const refCount = await countReferences(logFile);
243
+ console.log(`Reference objects in log: ${refCount}`);
244
+ console.log(`Estimated space saved: ~${formatBytes(refCount * 2000)} (assuming ~2KB per deduplicated field)`);
245
+ console.log();
246
+ }
247
+
248
+ /**
249
+ * Verify all references can be resolved
250
+ */
251
+ async function verifyReferences(options) {
252
+ const { logFile, dictFile } = options;
253
+
254
+ if (!fs.existsSync(logFile)) {
255
+ console.error(`Log file not found: ${logFile}`);
256
+ process.exit(1);
257
+ }
258
+
259
+ if (!fs.existsSync(dictFile)) {
260
+ console.log("No dictionary file found. Nothing to verify.");
261
+ return;
262
+ }
263
+
264
+ const deduplicator = new ContentDeduplicator(dictFile);
265
+ const fileStream = fs.createReadStream(logFile);
266
+ const rl = readline.createInterface({
267
+ input: fileStream,
268
+ crlfDelay: Infinity,
269
+ });
270
+
271
+ let totalRefs = 0;
272
+ let unresolvedRefs = 0;
273
+ const unresolvedHashes = new Set();
274
+
275
+ console.log("Verifying references...\n");
276
+
277
+ for await (const line of rl) {
278
+ if (!line.trim()) continue;
279
+
280
+ try {
281
+ const entry = JSON.parse(line);
282
+
283
+ // Check all fields for references
284
+ for (const [key, value] of Object.entries(entry)) {
285
+ if (typeof value === "object" && value !== null && value.$ref) {
286
+ totalRefs++;
287
+ const content = deduplicator.getContent(value.$ref);
288
+ if (content === null) {
289
+ unresolvedRefs++;
290
+ unresolvedHashes.add(value.$ref);
291
+ console.error(`✗ Unresolved reference: ${value.$ref} in field "${key}"`);
292
+ }
293
+ }
294
+ }
295
+ } catch (err) {
296
+ console.error("Malformed log entry:", err.message);
297
+ }
298
+ }
299
+
300
+ console.log("\n=== Verification Results ===\n");
301
+ console.log(`Total references: ${totalRefs}`);
302
+ console.log(`Unresolved references: ${unresolvedRefs}`);
303
+ console.log(`Unique unresolved hashes: ${unresolvedHashes.size}`);
304
+
305
+ if (unresolvedRefs === 0) {
306
+ console.log("\n✓ All references resolved successfully!");
307
+ } else {
308
+ console.log("\n✗ Some references could not be resolved. Dictionary may be incomplete.");
309
+ process.exit(1);
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Helper: Format bytes to human-readable string
315
+ */
316
+ function formatBytes(bytes) {
317
+ if (bytes < 1024) return `${bytes} B`;
318
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
319
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
320
+ }
321
+
322
+ /**
323
+ * Helper: Count lines in a file
324
+ */
325
+ async function countLines(filePath) {
326
+ const fileStream = fs.createReadStream(filePath);
327
+ const rl = readline.createInterface({
328
+ input: fileStream,
329
+ crlfDelay: Infinity,
330
+ });
331
+
332
+ let count = 0;
333
+ for await (const line of rl) {
334
+ if (line.trim()) count++;
335
+ }
336
+ return count;
337
+ }
338
+
339
+ /**
340
+ * Helper: Count reference objects in log
341
+ */
342
+ async function countReferences(filePath) {
343
+ const fileStream = fs.createReadStream(filePath);
344
+ const rl = readline.createInterface({
345
+ input: fileStream,
346
+ crlfDelay: Infinity,
347
+ });
348
+
349
+ let count = 0;
350
+ for await (const line of rl) {
351
+ if (!line.trim()) continue;
352
+ try {
353
+ const entry = JSON.parse(line);
354
+ for (const value of Object.values(entry)) {
355
+ if (typeof value === "object" && value !== null && value.$ref) {
356
+ count++;
357
+ }
358
+ }
359
+ } catch {
360
+ // Skip malformed lines
361
+ }
362
+ }
363
+ return count;
364
+ }
365
+
366
+ /**
367
+ * Main entry point
368
+ */
369
+ async function main() {
370
+ const options = parseArgs();
371
+
372
+ if (options.help) {
373
+ showHelp();
374
+ return;
375
+ }
376
+
377
+ if (options.stats) {
378
+ await showStats(options);
379
+ } else if (options.verify) {
380
+ await verifyReferences(options);
381
+ } else {
382
+ const count = await readLogEntries(options);
383
+ console.error(`\n(Processed ${count} entries)`);
384
+ }
385
+ }
386
+
387
+ // Run if called directly
388
+ if (require.main === module) {
389
+ main().catch((err) => {
390
+ console.error("Error:", err.message);
391
+ process.exit(1);
392
+ });
393
+ }
394
+
395
+ module.exports = {
396
+ readLogEntries,
397
+ showStats,
398
+ verifyReferences,
399
+ };
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build the difficulty-classifier evaluation set.
4
+ *
5
+ * Sources (all zero-cost, no LLM judge required):
6
+ * 1. RouterArena unused rows — native empirical difficulty labels
7
+ * (easy/medium/hard from 42-model pass rate). No LLM involved.
8
+ * 2. routellm/gpt4_dataset unused rows — native mixtral_score 1–5
9
+ * (GPT-4-judged when the dataset was built). No LLM involved.
10
+ * 3. Hand-crafted coding-agent-flavored prompts — representative of
11
+ * Lynkr's real traffic (real logs proved too thin, ~7 unique prompts).
12
+ *
13
+ * Leak avoidance: skip any text already in data/difficulty-anchors.provenance.json
14
+ * (the mined anchor set) so eval and anchors don't overlap.
15
+ *
16
+ * Label → tier mapping:
17
+ * RouterArena easy → MEDIUM (any competent model handles)
18
+ * RouterArena medium → COMPLEX (needs strong general model)
19
+ * RouterArena hard → REASONING (frontier model territory)
20
+ * gpt4_dataset score 5 → MEDIUM (Mixtral got it right — trivially easy)
21
+ * gpt4_dataset score 3–4 → COMPLEX (Mixtral partial — real difficulty)
22
+ * gpt4_dataset score 1–2 → REASONING (Mixtral failed — top-tier work)
23
+ *
24
+ * Output: data/difficulty-eval.jsonl (gitignored)
25
+ * Usage: node scripts/build-eval-set.js
26
+ */
27
+
28
+ const fs = require("fs");
29
+ const path = require("path");
30
+
31
+ const DS_SERVER = "https://datasets-server.huggingface.co/rows";
32
+ const OUT_FILE = path.join(__dirname, "../data/difficulty-eval.jsonl");
33
+ const PROVENANCE = path.join(__dirname, "../data/difficulty-anchors.provenance.json");
34
+
35
+ const TARGETS = { routerarena: 200, gpt4_dataset: 200 };
36
+
37
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
38
+
39
+ async function fetchRows(dataset, config, split, offset, length) {
40
+ const url = `${DS_SERVER}?dataset=${encodeURIComponent(dataset)}&config=${config}&split=${split}&offset=${offset}&length=${length}`;
41
+ for (let a = 0; a < 5; a++) {
42
+ try {
43
+ const res = await fetch(url);
44
+ if (res.status === 429) { await sleep(3000 * (a + 1)); continue; }
45
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
46
+ return (await res.json()).rows?.map(r => r.row) || [];
47
+ } catch (err) {
48
+ if (a === 4) return [];
49
+ await sleep(1500 * (a + 1));
50
+ }
51
+ }
52
+ return [];
53
+ }
54
+
55
+ function clean(text) {
56
+ if (typeof text !== "string") return null;
57
+ const t = text.replace(/\s+/g, " ").trim();
58
+ if (t.length < 15 || t.length > 400) return null;
59
+ if (/\(\s*\)/.test(t) || /_{3,}/.test(t)) return null;
60
+ if (/\b[A-D]\)\s/.test(t) || /which of the following/i.test(t)) return null;
61
+ if (/\b(premise|hypothesis)\b/i.test(t) && /\b(entail|inference|contradict)\b/i.test(t)) return null;
62
+ if (/NAME_\d|\{\{.*\}\}/.test(t)) return null;
63
+ const nonAscii = (t.match(/[^\x20-\x7E]/g) || []).length;
64
+ if (nonAscii > t.length * 0.1) return null;
65
+ return t;
66
+ }
67
+
68
+ function isQuizbowlStyle(t) {
69
+ return /^(this |one |a \d{4} (book|work|paper)|fictional |in one (work|section))/i.test(t) ||
70
+ /\([A-Za-z]+-[A-Z][A-Za-z-]+\)/.test(t);
71
+ }
72
+
73
+ // Hand-crafted coding-agent-flavored prompts by tier. Reflects real Lynkr
74
+ // traffic shape (imperatives, technical vocabulary, mix of simple/mechanical/
75
+ // systemic/rigorous). Labels are mine — the honest reviewer for this session.
76
+ const HAND_EVAL = [
77
+ // SIMPLE — casual/acks
78
+ { text: "hi", tier: "SIMPLE" },
79
+ { text: "hello", tier: "SIMPLE" },
80
+ { text: "ok thanks", tier: "SIMPLE" },
81
+ { text: "yes continue", tier: "SIMPLE" },
82
+ { text: "no skip that", tier: "SIMPLE" },
83
+ { text: "got it", tier: "SIMPLE" },
84
+ { text: "sure go ahead", tier: "SIMPLE" },
85
+ { text: "what time is it", tier: "SIMPLE" },
86
+ { text: "cool", tier: "SIMPLE" },
87
+ { text: "makes sense", tier: "SIMPLE" },
88
+ { text: "thanks a lot", tier: "SIMPLE" },
89
+ { text: "hey there", tier: "SIMPLE" },
90
+ { text: "yep proceed", tier: "SIMPLE" },
91
+ { text: "great work", tier: "SIMPLE" },
92
+ { text: "no worries", tier: "SIMPLE" },
93
+ // MEDIUM — one specific mechanical task
94
+ { text: "list the exports from src/router.js", tier: "MEDIUM" },
95
+ { text: "run the unit tests and summarize failures", tier: "MEDIUM" },
96
+ { text: "show me the git diff for this file", tier: "MEDIUM" },
97
+ { text: "what does this function do", tier: "MEDIUM" },
98
+ { text: "explain this regex pattern to me", tier: "MEDIUM" },
99
+ { text: "add error handling to this try block", tier: "MEDIUM" },
100
+ { text: "fix the linter warnings in this file", tier: "MEDIUM" },
101
+ { text: "write a unit test for the login helper", tier: "MEDIUM" },
102
+ { text: "convert this callback to async/await", tier: "MEDIUM" },
103
+ { text: "add a null check before this dereference", tier: "MEDIUM" },
104
+ { text: "rename this variable to something more descriptive", tier: "MEDIUM" },
105
+ { text: "extract this into a helper function", tier: "MEDIUM" },
106
+ { text: "add a docstring to this method", tier: "MEDIUM" },
107
+ { text: "check if this file has any TODO comments", tier: "MEDIUM" },
108
+ { text: "search the codebase for uses of deprecated API", tier: "MEDIUM" },
109
+ { text: "install the axios package as a dev dependency", tier: "MEDIUM" },
110
+ { text: "revert the last commit but keep the working tree", tier: "MEDIUM" },
111
+ { text: "delete unused imports from this file", tier: "MEDIUM" },
112
+ { text: "format this JSON blob nicely", tier: "MEDIUM" },
113
+ { text: "which files were changed in the last commit", tier: "MEDIUM" },
114
+ { text: "how do I use the fetch API with timeouts", tier: "MEDIUM" },
115
+ { text: "what's the difference between let and const in JavaScript", tier: "MEDIUM" },
116
+ { text: "add a rate limit to this endpoint", tier: "MEDIUM" },
117
+ { text: "grep for TODO in the routing folder", tier: "MEDIUM" },
118
+ { text: "make this loop use Promise.all instead of sequential awaits", tier: "MEDIUM" },
119
+ // COMPLEX — systemic / design / multi-file
120
+ { text: "do an architecture review of the orchestrator", tier: "COMPLEX" },
121
+ { text: "review this retry helper for bugs and race conditions", tier: "COMPLEX" },
122
+ { text: "refactor the entire ingestion pipeline and give me a plan", tier: "COMPLEX" },
123
+ { text: "design a horizontally scalable architecture for the router with failure-mode analysis", tier: "COMPLEX" },
124
+ { text: "code review the PR #84 routing hardening changes", tier: "COMPLEX" },
125
+ { text: "analyze every module in src/ for circular dependencies", tier: "COMPLEX" },
126
+ { text: "debug this complex race condition in the connection pool", tier: "COMPLEX" },
127
+ { text: "plan a zero-downtime migration to the new schema", tier: "COMPLEX" },
128
+ { text: "implement a distributed rate limiter with Redis backing", tier: "COMPLEX" },
129
+ { text: "design the caching strategy for this API gateway including invalidation rules", tier: "COMPLEX" },
130
+ { text: "trace through the request lifecycle end to end and identify bottlenecks", tier: "COMPLEX" },
131
+ { text: "restructure the module boundaries to reduce coupling between core and plugins", tier: "COMPLEX" },
132
+ { text: "propose a testing strategy for the new streaming pipeline covering edge cases", tier: "COMPLEX" },
133
+ { text: "identify all the places in the codebase that depend on the legacy auth flow and plan the deprecation", tier: "COMPLEX" },
134
+ { text: "walk me through how the tier routing decision cascade works and highlight the weak points", tier: "COMPLEX" },
135
+ { text: "compare our current queueing implementation against BullMQ and recommend a migration path", tier: "COMPLEX" },
136
+ { text: "extract the shared session logic into a separate package and update all consumers", tier: "COMPLEX" },
137
+ { text: "design the observability layer for the multi-tenant deployment with per-tenant metrics", tier: "COMPLEX" },
138
+ { text: "diagnose why the latency P99 spiked yesterday afternoon across the fleet", tier: "COMPLEX" },
139
+ { text: "plan the sharding strategy for the telemetry database as we scale to 10x traffic", tier: "COMPLEX" },
140
+ { text: "audit the codebase for uses of unsafe eval and propose safe replacements", tier: "COMPLEX" },
141
+ { text: "propose a graceful degradation plan for when the primary embedding model is unavailable", tier: "COMPLEX" },
142
+ { text: "review the error handling across the client SDK and standardize on a single pattern", tier: "COMPLEX" },
143
+ { text: "outline the migration from the monolith to a services split with backwards compatibility", tier: "COMPLEX" },
144
+ { text: "reason about whether this cache invalidation scheme is correct under concurrent writes", tier: "COMPLEX" },
145
+ // REASONING — proof / audit / formal / deep
146
+ { text: "prove the correctness of this lock-free queue implementation and identify any ABA hazards", tier: "REASONING" },
147
+ { text: "security audit the authentication middleware for token reuse vulnerabilities", tier: "REASONING" },
148
+ { text: "derive the optimal cache eviction policy for this access pattern and prove its competitive ratio", tier: "REASONING" },
149
+ { text: "formally verify that this state machine can never deadlock or produce a counterexample trace", tier: "REASONING" },
150
+ { text: "from first principles design a Byzantine-fault-tolerant consensus variant tolerating f faults with 2f+1 replicas", tier: "REASONING" },
151
+ { text: "think deeply about why this floating-point summation loses precision at scale and derive the compensated algorithm", tier: "REASONING" },
152
+ { text: "prove this rate limiter is fair under concurrent refill or construct the starvation schedule that breaks it", tier: "REASONING" },
153
+ { text: "reason through the exact memory ordering constraints this concurrent hashmap needs on ARM and justify each barrier", tier: "REASONING" },
154
+ { text: "given these heap dumps reason to the retention path causing the leak and rule out the two most plausible alternatives", tier: "REASONING" },
155
+ { text: "ultrathink: analyze whether this online schema migration can preserve every invariant the application relies on", tier: "REASONING" },
156
+ { text: "verify no path in this state machine violates the safety property that read never observes a partial write", tier: "REASONING" },
157
+ { text: "derive the amortized complexity bound for this splay tree under this adversarial access pattern and prove it tight", tier: "REASONING" },
158
+ { text: "prove that this distributed algorithm makes progress under fair scheduling regardless of message reordering", tier: "REASONING" },
159
+ { text: "penetration test the session token handling and enumerate every replay attack vector", tier: "REASONING" },
160
+ { text: "formal proof that this compensation transaction preserves ACID under partial-failure scenarios", tier: "REASONING" },
161
+ { text: "reason from first principles about whether exactly-once semantics are achievable on top of this at-least-once queue", tier: "REASONING" },
162
+ { text: "vulnerability scan the recently-added crypto path for downgrade attacks and misuse of primitives", tier: "REASONING" },
163
+ { text: "prove that this garbage collector will always terminate on any input given the described root set", tier: "REASONING" },
164
+ { text: "verify the linearizability of this concurrent skip list using the linearization-point argument", tier: "REASONING" },
165
+ { text: "think hard about which of these three algorithms actually preserves invariant I under crash recovery and which only appears to", tier: "REASONING" },
166
+ ];
167
+
168
+ async function main() {
169
+ const seenTexts = new Set();
170
+ try {
171
+ const prov = JSON.parse(fs.readFileSync(PROVENANCE, "utf8"));
172
+ for (const text of Object.keys(prov)) seenTexts.add(text);
173
+ console.log(`Loaded ${seenTexts.size} anchor texts from provenance (excluded from eval)`);
174
+ } catch { /* first run may have no provenance */ }
175
+
176
+ const eval_ = [];
177
+ const seen = new Set([...seenTexts].map(t => t.slice(0, 80)));
178
+
179
+ const add = (text, tier, source, label) => {
180
+ const key = text.slice(0, 80);
181
+ if (seen.has(key)) return false;
182
+ seen.add(key);
183
+ eval_.push({ text, tier, source, label });
184
+ return true;
185
+ };
186
+
187
+ // 1. Hand-crafted prompts (canary + real-traffic representative)
188
+ for (const item of HAND_EVAL) {
189
+ add(item.text, item.tier, "hand", "author-labeled");
190
+ }
191
+ console.log(`Hand-crafted: ${eval_.length} added`);
192
+
193
+ // 2. RouterArena — technical domains only, empirical difficulty labels
194
+ console.log("Fetching RouterArena rows...");
195
+ const TECH_DOMAIN_RE = /computer science|technology|engineering|mathematic|science/i;
196
+ const DATASET_EXCLUDE_RE = /superglue|cloze|entailment/i;
197
+ let raAdded = 0;
198
+ outer: for (let offset = 4000; offset < 8400; offset += 100) {
199
+ const rows = await fetchRows("RouteWorks/RouterArena", "default", "full", offset, 100);
200
+ for (const row of rows) {
201
+ if (raAdded >= TARGETS.routerarena) break outer;
202
+ if (row.Context && String(row.Context).trim()) continue;
203
+ if (Array.isArray(row.Options) && row.Options.length > 0) continue;
204
+ if (DATASET_EXCLUDE_RE.test(String(row["Dataset name"] || ""))) continue;
205
+ const text = clean(row.Question);
206
+ if (!text || isQuizbowlStyle(text)) continue;
207
+ const technical = TECH_DOMAIN_RE.test(String(row.Domain || ""));
208
+ const diff = String(row.Difficulty || "").toLowerCase();
209
+ let tier = null;
210
+ if (diff === "easy") tier = "MEDIUM";
211
+ else if (diff === "medium" && technical) tier = "COMPLEX";
212
+ else if (diff === "hard" && technical) tier = "REASONING";
213
+ if (!tier) continue;
214
+ if (add(text, tier, "RouterArena", `${diff}/${row["Dataset name"]}`)) raAdded++;
215
+ }
216
+ await sleep(200);
217
+ }
218
+ console.log(`RouterArena: +${raAdded}`);
219
+
220
+ // 3. gpt4_dataset — skip pages we mined for anchors (offset 0, 1817, 3634...)
221
+ console.log("Fetching gpt4_dataset rows...");
222
+ let gpAdded = 0;
223
+ outer2: for (let offset = 60000; offset < 109000; offset += 500) {
224
+ const rows = await fetchRows("routellm/gpt4_dataset", "default", "train", offset, 100);
225
+ for (const row of rows) {
226
+ if (gpAdded >= TARGETS.gpt4_dataset) break outer2;
227
+ const text = clean(row.prompt);
228
+ if (!text) continue;
229
+ const score = Number(row.mixtral_score);
230
+ let tier = null;
231
+ if (score === 5) tier = "MEDIUM";
232
+ else if (score === 3 || score === 4) tier = "COMPLEX";
233
+ else if (score === 1 || score === 2) tier = text.length < 60 ? null : "REASONING";
234
+ if (!tier) continue;
235
+ if (add(text, tier, "gpt4_dataset", `mixtral_score=${score}`)) gpAdded++;
236
+ }
237
+ await sleep(200);
238
+ }
239
+ console.log(`gpt4_dataset: +${gpAdded}`);
240
+
241
+ // Shuffle deterministically (session-repro via fixed permutation)
242
+ eval_.sort((a, b) => a.text.length - b.text.length);
243
+ const shuffled = [];
244
+ for (let i = 0; i < eval_.length; i++) {
245
+ const j = (i * 37 + 11) % eval_.length;
246
+ shuffled.push(eval_[j]);
247
+ }
248
+
249
+ fs.writeFileSync(OUT_FILE, shuffled.map(r => JSON.stringify(r)).join("\n") + "\n");
250
+ console.log(`\nWrote ${OUT_FILE}: ${shuffled.length} rows`);
251
+ const perTier = {};
252
+ for (const r of shuffled) perTier[r.tier] = (perTier[r.tier] || 0) + 1;
253
+ console.log("Per-tier:", JSON.stringify(perTier));
254
+ }
255
+
256
+ main().catch(err => { console.error(err); process.exit(1); });