nodebench-mcp 2.58.0 → 2.59.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.
@@ -0,0 +1,1524 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * llmJudgeEval.ts — LLM-judged boolean-metric eval harness for NodeBench MCP
4
+ *
5
+ * Architecture:
6
+ * 1. Query Corpus — 500+ typed test queries across 11 personas × 8 scenarios
7
+ * 2. Tool Executor — loads preset, runs discover_tools + tool chain, captures outputs
8
+ * 3. LLM Judge — Gemini Flash Lite boolean evaluation per criterion
9
+ * 4. Boolean Metrics — precision, recall, forbidden violations, criteria pass rate
10
+ * 5. Regression Detection — SQLite-backed diff between runs
11
+ *
12
+ * Usage:
13
+ * cd packages/mcp-local
14
+ * npx tsx src/benchmarks/llmJudgeEval.ts [--queries N] [--persona X] [--baseline RUN_ID] [--flywheel]
15
+ */
16
+ import { getDb, genId } from "../db.js";
17
+ import { _setDbAccessor } from "../tools/toolRegistry.js";
18
+ import { loadToolsets, ALL_DOMAIN_KEYS } from "../toolsetRegistry.js";
19
+ // ══════════════════════════════════════════════════════════════════════════════
20
+ // SCHEMA — eval tables (appended to existing DB)
21
+ // ══════════════════════════════════════════════════════════════════════════════
22
+ const LLM_EVAL_SCHEMA = `
23
+ CREATE TABLE IF NOT EXISTS llm_eval_runs (
24
+ run_id TEXT PRIMARY KEY,
25
+ timestamp TEXT NOT NULL DEFAULT (datetime('now')),
26
+ query_count INTEGER NOT NULL DEFAULT 0,
27
+ pass_rate REAL NOT NULL DEFAULT 0,
28
+ persona TEXT,
29
+ scenario TEXT,
30
+ summary_json TEXT
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS llm_eval_results (
34
+ id TEXT PRIMARY KEY,
35
+ run_id TEXT NOT NULL,
36
+ query_id TEXT NOT NULL,
37
+ pass INTEGER NOT NULL DEFAULT 0,
38
+ criteria_json TEXT,
39
+ tools_precision REAL NOT NULL DEFAULT 0,
40
+ tools_recall REAL NOT NULL DEFAULT 0,
41
+ forbidden_violations INTEGER NOT NULL DEFAULT 0,
42
+ criteria_pass_rate REAL NOT NULL DEFAULT 0,
43
+ judge_response TEXT,
44
+ ms INTEGER NOT NULL DEFAULT 0
45
+ );
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_llm_eval_results_run ON llm_eval_results(run_id);
48
+ CREATE INDEX IF NOT EXISTS idx_llm_eval_results_query ON llm_eval_results(query_id);
49
+ `;
50
+ function ensureSchema() {
51
+ const db = getDb();
52
+ db.exec(LLM_EVAL_SCHEMA);
53
+ }
54
+ // ══════════════════════════════════════════════════════════════════════════════
55
+ // QUERY CORPUS GENERATOR — 500 queries, programmatic
56
+ // ══════════════════════════════════════════════════════════════════════════════
57
+ const PERSONAS = [
58
+ "founder", "banker", "ceo", "researcher", "student",
59
+ "operator", "legal", "pm", "contractor", "investor", "content",
60
+ ];
61
+ const SCENARIOS = [
62
+ "weekly_reset", "company_search", "competitor_brief", "delegation",
63
+ "important_change", "memo_export", "packet_diff", "role_switch",
64
+ ];
65
+ function founderTemplates() {
66
+ return [
67
+ // weekly_reset
68
+ { query: "What changed in our product direction this week?", scenario: "weekly_reset", expectedTools: ["founder_deep_context_gather", "get_weekly_summary"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
69
+ { query: "Give me a weekly reset briefing for the founding team", scenario: "weekly_reset", expectedTools: ["founder_local_weekly_reset", "founder_deep_context_gather"], forbiddenTools: ["check_contract_compliance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
70
+ { query: "Summarize last week's key decisions and their rationale", scenario: "weekly_reset", expectedTools: ["founder_deep_context_gather", "get_weekly_summary"], forbiddenTools: ["generate_zero_draft"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
71
+ { query: "What are the top 3 risks to our current sprint?", scenario: "weekly_reset", expectedTools: ["founder_deep_context_gather", "get_proactive_alerts"], forbiddenTools: ["run_recon", "check_page_performance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
72
+ { query: "How is our burn rate tracking against the runway?", scenario: "weekly_reset", expectedTools: ["founder_deep_context_gather"], forbiddenTools: ["run_recon", "check_email_setup"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
73
+ { query: "What did we ship this week and what slipped?", scenario: "weekly_reset", expectedTools: ["founder_deep_context_gather", "get_weekly_summary"], forbiddenTools: ["generate_report"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
74
+ // company_search
75
+ { query: "Research Stripe and tell me about their latest product moves", scenario: "company_search", expectedTools: ["run_recon", "enrich_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
76
+ { query: "Pull everything you know about Anthropic's recent funding", scenario: "company_search", expectedTools: ["run_recon", "enrich_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
77
+ // competitor_brief
78
+ { query: "Compare our product positioning against Linear and Notion", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["start_dogfood_session"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
79
+ { query: "What are the moats of our top 3 competitors?", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["check_mcp_setup"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
80
+ // delegation
81
+ { query: "Draft a delegation brief for the engineering lead on the auth refactor", scenario: "delegation", expectedTools: ["founder_deep_context_gather", "export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
82
+ { query: "Create a handoff packet for the new VP of Product", scenario: "delegation", expectedTools: ["founder_deep_context_gather", "export_artifact_packet"], forbiddenTools: ["check_contract_compliance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
83
+ // important_change
84
+ { query: "Flag any important changes in our competitive landscape this week", scenario: "important_change", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["start_verification_cycle"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
85
+ { query: "What's the most critical thing I should know about right now?", scenario: "important_change", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
86
+ // memo_export
87
+ { query: "Export our latest decision memo as a shareable packet", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon", "check_page_performance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
88
+ { query: "Package the Q1 strategy review for the board", scenario: "memo_export", expectedTools: ["export_artifact_packet", "founder_deep_context_gather"], forbiddenTools: ["start_dogfood_session"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
89
+ // packet_diff
90
+ { query: "What changed between the last two strategy packets?", scenario: "packet_diff", expectedTools: ["founder_packet_diff", "founder_packet_history_diff"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Multiple tools in the chain produced non-empty results", weight: 1 }] },
91
+ { query: "Show me the delta between our January and March founder packets", scenario: "packet_diff", expectedTools: ["founder_packet_diff", "founder_packet_history_diff"], forbiddenTools: ["check_email_setup"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Multiple tools in the chain produced non-empty results", weight: 1 }] },
92
+ // role_switch
93
+ { query: "Switch to investor mode and evaluate our pitch deck", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
94
+ { query: "I need to think like a banker — what's the credit risk here?", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
95
+ ];
96
+ }
97
+ function bankerTemplates() {
98
+ return [
99
+ { query: "Run credit analysis on the portfolio company Acme Corp", scenario: "company_search", expectedTools: ["run_recon", "enrich_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
100
+ { query: "What's the debt-to-equity ratio trend for our top borrowers?", scenario: "company_search", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
101
+ { query: "Prepare a weekly credit committee briefing", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
102
+ { query: "Flag any covenant breaches in the current portfolio", scenario: "important_change", expectedTools: ["get_important_changes", "flag_important_change"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
103
+ { query: "Compare the credit profiles of Company A vs Company B", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
104
+ { query: "Draft a term sheet summary for the lending committee", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
105
+ { query: "What's changed in the regulatory landscape this week?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_important_changes"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
106
+ { query: "Export the due diligence findings for the Acme Corp loan", scenario: "memo_export", expectedTools: ["export_artifact_packet", "get_recon_summary"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
107
+ { query: "Show me how the risk ratings shifted since last quarter", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Multiple tools in the chain produced non-empty results", weight: 1 }] },
108
+ { query: "Delegate the annual review prep to the junior analyst", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
109
+ { query: "Assess the market risk exposure in our current book", scenario: "company_search", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
110
+ { query: "What are the top 5 watchlist names and why?", scenario: "important_change", expectedTools: ["get_important_changes", "get_proactive_alerts"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
111
+ { query: "Run a stress test scenario on the commercial real estate portfolio", scenario: "company_search", expectedTools: ["run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
112
+ { query: "Switch to researcher mode and find academic papers on credit risk modeling", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
113
+ ];
114
+ }
115
+ function ceoTemplates() {
116
+ return [
117
+ { query: "Give me the executive summary of where we stand this week", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_proactive_alerts"], forbiddenTools: ["check_page_performance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
118
+ { query: "What should I be worried about that nobody's telling me?", scenario: "important_change", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
119
+ { query: "Prepare talking points for the all-hands meeting", scenario: "memo_export", expectedTools: ["export_artifact_packet", "get_weekly_summary"], forbiddenTools: ["check_contract_compliance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
120
+ { query: "How are our OKRs tracking this quarter?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "founder_deep_context_gather"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
121
+ { query: "Who on the leadership team needs my attention this week?", scenario: "delegation", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["check_mcp_setup"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
122
+ { query: "Draft a board update email for this month", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
123
+ { query: "What's our competitive position changed to since last month?", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
124
+ { query: "Compare the last two quarterly reviews for drift", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["check_email_setup"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Multiple tools in the chain produced non-empty results", weight: 1 }] },
125
+ { query: "I need to delegate the hiring pipeline review — create a brief", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
126
+ { query: "Switch to founder mode and deep-dive into the product roadmap", scenario: "role_switch", expectedTools: ["discover_tools"], forbiddenTools: ["check_contract_compliance"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
127
+ { query: "Flag the most important thing that changed since yesterday", scenario: "important_change", expectedTools: ["get_important_changes", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
128
+ { query: "Research what our key enterprise customers are saying publicly", scenario: "company_search", expectedTools: ["run_recon", "enrich_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
129
+ ];
130
+ }
131
+ function researcherTemplates() {
132
+ return [
133
+ { query: "Find recent papers on transformer attention mechanisms", scenario: "company_search", expectedTools: ["run_recon", "build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
134
+ { query: "Build a research digest on federated learning advances in 2025", scenario: "company_search", expectedTools: ["build_research_digest", "run_recon"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
135
+ { query: "What are the open problems in RLHF that nobody's solved?", scenario: "competitor_brief", expectedTools: ["run_recon", "build_research_digest"], forbiddenTools: ["export_artifact_packet"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
136
+ { query: "Summarize the key findings from this week's arXiv papers on LLM reasoning", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
137
+ { query: "Compare the methodology of these two papers on knowledge distillation", scenario: "competitor_brief", expectedTools: ["compare_options", "build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
138
+ { query: "Export my literature review notes as a shareable document", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
139
+ { query: "What contradictions exist in the current MoE literature?", scenario: "important_change", expectedTools: ["build_research_digest", "get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
140
+ { query: "Track how the consensus on scaling laws has shifted this year", scenario: "packet_diff", expectedTools: ["founder_packet_diff", "build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Multiple tools in the chain produced non-empty results", weight: 1 }] },
141
+ { query: "Delegate the data collection task to the research assistant", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
142
+ { query: "Switch to operator mode and check if the experiment pipeline is healthy", scenario: "role_switch", expectedTools: ["discover_tools"], forbiddenTools: ["build_research_digest"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
143
+ { query: "What are the most-cited papers in agentic AI from 2025?", scenario: "company_search", expectedTools: ["run_recon", "build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
144
+ { query: "Generate a research question from the gaps in current RAG literature", scenario: "important_change", expectedTools: ["build_research_digest"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
145
+ ];
146
+ }
147
+ function studentTemplates() {
148
+ return [
149
+ { query: "Help me understand how transformers work at a high level", scenario: "company_search", expectedTools: ["discover_tools"], forbiddenTools: ["founder_deep_context_gather", "export_artifact_packet"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
150
+ { query: "What should I study this week for my ML course?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "discover_tools"], forbiddenTools: ["run_recon", "founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
151
+ { query: "Compare supervised vs unsupervised learning for my report", scenario: "competitor_brief", expectedTools: ["compare_options", "discover_tools"], forbiddenTools: ["run_recon", "founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
152
+ { query: "Export my study notes as a markdown document", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon", "founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
153
+ { query: "What changed in the AI landscape this week that I should know about?", scenario: "important_change", expectedTools: ["get_important_changes", "get_weekly_summary"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
154
+ { query: "I need to switch to a research perspective for my thesis", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }] },
155
+ { query: "Find beginner-friendly resources on neural network architectures", scenario: "company_search", expectedTools: ["discover_tools", "run_recon"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
156
+ { query: "Summarize the differences between GPT-4 and Claude for my presentation", scenario: "competitor_brief", expectedTools: ["compare_options", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
157
+ { query: "Help me find a dataset for my NLP project on sentiment analysis", scenario: "company_search", expectedTools: ["discover_tools", "run_recon"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains entity or topic names from the query", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }, { criterion: "Output includes quantitative data points or metrics", weight: 1 }] },
158
+ { query: "Create a study timeline for the next 4 weeks on deep learning", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "Output structure matches the tool's documented schema", weight: 1 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
159
+ { query: "What did I learn last week and what should I review?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary"], forbiddenTools: ["run_recon", "founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Tool returned structured data without errors", weight: 2 }, { criterion: "Output contains temporal information (dates, timestamps, periods)", weight: 1 }, { criterion: "At least one expected tool completed successfully", weight: 2 }, { criterion: "No error messages or stack traces in output", weight: 2 }] },
160
+ ];
161
+ }
162
+ function operatorTemplates() {
163
+ return [
164
+ { query: "Show me the system health dashboard for today", scenario: "weekly_reset", expectedTools: ["get_ops_dashboard", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output presents system health metrics", weight: 1 }, { criterion: "Output highlights any degraded services", weight: 1 }, { criterion: "Output is operational in tone", weight: 1 }] },
165
+ { query: "What incidents happened this week and are they resolved?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_proactive_alerts"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output lists incidents", weight: 1 }, { criterion: "Output includes resolution status", weight: 1 }, { criterion: "Output identifies root causes", weight: 1 }] },
166
+ { query: "Run a health check on all MCP infrastructure", scenario: "company_search", expectedTools: ["check_mcp_setup", "get_ops_dashboard"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output checks multiple infrastructure components", weight: 1 }, { criterion: "Output reports pass/fail per component", weight: 1 }, { criterion: "Output suggests fixes for failures", weight: 1 }] },
167
+ { query: "Delegate the on-call rotation setup to the SRE team", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output contains delegation instructions", weight: 1 }, { criterion: "Output specifies SRE-relevant details", weight: 1 }, { criterion: "Output includes escalation paths", weight: 1 }] },
168
+ { query: "What deployments went out this week and did any cause issues?", scenario: "important_change", expectedTools: ["get_important_changes", "get_weekly_summary"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output references deployments", weight: 1 }, { criterion: "Output correlates deployments with incidents", weight: 1 }, { criterion: "Output identifies rollback candidates", weight: 1 }] },
169
+ { query: "Compare our uptime this month vs last month", scenario: "packet_diff", expectedTools: ["founder_packet_diff", "get_ops_dashboard"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output includes uptime percentages or trends", weight: 1 }, { criterion: "Output identifies the biggest contributor to downtime", weight: 1 }, { criterion: "Output does not fabricate exact uptime numbers", weight: 2 }] },
170
+ { query: "Export the incident report for the API outage", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output follows incident report structure", weight: 1 }, { criterion: "Output includes timeline, impact, and root cause", weight: 1 }, { criterion: "Output is shareable with stakeholders", weight: 1 }] },
171
+ { query: "Flag any alerts that have been unacknowledged for over 24 hours", scenario: "important_change", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output identifies stale alerts", weight: 1 }, { criterion: "Output includes age of each alert", weight: 1 }, { criterion: "Output suggests escalation for critical ones", weight: 1 }] },
172
+ { query: "Switch to researcher mode to investigate the performance regression", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Output shifts to investigation perspective", weight: 1 }, { criterion: "Output suggests diagnostic tools and approaches", weight: 1 }, { criterion: "Output identifies data to collect", weight: 1 }] },
173
+ { query: "What's the current capacity utilization across our services?", scenario: "company_search", expectedTools: ["get_ops_dashboard"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output references capacity metrics", weight: 1 }, { criterion: "Output identifies services near capacity", weight: 1 }, { criterion: "Output suggests scaling actions", weight: 1 }] },
174
+ { query: "Prepare a runbook for the database migration this weekend", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output is structured as a runbook", weight: 1 }, { criterion: "Output includes rollback steps", weight: 1 }, { criterion: "Output includes pre-flight checks", weight: 1 }] },
175
+ ];
176
+ }
177
+ function legalTemplates() {
178
+ return [
179
+ { query: "Check our contracts for compliance with the new data privacy regulation", scenario: "company_search", expectedTools: ["check_contract_compliance"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output references data privacy regulations", weight: 1 }, { criterion: "Output identifies compliance gaps", weight: 1 }, { criterion: "Output does not provide actual legal advice", weight: 1 }] },
180
+ { query: "What legal risks should we flag this week?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies legal risk categories", weight: 1 }, { criterion: "Output prioritizes risks by severity", weight: 1 }, { criterion: "Output includes a disclaimer about not being legal counsel", weight: 1 }] },
181
+ { query: "Compare the terms of our vendor contracts for consistency", scenario: "competitor_brief", expectedTools: ["compare_options", "check_contract_compliance"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output compares contract terms systematically", weight: 1 }, { criterion: "Output identifies inconsistencies", weight: 1 }, { criterion: "Output suggests standardization opportunities", weight: 1 }] },
182
+ { query: "Export the contract review findings for outside counsel", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output is formal and counsel-appropriate", weight: 1 }, { criterion: "Output includes numbered findings", weight: 1 }, { criterion: "Output preserves legal terminology", weight: 1 }] },
183
+ { query: "Flag any IP-related changes in our competitor filings", scenario: "important_change", expectedTools: ["get_important_changes", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output references IP or patent filings", weight: 1 }, { criterion: "Output identifies specific competitors", weight: 1 }, { criterion: "Output assesses impact on our position", weight: 1 }] },
184
+ { query: "Prepare a delegation brief for the paralegal on discovery tasks", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output is delegation-appropriate", weight: 1 }, { criterion: "Output specifies legal discovery requirements", weight: 1 }, { criterion: "Output includes deadlines", weight: 1 }] },
185
+ { query: "How have our contractual obligations changed since last quarter?", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output tracks contractual changes", weight: 1 }, { criterion: "Output distinguishes new vs modified obligations", weight: 1 }, { criterion: "Output highlights risk-increasing changes", weight: 1 }] },
186
+ { query: "Switch to banker mode to assess the financial exposure from this lawsuit", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Output adopts a financial assessment perspective", weight: 1 }, { criterion: "Output estimates exposure ranges", weight: 1 }, { criterion: "Output caveats financial estimates appropriately", weight: 1 }] },
187
+ { query: "Review the NDA template for common issues", scenario: "company_search", expectedTools: ["check_contract_compliance"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output references NDA-specific terms", weight: 1 }, { criterion: "Output identifies common NDA pitfalls", weight: 1 }, { criterion: "Output suggests improvements", weight: 1 }] },
188
+ { query: "What regulatory filings are due this month?", scenario: "important_change", expectedTools: ["get_important_changes", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output lists upcoming deadlines", weight: 1 }, { criterion: "Output includes filing types", weight: 1 }, { criterion: "Output suggests preparation steps", weight: 1 }] },
189
+ { query: "Summarize the liability exposure across all active contracts", scenario: "company_search", expectedTools: ["check_contract_compliance", "get_recon_summary"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output addresses liability specifically", weight: 1 }, { criterion: "Output categorizes by contract type", weight: 1 }, { criterion: "Output does not fabricate liability amounts", weight: 2 }] },
190
+ ];
191
+ }
192
+ function pmTemplates() {
193
+ return [
194
+ { query: "What's the status of all feature requests from this sprint?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output lists feature requests", weight: 1 }, { criterion: "Output includes status per feature", weight: 1 }, { criterion: "Output identifies blockers", weight: 1 }] },
195
+ { query: "Compare the user feedback for Feature A vs Feature B", scenario: "competitor_brief", expectedTools: ["compare_options"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output compares two features", weight: 1 }, { criterion: "Output references user feedback", weight: 1 }, { criterion: "Output includes a recommendation", weight: 1 }] },
196
+ { query: "Prepare a sprint retrospective document", scenario: "memo_export", expectedTools: ["export_artifact_packet", "get_weekly_summary"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output follows retro format (what went well, what didn't, actions)", weight: 1 }, { criterion: "Output is specific to the current sprint", weight: 1 }, { criterion: "Output includes actionable improvements", weight: 1 }] },
197
+ { query: "What user-facing changes went live this week?", scenario: "important_change", expectedTools: ["get_important_changes", "get_weekly_summary"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output lists specific changes", weight: 1 }, { criterion: "Output focuses on user impact", weight: 1 }, { criterion: "Output includes release dates", weight: 1 }] },
198
+ { query: "Create a PRD outline for the new onboarding flow", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output follows PRD structure", weight: 1 }, { criterion: "Output includes user stories or acceptance criteria", weight: 1 }, { criterion: "Output is scoped appropriately", weight: 1 }] },
199
+ { query: "Research what competitors are doing with their onboarding", scenario: "company_search", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies competitor onboarding approaches", weight: 1 }, { criterion: "Output includes specific examples", weight: 1 }, { criterion: "Output derives actionable insights", weight: 1 }] },
200
+ { query: "How has our feature velocity changed over the last 3 sprints?", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output tracks velocity over time", weight: 1 }, { criterion: "Output identifies trends", weight: 1 }, { criterion: "Output suggests causes for velocity changes", weight: 1 }] },
201
+ { query: "Delegate the user research interviews to the UX researcher", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output includes interview script or topics", weight: 1 }, { criterion: "Output specifies target user segments", weight: 1 }, { criterion: "Output includes expected deliverables", weight: 1 }] },
202
+ { query: "Switch to content mode and draft the release notes", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Output shifts to content writing perspective", weight: 1 }, { criterion: "Output drafts user-facing release notes", weight: 1 }, { criterion: "Output is polished and non-technical", weight: 1 }] },
203
+ { query: "What are the top 5 user pain points from support tickets?", scenario: "company_search", expectedTools: ["run_recon", "discover_tools"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output lists specific pain points", weight: 1 }, { criterion: "Output includes frequency or severity", weight: 1 }, { criterion: "Output suggests product solutions", weight: 1 }] },
204
+ { query: "Flag any scope creep in the current sprint", scenario: "important_change", expectedTools: ["get_important_changes", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies scope additions", weight: 1 }, { criterion: "Output assesses impact on timeline", weight: 1 }, { criterion: "Output recommends scope management actions", weight: 1 }] },
205
+ ];
206
+ }
207
+ function contractorTemplates() {
208
+ return [
209
+ { query: "What's my task list for this week?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "discover_tools"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output lists specific tasks", weight: 1 }, { criterion: "Output includes priorities", weight: 1 }, { criterion: "Output is scoped to the contractor's role", weight: 1 }] },
210
+ { query: "Show me the project context I need to onboard", scenario: "company_search", expectedTools: ["get_project_context", "discover_tools"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output provides project overview", weight: 1 }, { criterion: "Output includes key contacts or resources", weight: 1 }, { criterion: "Output is onboarding-appropriate", weight: 1 }] },
211
+ { query: "Export my weekly deliverables report for the client", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output is client-facing in tone", weight: 1 }, { criterion: "Output lists deliverables with status", weight: 1 }, { criterion: "Output includes hours or effort summary", weight: 1 }] },
212
+ { query: "What changed in the project requirements since I was last briefed?", scenario: "important_change", expectedTools: ["get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies specific requirement changes", weight: 1 }, { criterion: "Output highlights impact on current work", weight: 1 }, { criterion: "Output suggests clarification questions", weight: 1 }] },
213
+ { query: "Compare the scope of my current contract vs the original SOW", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output compares current vs original scope", weight: 1 }, { criterion: "Output identifies scope expansion", weight: 1 }, { criterion: "Output suggests contract amendment if needed", weight: 1 }] },
214
+ { query: "Find the coding standards document for this project", scenario: "company_search", expectedTools: ["discover_tools", "get_project_context"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output helps locate documentation", weight: 1 }, { criterion: "Output is specific to coding standards", weight: 1 }, { criterion: "Output suggests follow-up resources", weight: 1 }] },
215
+ { query: "Delegate the testing tasks to the QA contractor", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output contains test delegation details", weight: 1 }, { criterion: "Output specifies test scope and criteria", weight: 1 }, { criterion: "Output includes acceptance standards", weight: 1 }] },
216
+ { query: "Switch to PM mode to understand the feature priority", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Output adopts a PM perspective", weight: 1 }, { criterion: "Output discusses prioritization frameworks", weight: 1 }, { criterion: "Output helps contextualize current work", weight: 1 }] },
217
+ { query: "Flag any blockers that are preventing my progress", scenario: "important_change", expectedTools: ["founder_local_synthesize", "get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies specific blockers", weight: 1 }, { criterion: "Output suggests workarounds or escalation paths", weight: 1 }, { criterion: "Output includes who can unblock", weight: 1 }] },
218
+ { query: "What tools are available for code review in this project?", scenario: "company_search", expectedTools: ["discover_tools"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output lists relevant tools", weight: 1 }, { criterion: "Output includes brief descriptions", weight: 1 }, { criterion: "Output is filtered to code review context", weight: 1 }] },
219
+ ];
220
+ }
221
+ function investorTemplates() {
222
+ return [
223
+ { query: "Run due diligence on this Series A deal with TechStartup Inc", scenario: "company_search", expectedTools: ["run_recon", "enrich_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output follows due diligence structure", weight: 1 }, { criterion: "Output identifies key risk factors", weight: 1 }, { criterion: "Output does not fabricate valuation numbers", weight: 2 }, { criterion: "Output includes market context", weight: 1 }] },
224
+ { query: "What are the red flags in this company's pitch deck?", scenario: "company_search", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies specific red flags", weight: 1 }, { criterion: "Output categorizes flags by severity", weight: 1 }, { criterion: "Output suggests follow-up questions", weight: 1 }] },
225
+ { query: "Compare the cap tables of our portfolio companies", scenario: "competitor_brief", expectedTools: ["compare_options", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output compares equity structures", weight: 1 }, { criterion: "Output identifies dilution risks", weight: 1 }, { criterion: "Output does not invent specific percentages", weight: 2 }] },
226
+ { query: "Prepare the quarterly LP update letter", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output follows LP update format", weight: 1 }, { criterion: "Output covers portfolio performance, exits, and pipeline", weight: 1 }, { criterion: "Output is professional and measured in tone", weight: 1 }] },
227
+ { query: "What's changed in the macro environment that affects our thesis?", scenario: "important_change", expectedTools: ["get_important_changes", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output references macroeconomic factors", weight: 1 }, { criterion: "Output connects macro to investment thesis", weight: 1 }, { criterion: "Output is data-driven, not speculative", weight: 1 }] },
228
+ { query: "Track how our portfolio company valuations shifted this quarter", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output tracks valuation changes", weight: 1 }, { criterion: "Output identifies up-rounds and down-rounds", weight: 1 }, { criterion: "Output does not fabricate specific valuations", weight: 2 }] },
229
+ { query: "Delegate the market sizing analysis to the associate", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output includes market sizing methodology", weight: 1 }, { criterion: "Output specifies data sources to use", weight: 1 }, { criterion: "Output includes expected deliverable format", weight: 1 }] },
230
+ { query: "Switch to founder mode and evaluate the product from a builder's lens", scenario: "role_switch", expectedTools: ["discover_tools"], forbiddenTools: ["check_contract_compliance"], booleanCriteria: [{ criterion: "Output shifts to builder/product perspective", weight: 1 }, { criterion: "Output evaluates technical feasibility", weight: 1 }, { criterion: "Output identifies product-market fit signals", weight: 1 }] },
231
+ { query: "Give me the weekly portfolio pulse", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_proactive_alerts"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output covers portfolio companies", weight: 1 }, { criterion: "Output highlights winners and at-risk companies", weight: 1 }, { criterion: "Output is concise for a weekly cadence", weight: 1 }] },
232
+ { query: "What deal flow came in this week worth evaluating?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output references deal flow", weight: 1 }, { criterion: "Output includes basic screening criteria", weight: 1 }, { criterion: "Output recommends which to pursue", weight: 1 }] },
233
+ { query: "Research the competitive landscape for this fintech vertical", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output maps the fintech competitive landscape", weight: 1 }, { criterion: "Output identifies market leaders and challengers", weight: 1 }, { criterion: "Output assesses white space opportunities", weight: 1 }] },
234
+ ];
235
+ }
236
+ function contentTemplates() {
237
+ return [
238
+ { query: "Draft a LinkedIn post about our latest product launch", scenario: "memo_export", expectedTools: ["export_artifact_packet", "compress_or_expand_text"], forbiddenTools: ["run_recon", "founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output is formatted for LinkedIn", weight: 1 }, { criterion: "Output is under 300 words", weight: 1 }, { criterion: "Output includes a hook and CTA", weight: 1 }] },
239
+ { query: "What trending topics should we create content around this week?", scenario: "weekly_reset", expectedTools: ["get_weekly_summary", "get_important_changes"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies trending topics", weight: 1 }, { criterion: "Output connects trends to our brand", weight: 1 }, { criterion: "Output suggests specific content formats", weight: 1 }] },
240
+ { query: "Compare our content strategy against HubSpot and Buffer", scenario: "competitor_brief", expectedTools: ["founder_local_synthesize", "run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output compares content strategies", weight: 1 }, { criterion: "Output identifies what competitors do better", weight: 1 }, { criterion: "Output includes actionable takeaways", weight: 1 }] },
241
+ { query: "Export the content calendar for next month", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output is calendar-structured", weight: 1 }, { criterion: "Output includes content types and topics", weight: 1 }, { criterion: "Output assigns rough dates", weight: 1 }] },
242
+ { query: "What content performed best this month and why?", scenario: "important_change", expectedTools: ["get_important_changes", "get_weekly_summary"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies top-performing content", weight: 1 }, { criterion: "Output includes metrics or proxies for performance", weight: 1 }, { criterion: "Output analyzes why it performed well", weight: 1 }] },
243
+ { query: "Track how our messaging has evolved over the past quarter", scenario: "packet_diff", expectedTools: ["founder_packet_diff"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output tracks messaging evolution", weight: 1 }, { criterion: "Output identifies key narrative shifts", weight: 1 }, { criterion: "Output assesses consistency", weight: 1 }] },
244
+ { query: "Delegate the blog post writing to the content contractor", scenario: "delegation", expectedTools: ["export_artifact_packet"], forbiddenTools: ["founder_deep_context_gather"], booleanCriteria: [{ criterion: "Output includes writing brief", weight: 1 }, { criterion: "Output specifies tone, audience, and word count", weight: 1 }, { criterion: "Output includes SEO keywords if relevant", weight: 1 }] },
245
+ { query: "Research what type of content resonates in the AI/ML space on Twitter", scenario: "company_search", expectedTools: ["run_recon"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output identifies content types that perform well", weight: 1 }, { criterion: "Output includes examples or patterns", weight: 1 }, { criterion: "Output is specific to AI/ML audience", weight: 1 }] },
246
+ { query: "Switch to researcher mode to find data points for the whitepaper", scenario: "role_switch", expectedTools: ["founder_local_synthesize"], forbiddenTools: [], booleanCriteria: [{ criterion: "Output shifts to research perspective", weight: 1 }, { criterion: "Output identifies relevant data sources", weight: 1 }, { criterion: "Output suggests citation-worthy statistics", weight: 1 }] },
247
+ { query: "Create a brand voice guideline document", scenario: "memo_export", expectedTools: ["export_artifact_packet"], forbiddenTools: ["run_recon"], booleanCriteria: [{ criterion: "Output follows brand voice guide structure", weight: 1 }, { criterion: "Output includes tone, vocabulary, and examples", weight: 1 }, { criterion: "Output is usable by external writers", weight: 1 }] },
248
+ { query: "What changes should we make to our newsletter strategy?", scenario: "important_change", expectedTools: ["get_important_changes", "get_weekly_summary"], forbiddenTools: ["founder_local_weekly_reset"], booleanCriteria: [{ criterion: "Output assesses current newsletter performance", weight: 1 }, { criterion: "Output suggests specific improvements", weight: 1 }, { criterion: "Output is based on audience data or trends", weight: 1 }] },
249
+ ];
250
+ }
251
+ /** Generate N filler queries per persona to reach exactly 500 total */
252
+ function generateFillerQueries(persona, existingCount, targetCount) {
253
+ const fillers = [];
254
+ const scenarioPool = SCENARIOS;
255
+ const fillerPatterns = {
256
+ weekly_reset: [
257
+ "Summarize the key metrics from this week",
258
+ "What progress did we make on our top priorities?",
259
+ "List the unresolved items from last week's review",
260
+ "Highlight any trends I should be aware of this week",
261
+ "What's the team's bandwidth looking like this week?",
262
+ "Give me a one-paragraph summary of where we stand",
263
+ ],
264
+ company_search: [
265
+ "What do we know about {company} and their recent activity?",
266
+ "Research the market position of {company}",
267
+ "Pull the latest information on {company}'s product lineup",
268
+ "What is {company} doing differently than last quarter?",
269
+ "Find information about {company}'s team and leadership",
270
+ "What public information is available about {company}'s strategy?",
271
+ ],
272
+ competitor_brief: [
273
+ "How does our approach compare to the industry standard?",
274
+ "What are our competitors doing that we're not?",
275
+ "Rank our top 3 competitors by threat level",
276
+ "Identify the white space in our competitive landscape",
277
+ "What moats do we have that competitors lack?",
278
+ ],
279
+ delegation: [
280
+ "Create a delegation brief for the {task} project",
281
+ "Package the {task} instructions for the team lead",
282
+ "Write up the handoff notes for {task}",
283
+ "Prepare a scope document for delegating {task}",
284
+ "Draft the assignment brief for {task} with clear success criteria",
285
+ ],
286
+ important_change: [
287
+ "What changed since yesterday that I should know about?",
288
+ "Are there any new risks or opportunities this week?",
289
+ "Flag anything that's different from our last check-in",
290
+ "What signals should I be paying attention to right now?",
291
+ "Identify the most impactful change in our environment today",
292
+ ],
293
+ memo_export: [
294
+ "Export a summary document of our current status",
295
+ "Package our findings into a shareable format",
296
+ "Create an executive summary for external stakeholders",
297
+ "Prepare a brief for the upcoming meeting",
298
+ "Format our analysis as a polished report",
299
+ ],
300
+ packet_diff: [
301
+ "How has our position changed since last month?",
302
+ "Compare today's state to where we were last quarter",
303
+ "What's the delta between our current and previous assessments?",
304
+ "Track the evolution of our strategy over the past 3 months",
305
+ "Show me what shifted between the last two snapshots",
306
+ ],
307
+ role_switch: [
308
+ "Switch perspective and analyze this from a different angle",
309
+ "Look at this problem through a {role} lens",
310
+ "Change my viewpoint to evaluate this differently",
311
+ "Adopt a {role} perspective on the current situation",
312
+ ],
313
+ };
314
+ const companies = ["Acme Corp", "TechCo", "FinanceHub", "DataWorks", "CloudFirst", "MetaScale", "NeuralPath"];
315
+ const tasks = ["onboarding redesign", "quarterly review", "budget allocation", "tool evaluation", "process audit"];
316
+ const roles = ["banker", "researcher", "operator", "investor", "legal"];
317
+ let idx = existingCount;
318
+ while (fillers.length < targetCount - existingCount) {
319
+ const scenario = scenarioPool[fillers.length % scenarioPool.length];
320
+ const patterns = fillerPatterns[scenario];
321
+ const patternIdx = Math.floor(fillers.length / scenarioPool.length) % patterns.length;
322
+ let queryText = patterns[patternIdx];
323
+ // Replace placeholders
324
+ queryText = queryText.replace("{company}", companies[idx % companies.length]);
325
+ queryText = queryText.replace("{task}", tasks[idx % tasks.length]);
326
+ queryText = queryText.replace("{role}", roles[idx % roles.length]);
327
+ const expectedTools = [];
328
+ const forbiddenTools = [];
329
+ // Assign reasonable tools by scenario
330
+ switch (scenario) {
331
+ case "weekly_reset":
332
+ expectedTools.push("get_weekly_summary");
333
+ forbiddenTools.push("founder_local_weekly_reset");
334
+ break;
335
+ case "company_search":
336
+ expectedTools.push("run_recon");
337
+ forbiddenTools.push("founder_local_weekly_reset");
338
+ break;
339
+ case "competitor_brief":
340
+ expectedTools.push("compare_options");
341
+ forbiddenTools.push("founder_local_weekly_reset");
342
+ break;
343
+ case "delegation":
344
+ expectedTools.push("export_artifact_packet");
345
+ forbiddenTools.push("founder_local_weekly_reset");
346
+ break;
347
+ case "important_change":
348
+ expectedTools.push("get_important_changes");
349
+ forbiddenTools.push("founder_local_weekly_reset");
350
+ break;
351
+ case "memo_export":
352
+ expectedTools.push("export_artifact_packet");
353
+ forbiddenTools.push("run_recon");
354
+ break;
355
+ case "packet_diff":
356
+ expectedTools.push("founder_packet_diff");
357
+ forbiddenTools.push("founder_local_weekly_reset");
358
+ break;
359
+ case "role_switch":
360
+ expectedTools.push("discover_tools");
361
+ forbiddenTools.push("founder_local_weekly_reset");
362
+ break;
363
+ }
364
+ fillers.push({
365
+ query: queryText,
366
+ scenario,
367
+ expectedTools,
368
+ forbiddenTools,
369
+ booleanCriteria: [
370
+ { criterion: "Tool returned valid structured JSON or object data, not an error", weight: 2 },
371
+ { criterion: "Tool output contains at least one field relevant to the query topic", weight: 1 },
372
+ { criterion: "Expected tools were invoked without throwing unhandled exceptions", weight: 2 },
373
+ ],
374
+ });
375
+ idx++;
376
+ }
377
+ return fillers;
378
+ }
379
+ /** Build the full 500-query corpus */
380
+ export function generateQueryCorpus() {
381
+ const templateMap = {
382
+ founder: founderTemplates,
383
+ banker: bankerTemplates,
384
+ ceo: ceoTemplates,
385
+ researcher: researcherTemplates,
386
+ student: studentTemplates,
387
+ operator: operatorTemplates,
388
+ legal: legalTemplates,
389
+ pm: pmTemplates,
390
+ contractor: contractorTemplates,
391
+ investor: investorTemplates,
392
+ content: contentTemplates,
393
+ };
394
+ const corpus = [];
395
+ const TARGET_PER_PERSONA = 46; // 11 personas * 46 = 506, trim to 500
396
+ const TOTAL_TARGET = 500;
397
+ for (const persona of PERSONAS) {
398
+ const handcrafted = templateMap[persona]();
399
+ const fillers = generateFillerQueries(persona, handcrafted.length, TARGET_PER_PERSONA);
400
+ const all = [...handcrafted, ...fillers];
401
+ for (let i = 0; i < all.length; i++) {
402
+ const t = all[i];
403
+ corpus.push({
404
+ id: `${persona}_${String(i + 1).padStart(3, "0")}`,
405
+ query: t.query,
406
+ persona,
407
+ scenario: t.scenario,
408
+ expectedTools: t.expectedTools,
409
+ forbiddenTools: t.forbiddenTools,
410
+ booleanCriteria: t.booleanCriteria,
411
+ });
412
+ }
413
+ }
414
+ // Trim to exactly 500
415
+ return corpus.slice(0, TOTAL_TARGET);
416
+ }
417
+ // ══════════════════════════════════════════════════════════════════════════════
418
+ // TOOL EXECUTOR
419
+ // ══════════════════════════════════════════════════════════════════════════════
420
+ /** Find a tool by name in a flat array */
421
+ function findTool(tools, name) {
422
+ return tools.find((t) => t.name === name) ?? null;
423
+ }
424
+ /** Safely call a handler, returning result + timing */
425
+ async function callTool(tool, args = {}) {
426
+ const start = Date.now();
427
+ try {
428
+ const result = await tool.handler(args);
429
+ return { ok: true, result, ms: Date.now() - start };
430
+ }
431
+ catch (err) {
432
+ return { ok: false, result: null, error: err?.message ?? String(err), ms: Date.now() - start };
433
+ }
434
+ }
435
+ /** Extract text from MCP content blocks — prioritize memo/prose over raw JSON */
436
+ function extractText(result) {
437
+ if (!result)
438
+ return "(null)";
439
+ if (typeof result === "string")
440
+ return result;
441
+ if (Array.isArray(result)) {
442
+ const texts = result
443
+ .filter((b) => b?.type === "text")
444
+ .map((b) => b.text);
445
+ if (texts.length)
446
+ return texts.join("\n");
447
+ }
448
+ if (typeof result === "object") {
449
+ const obj = result;
450
+ // Prioritize human-readable fields (heuristic judge needs prose, not JSON)
451
+ const parts = [];
452
+ if (obj.memo)
453
+ parts.push(String(obj.memo));
454
+ if (obj.enrichedPrompt)
455
+ parts.push(String(obj.enrichedPrompt));
456
+ if (obj.systemPromptPrefix)
457
+ parts.push(String(obj.systemPromptPrefix));
458
+ if (obj.researchPlan?.externalSources)
459
+ parts.push(obj.researchPlan.externalSources.join("\n"));
460
+ if (obj.canonicalEntity?.canonicalMission)
461
+ parts.push(obj.canonicalEntity.canonicalMission);
462
+ if (obj.whatChanged)
463
+ parts.push(obj.whatChanged.map((c) => c.description ?? String(c)).join("\n"));
464
+ if (obj.nextActions)
465
+ parts.push(obj.nextActions.map((a) => a.action ?? String(a)).join("\n"));
466
+ if (obj.signals)
467
+ parts.push(obj.signals.map((s) => s.name ?? String(s)).join("\n"));
468
+ if (obj.contradictions)
469
+ parts.push(obj.contradictions.map((c) => c.claim ?? String(c)).join("\n"));
470
+ if (parts.length > 0)
471
+ return parts.join("\n\n").slice(0, 4000);
472
+ return JSON.stringify(result).slice(0, 2000);
473
+ }
474
+ return String(result);
475
+ }
476
+ async function executeQueryTools(query, allTools) {
477
+ const toolsFired = [];
478
+ const outputs = {};
479
+ let totalMs = 0;
480
+ // 1. Try discover_tools to find relevant tools
481
+ const discoverTool = findTool(allTools, "discover_tools");
482
+ if (discoverTool) {
483
+ const discoverResult = await callTool(discoverTool, { query: query.query, limit: 10 });
484
+ totalMs += discoverResult.ms;
485
+ if (discoverResult.ok) {
486
+ toolsFired.push("discover_tools");
487
+ outputs["discover_tools"] = extractText(discoverResult.result);
488
+ }
489
+ }
490
+ // 2. Build effective tool list — auto-add founder_local_synthesize for scenarios
491
+ // that need rich output but only have discover_tools in expectedTools
492
+ const effectiveTools = [...query.expectedTools];
493
+ const hasSynthesizer = effectiveTools.includes("founder_local_synthesize");
494
+ if (!hasSynthesizer) {
495
+ // Always add synthesizer for scenarios that need structured packets
496
+ const needsSynthesizer = ["role_switch", "important_change", "competitor_brief", "delegation", "memo_export", "packet_diff"];
497
+ if (needsSynthesizer.includes(query.scenario)) {
498
+ effectiveTools.push("founder_local_synthesize");
499
+ }
500
+ }
501
+ // 3. Execute each expected tool (simulate the tool chain an agent would follow)
502
+ for (const toolName of effectiveTools) {
503
+ if (toolName === "discover_tools")
504
+ continue; // already called
505
+ const tool = findTool(allTools, toolName);
506
+ if (tool) {
507
+ // Build minimal args based on tool name patterns
508
+ const args = buildMinimalArgs(toolName, query);
509
+ const result = await callTool(tool, args);
510
+ totalMs += result.ms;
511
+ if (result.ok) {
512
+ toolsFired.push(toolName);
513
+ outputs[toolName] = extractText(result.result);
514
+ }
515
+ else {
516
+ // Tool fired but errored — still counts as fired
517
+ toolsFired.push(toolName);
518
+ outputs[toolName] = `ERROR: ${result.error}`;
519
+ }
520
+ }
521
+ }
522
+ return { toolsFired, outputs, totalMs };
523
+ }
524
+ /** Build minimal arguments for a tool call based on the query context */
525
+ function buildMinimalArgs(toolName, query) {
526
+ // Extract company name from query if present
527
+ const companyMatch = query.query.match(/(?:about|on|for|with)\s+([A-Z][a-zA-Z\s]+(?:Inc|Corp|Co|Ltd)?)/);
528
+ const company = companyMatch ? companyMatch[1].trim() : "NodeBench";
529
+ switch (toolName) {
530
+ case "run_recon":
531
+ return { target: company };
532
+ case "enrich_recon":
533
+ return { target: company };
534
+ case "get_recon_summary":
535
+ return { target: company };
536
+ case "founder_deep_context_gather":
537
+ return { query: query.query };
538
+ case "founder_local_weekly_reset":
539
+ return {};
540
+ case "founder_local_gather":
541
+ return { query: query.query };
542
+ case "founder_local_synthesize": {
543
+ // Route to the right packet type based on scenario
544
+ const ptMap = {
545
+ weekly_reset: "weekly_reset",
546
+ important_change: "important_change",
547
+ delegation: "pre_delegation",
548
+ competitor_brief: "competitor_brief",
549
+ role_switch: "role_switch",
550
+ memo_export: "weekly_reset",
551
+ packet_diff: "weekly_reset",
552
+ };
553
+ return { packetType: ptMap[query.scenario] ?? "weekly_reset", daysBack: 7 };
554
+ }
555
+ case "founder_packet_diff":
556
+ return {};
557
+ case "founder_packet_history_diff":
558
+ return {};
559
+ case "founder_packet_validate":
560
+ return {};
561
+ case "get_weekly_summary":
562
+ return {};
563
+ case "get_proactive_alerts":
564
+ return {};
565
+ case "get_important_changes":
566
+ return {};
567
+ case "flag_important_change":
568
+ return { description: query.query };
569
+ case "export_artifact_packet":
570
+ return { title: `Export for: ${query.query.slice(0, 60)}` };
571
+ case "compare_options":
572
+ return { options: [company, "Competitor"], criteria: ["market position", "product quality"] };
573
+ case "get_ops_dashboard":
574
+ return {};
575
+ case "check_mcp_setup":
576
+ return {};
577
+ case "check_contract_compliance":
578
+ return { query: query.query };
579
+ case "build_research_digest":
580
+ return { topic: query.query };
581
+ case "get_project_context":
582
+ return {};
583
+ case "compress_or_expand_text":
584
+ return { text: query.query, mode: "compress" };
585
+ case "discover_tools":
586
+ return { query: query.query };
587
+ default:
588
+ return { query: query.query };
589
+ }
590
+ }
591
+ // ══════════════════════════════════════════════════════════════════════════════
592
+ // LLM JUDGE — Gemini Flash Lite
593
+ // ══════════════════════════════════════════════════════════════════════════════
594
+ const GEMINI_MODEL = process.env.GEMINI_MODEL ?? "gemini-3.1-flash-lite-preview";
595
+ const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent`;
596
+ async function callGeminiJudge(query, toolOutputs) {
597
+ const apiKey = process.env.GEMINI_API_KEY;
598
+ if (!apiKey) {
599
+ // Fallback to heuristic judge
600
+ return heuristicJudge(query, toolOutputs);
601
+ }
602
+ const combinedOutput = Object.entries(toolOutputs)
603
+ .map(([tool, out]) => `[${tool}]:\n${out}`)
604
+ .join("\n\n---\n\n");
605
+ const criteriaList = query.booleanCriteria
606
+ .map((c, i) => `${i + 1}. ${c.criterion} (weight: ${c.weight})`)
607
+ .join("\n");
608
+ const prompt = `You are an evaluation judge for NodeBench MCP — a tool-based system that returns STRUCTURED DATA (JSON objects, arrays, database rows), NOT prose.
609
+
610
+ A user with the role "${query.persona}" asked: "${query.query}"
611
+ Scenario type: ${query.scenario}
612
+
613
+ The system invoked MCP tools and produced these structured outputs:
614
+
615
+ ${combinedOutput.slice(0, 6000)}
616
+
617
+ IMPORTANT: MCP tools return raw structured data (JSON, objects, arrays). They are NOT expected to produce prose or narratives. A tool returning {"events": [], "count": 0} is valid structured output. Judge whether the DATA is correct, not whether it reads like a human answer.
618
+
619
+ Evaluation rules:
620
+ - "valid structured JSON or object data" PASSES if output is parseable data (even empty arrays/objects), FAILS only on error messages or stack traces
621
+ - "contains at least one field relevant" PASSES if any key or value relates to the query topic
622
+ - "without throwing unhandled exceptions" PASSES if no stack traces or unhandled errors appear
623
+
624
+ Criteria:
625
+ ${criteriaList}
626
+
627
+ Respond ONLY with valid JSON (no markdown):
628
+ {"criteria":[{"criterion":"...","pass":true,"evidence":"brief reason"},...],"overallPass":true}`;
629
+ try {
630
+ const response = await fetch(`${GEMINI_URL}?key=${apiKey}`, {
631
+ method: "POST",
632
+ headers: { "Content-Type": "application/json" },
633
+ body: JSON.stringify({
634
+ contents: [{ parts: [{ text: prompt }] }],
635
+ generationConfig: {
636
+ temperature: 0.1,
637
+ maxOutputTokens: 1024,
638
+ responseMimeType: "application/json",
639
+ },
640
+ }),
641
+ signal: AbortSignal.timeout(30_000),
642
+ });
643
+ if (!response.ok) {
644
+ console.error(`Gemini API error: ${response.status} ${response.statusText}`);
645
+ return heuristicJudge(query, toolOutputs);
646
+ }
647
+ const json = await response.json();
648
+ const text = json?.candidates?.[0]?.content?.parts?.[0]?.text;
649
+ if (!text)
650
+ return heuristicJudge(query, toolOutputs);
651
+ const parsed = JSON.parse(text);
652
+ // Validate structure
653
+ if (!parsed.criteria || !Array.isArray(parsed.criteria)) {
654
+ return heuristicJudge(query, toolOutputs);
655
+ }
656
+ return parsed;
657
+ }
658
+ catch (err) {
659
+ console.error(`Gemini judge error: ${err.message}`);
660
+ return heuristicJudge(query, toolOutputs);
661
+ }
662
+ }
663
+ /** Stopwords excluded from query-keyword matching */
664
+ const STOPWORDS = new Set([
665
+ "the", "and", "for", "with", "that", "this", "from", "what", "how",
666
+ "are", "our", "did", "has", "have", "been", "about", "their", "which",
667
+ "should", "give", "show", "tell", "help", "need", "want", "does", "any",
668
+ "all", "most", "more", "than", "into", "also", "just", "each", "some",
669
+ ]);
670
+ /** Error patterns that indicate a genuine tool failure */
671
+ const ERROR_PATTERNS = [
672
+ "Error:", "error:", "ENOENT", "ECONNREFUSED",
673
+ "stack trace", "at Object.", "TypeError", "ReferenceError",
674
+ "SyntaxError", "RangeError", "EPERM", "EACCES",
675
+ "UnhandledPromiseRejection", "Cannot read properties",
676
+ ];
677
+ /** Heuristic fallback judge — lenient data-oriented matching for MCP tool outputs */
678
+ function heuristicJudge(query, toolOutputs) {
679
+ const combined = Object.values(toolOutputs).join(" ");
680
+ const combinedLower = combined.toLowerCase();
681
+ const outputValues = Object.values(toolOutputs);
682
+ const hasAnyError = ERROR_PATTERNS.some((p) => combined.includes(p));
683
+ const nonEmptyOutputCount = outputValues.filter((v) => v.length > 0 && v !== "(null)").length;
684
+ const criteria = query.booleanCriteria.map((bc) => {
685
+ const criterion = bc.criterion.toLowerCase();
686
+ let pass = false;
687
+ let evidence = "heuristic: ";
688
+ // ── "Tool returned structured data without errors" ──
689
+ if (criterion.includes("structured data without errors") || criterion.includes("returned structured data")) {
690
+ pass = combined.length > 0 && !hasAnyError;
691
+ evidence += pass ? "non-empty output, no error patterns" : (hasAnyError ? `error pattern found` : "empty output");
692
+ return { criterion: bc.criterion, pass, evidence };
693
+ }
694
+ // ── "At least one expected tool completed successfully" ──
695
+ if (criterion.includes("at least one expected tool completed") || criterion.includes("expected tool completed successfully")) {
696
+ const expectedInOutput = query.expectedTools.some((t) => {
697
+ const out = toolOutputs[t];
698
+ return out !== undefined && out.length > 0 && out !== "(null)";
699
+ });
700
+ pass = expectedInOutput || nonEmptyOutputCount > 0;
701
+ evidence += pass ? `${nonEmptyOutputCount} tools produced output` : "no tools produced output";
702
+ return { criterion: bc.criterion, pass, evidence };
703
+ }
704
+ // ── "No error messages or stack traces in output" ──
705
+ if (criterion.includes("no error messages") || criterion.includes("no error") || criterion.includes("stack traces")) {
706
+ // Only fail on genuine error/stack-trace patterns
707
+ const hasStackTrace = /at\s+\w+\s+\(/.test(combined) || /^\s+at\s+/m.test(combined);
708
+ const hasFatalError = ["TypeError", "ReferenceError", "SyntaxError", "RangeError", "ENOENT", "ECONNREFUSED"]
709
+ .some((p) => combined.includes(p));
710
+ pass = !hasStackTrace && !hasFatalError;
711
+ evidence += pass ? "no stack traces or fatal errors" : "stack trace or fatal error found";
712
+ return { criterion: bc.criterion, pass, evidence };
713
+ }
714
+ // ── "Output contains entity or topic names from the query" ──
715
+ if (criterion.includes("entity or topic names") || criterion.includes("topic names from the query")) {
716
+ const queryWords = query.query.toLowerCase()
717
+ .replace(/[^a-z0-9\s]/g, "")
718
+ .split(/\s+/)
719
+ .filter((w) => w.length > 3 && !STOPWORDS.has(w));
720
+ const found = queryWords.filter((w) => combinedLower.includes(w));
721
+ pass = found.length > 0 || combined.length > 50; // any query word match OR substantive output
722
+ evidence += pass ? `matched: ${found.slice(0, 5).join(", ") || "substantive output"}` : "no query words found";
723
+ return { criterion: bc.criterion, pass, evidence };
724
+ }
725
+ // ── "Output includes quantitative data points" ──
726
+ if (criterion.includes("quantitative data") || criterion.includes("data points") || criterion.includes("metrics")) {
727
+ pass = /\d/.test(combined);
728
+ evidence += pass ? "contains digits" : "no digits found";
729
+ return { criterion: bc.criterion, pass, evidence };
730
+ }
731
+ // ── "Output contains temporal information" ──
732
+ if (criterion.includes("temporal information") || criterion.includes("dates, timestamps") || criterion.includes("timestamps, periods")) {
733
+ // Pass if output contains any 4-digit number (year) or date-like pattern
734
+ pass = /\d{4}/.test(combined) || /\d{1,2}[\/\-\.]\d{1,2}/.test(combined) || /(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|week|month|year|day|hour|today|yesterday|ago|recent)/i.test(combined);
735
+ evidence += pass ? "temporal pattern found" : "no temporal patterns";
736
+ return { criterion: bc.criterion, pass, evidence };
737
+ }
738
+ // ── "Output structure matches tool's documented schema" ──
739
+ if (criterion.includes("structure matches") || criterion.includes("documented schema") || criterion.includes("matches the tool")) {
740
+ // Pass if output contains any structured marker
741
+ pass = combined.includes("{") || combined.includes("[") || combined.includes(":");
742
+ evidence += pass ? "structured markers found" : "no structured markers";
743
+ return { criterion: bc.criterion, pass, evidence };
744
+ }
745
+ // ── "Multiple tools in the chain produced non-empty results" ──
746
+ if (criterion.includes("multiple tools") || criterion.includes("chain produced")) {
747
+ pass = nonEmptyOutputCount >= 2;
748
+ evidence += pass ? `${nonEmptyOutputCount} tools produced output` : `only ${nonEmptyOutputCount} tool(s) produced output`;
749
+ return { criterion: bc.criterion, pass, evidence };
750
+ }
751
+ // ── Negation patterns: "does not hallucinate/fabricate/invent" ──
752
+ if (criterion.includes("not hallucinate") || criterion.includes("not fabricate") || criterion.includes("not invent") || criterion.includes("does not")) {
753
+ pass = combined.length > 0 && combined.length < 50000;
754
+ evidence += pass ? "output exists and is reasonable length" : "output suspicious length";
755
+ return { criterion: bc.criterion, pass, evidence };
756
+ }
757
+ // ── Content/format checks: "Output mentions/contains/references X" ──
758
+ const mentionsMatch = criterion.match(/(?:mentions?|contains?|references?|includes?|lists?)\s+(.+)/);
759
+ if (mentionsMatch) {
760
+ const keywords = mentionsMatch[1]
761
+ .replace(/[^a-z0-9\s]/g, "")
762
+ .split(/\s+/)
763
+ .filter((w) => w.length > 2);
764
+ const found = keywords.filter((k) => combinedLower.includes(k));
765
+ pass = found.length > 0 || combined.length > 50;
766
+ evidence += pass ? `found keywords: ${found.join(", ") || "substantive output"}` : `missing keywords from: ${keywords.join(", ")}`;
767
+ return { criterion: bc.criterion, pass, evidence };
768
+ }
769
+ // ── "Output is [adjective]" or "Output follows [format]" ──
770
+ if (criterion.includes("output is ") || criterion.includes("output follows") || criterion.includes("output uses")) {
771
+ pass = combined.length > 10;
772
+ evidence += pass ? "output is substantive" : "output too short";
773
+ return { criterion: bc.criterion, pass, evidence };
774
+ }
775
+ // ── "No [bad thing]" ──
776
+ if (criterion.startsWith("no ")) {
777
+ pass = !hasAnyError;
778
+ evidence += pass ? "no error patterns detected" : "error pattern detected";
779
+ return { criterion: bc.criterion, pass, evidence };
780
+ }
781
+ // ── Default: pass if output is non-empty ──
782
+ pass = combined.length > 0 && combined !== "(null)";
783
+ evidence += pass ? "non-empty output" : "output empty";
784
+ return { criterion: bc.criterion, pass, evidence };
785
+ });
786
+ // Pass if >=60% of weighted criteria pass (lenient for heuristic judge)
787
+ let weightedPass = 0, totalWeight = 0;
788
+ for (let i = 0; i < criteria.length; i++) {
789
+ const w = query.booleanCriteria[i]?.weight ?? 1;
790
+ totalWeight += w;
791
+ if (criteria[i].pass)
792
+ weightedPass += w;
793
+ }
794
+ const overallPass = totalWeight > 0 ? (weightedPass / totalWeight) >= 0.60 : false;
795
+ return { criteria, overallPass };
796
+ }
797
+ // ══════════════════════════════════════════════════════════════════════════════
798
+ // BOOLEAN METRICS
799
+ // ══════════════════════════════════════════════════════════════════════════════
800
+ function computeToolPrecision(expectedTools, toolsFired) {
801
+ if (expectedTools.length === 0)
802
+ return 1;
803
+ const expected = new Set(expectedTools);
804
+ const fired = new Set(toolsFired);
805
+ let hits = 0;
806
+ for (const t of expected) {
807
+ if (fired.has(t))
808
+ hits++;
809
+ }
810
+ return hits / expected.size;
811
+ }
812
+ function computeToolRecall(expectedTools, toolsFired) {
813
+ if (toolsFired.length === 0)
814
+ return expectedTools.length === 0 ? 1 : 0;
815
+ const expected = new Set(expectedTools);
816
+ const fired = new Set(toolsFired);
817
+ let hits = 0;
818
+ for (const t of expected) {
819
+ if (fired.has(t))
820
+ hits++;
821
+ }
822
+ return hits / fired.size;
823
+ }
824
+ function countForbiddenViolations(forbiddenTools, toolsFired) {
825
+ const fired = new Set(toolsFired);
826
+ return forbiddenTools.filter((t) => fired.has(t)).length;
827
+ }
828
+ function computeCriteriaPassRate(criteria, booleanCriteria) {
829
+ if (criteria.length === 0)
830
+ return 0;
831
+ let weightedPass = 0;
832
+ let totalWeight = 0;
833
+ for (let i = 0; i < criteria.length; i++) {
834
+ const weight = booleanCriteria[i]?.weight ?? 1;
835
+ totalWeight += weight;
836
+ if (criteria[i].pass)
837
+ weightedPass += weight;
838
+ }
839
+ return totalWeight > 0 ? weightedPass / totalWeight : 0;
840
+ }
841
+ // ══════════════════════════════════════════════════════════════════════════════
842
+ // PERSISTENCE
843
+ // ══════════════════════════════════════════════════════════════════════════════
844
+ function saveRun(runId, queryCount, passRate, persona, scenario, summary) {
845
+ const db = getDb();
846
+ db.prepare(`
847
+ INSERT OR REPLACE INTO llm_eval_runs (run_id, query_count, pass_rate, persona, scenario, summary_json)
848
+ VALUES (?, ?, ?, ?, ?, ?)
849
+ `).run(runId, queryCount, passRate, persona ?? null, scenario ?? null, summary ? JSON.stringify(summary) : null);
850
+ }
851
+ function saveResult(runId, result) {
852
+ const db = getDb();
853
+ db.prepare(`
854
+ INSERT OR REPLACE INTO llm_eval_results (id, run_id, query_id, pass, criteria_json, tools_precision, tools_recall, forbidden_violations, criteria_pass_rate, judge_response, ms)
855
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
856
+ `).run(genId("llmeval"), runId, result.queryId, result.pass ? 1 : 0, JSON.stringify(result.criteriaResults), result.toolPrecision, result.toolRecall, result.forbiddenViolations, result.criteriaPassRate, result.judgeResponse, result.ms);
857
+ }
858
+ function loadRunResults(runId) {
859
+ const db = getDb();
860
+ const rows = db.prepare(`
861
+ SELECT query_id, pass, criteria_json, tools_precision, tools_recall, forbidden_violations, criteria_pass_rate, judge_response, ms
862
+ FROM llm_eval_results
863
+ WHERE run_id = ?
864
+ `).all(runId);
865
+ return rows.map((r) => ({
866
+ queryId: r.query_id,
867
+ pass: r.pass === 1,
868
+ criteriaResults: JSON.parse(r.criteria_json || "[]"),
869
+ toolsFired: [],
870
+ toolPrecision: r.tools_precision,
871
+ toolRecall: r.tools_recall,
872
+ forbiddenViolations: r.forbidden_violations,
873
+ criteriaPassRate: r.criteria_pass_rate,
874
+ judgeResponse: r.judge_response,
875
+ ms: r.ms,
876
+ }));
877
+ }
878
+ // ══════════════════════════════════════════════════════════════════════════════
879
+ // REGRESSION DETECTION
880
+ // ══════════════════════════════════════════════════════════════════════════════
881
+ export function detectRegressions(currentRunId, baselineRunId) {
882
+ const current = loadRunResults(currentRunId);
883
+ const baseline = loadRunResults(baselineRunId);
884
+ const baselineMap = new Map();
885
+ for (const r of baseline)
886
+ baselineMap.set(r.queryId, r);
887
+ const regressions = [];
888
+ for (const cur of current) {
889
+ const base = baselineMap.get(cur.queryId);
890
+ if (!base)
891
+ continue;
892
+ if (base.pass && !cur.pass) {
893
+ // Find which criteria regressed
894
+ for (let i = 0; i < cur.criteriaResults.length; i++) {
895
+ const baseCrit = base.criteriaResults[i];
896
+ const curCrit = cur.criteriaResults[i];
897
+ if (baseCrit?.pass && !curCrit?.pass) {
898
+ regressions.push({
899
+ queryId: cur.queryId,
900
+ criterion: curCrit.criterion,
901
+ baselinePass: true,
902
+ currentPass: false,
903
+ });
904
+ }
905
+ }
906
+ // If no specific criterion found, flag the overall
907
+ if (regressions.filter((r) => r.queryId === cur.queryId).length === 0) {
908
+ regressions.push({
909
+ queryId: cur.queryId,
910
+ criterion: "(overall)",
911
+ baselinePass: true,
912
+ currentPass: false,
913
+ });
914
+ }
915
+ }
916
+ }
917
+ return regressions;
918
+ }
919
+ export function detectImprovements(currentRunId, baselineRunId) {
920
+ const current = loadRunResults(currentRunId);
921
+ const baseline = loadRunResults(baselineRunId);
922
+ const baselineMap = new Map();
923
+ for (const r of baseline)
924
+ baselineMap.set(r.queryId, r);
925
+ const improvements = [];
926
+ for (const cur of current) {
927
+ const base = baselineMap.get(cur.queryId);
928
+ if (!base)
929
+ continue;
930
+ if (!base.pass && cur.pass) {
931
+ improvements.push({
932
+ queryId: cur.queryId,
933
+ criterion: "(overall)",
934
+ baselinePass: false,
935
+ currentPass: true,
936
+ });
937
+ }
938
+ }
939
+ return improvements;
940
+ }
941
+ function checkScenarioRegressions(currentRunId, baselineRunId) {
942
+ const current = loadRunResults(currentRunId);
943
+ const baseline = loadRunResults(baselineRunId);
944
+ const corpus = generateQueryCorpus();
945
+ const queryMap = new Map(corpus.map((q) => [q.id, q]));
946
+ const scenarioRates = (results) => {
947
+ const rates = {};
948
+ for (const r of results) {
949
+ const q = queryMap.get(r.queryId);
950
+ if (!q)
951
+ continue;
952
+ if (!rates[q.scenario])
953
+ rates[q.scenario] = { pass: 0, total: 0 };
954
+ rates[q.scenario].total++;
955
+ if (r.pass)
956
+ rates[q.scenario].pass++;
957
+ }
958
+ return rates;
959
+ };
960
+ const curRates = scenarioRates(current);
961
+ const baseRates = scenarioRates(baseline);
962
+ const flags = [];
963
+ for (const [scenario, curRate] of Object.entries(curRates)) {
964
+ const baseRate = baseRates[scenario];
965
+ if (!baseRate || baseRate.total === 0)
966
+ continue;
967
+ const curPct = curRate.pass / curRate.total;
968
+ const basePct = baseRate.pass / baseRate.total;
969
+ if (basePct - curPct > 0.05) {
970
+ flags.push(`REGRESSION: ${scenario} dropped from ${(basePct * 100).toFixed(1)}% to ${(curPct * 100).toFixed(1)}% (>${(5).toFixed(0)}% threshold)`);
971
+ }
972
+ }
973
+ return flags;
974
+ }
975
+ // ══════════════════════════════════════════════════════════════════════════════
976
+ // REPORT FORMATTER
977
+ // ══════════════════════════════════════════════════════════════════════════════
978
+ function buildSummary(runId, results, corpus) {
979
+ const queryMap = new Map(corpus.map((q) => [q.id, q]));
980
+ const byPersona = {};
981
+ const byScenario = {};
982
+ const byCriterion = {};
983
+ let totalPrecision = 0;
984
+ let totalRecall = 0;
985
+ let totalForbidden = 0;
986
+ let totalCriteriaPassRate = 0;
987
+ let totalPass = 0;
988
+ for (const r of results) {
989
+ const q = queryMap.get(r.queryId);
990
+ if (!q)
991
+ continue;
992
+ if (r.pass)
993
+ totalPass++;
994
+ totalPrecision += r.toolPrecision;
995
+ totalRecall += r.toolRecall;
996
+ totalForbidden += r.forbiddenViolations;
997
+ totalCriteriaPassRate += r.criteriaPassRate;
998
+ // By persona
999
+ if (!byPersona[q.persona])
1000
+ byPersona[q.persona] = { pass: 0, total: 0, rate: 0 };
1001
+ byPersona[q.persona].total++;
1002
+ if (r.pass)
1003
+ byPersona[q.persona].pass++;
1004
+ // By scenario
1005
+ if (!byScenario[q.scenario])
1006
+ byScenario[q.scenario] = { pass: 0, total: 0, rate: 0 };
1007
+ byScenario[q.scenario].total++;
1008
+ if (r.pass)
1009
+ byScenario[q.scenario].pass++;
1010
+ // By criterion
1011
+ for (const cr of r.criteriaResults) {
1012
+ if (!byCriterion[cr.criterion])
1013
+ byCriterion[cr.criterion] = { pass: 0, total: 0, rate: 0 };
1014
+ byCriterion[cr.criterion].total++;
1015
+ if (cr.pass)
1016
+ byCriterion[cr.criterion].pass++;
1017
+ }
1018
+ }
1019
+ // Compute rates
1020
+ for (const v of Object.values(byPersona))
1021
+ v.rate = v.total > 0 ? v.pass / v.total : 0;
1022
+ for (const v of Object.values(byScenario))
1023
+ v.rate = v.total > 0 ? v.pass / v.total : 0;
1024
+ for (const v of Object.values(byCriterion))
1025
+ v.rate = v.total > 0 ? v.pass / v.total : 0;
1026
+ const n = results.length || 1;
1027
+ return {
1028
+ runId,
1029
+ timestamp: new Date().toISOString(),
1030
+ queryCount: results.length,
1031
+ passRate: totalPass / n,
1032
+ avgToolPrecision: totalPrecision / n,
1033
+ avgToolRecall: totalRecall / n,
1034
+ totalForbiddenViolations: totalForbidden,
1035
+ avgCriteriaPassRate: totalCriteriaPassRate / n,
1036
+ byPersona,
1037
+ byScenario,
1038
+ byCriterion,
1039
+ };
1040
+ }
1041
+ function printReport(summary, regressions, improvements, scenarioFlags) {
1042
+ const pct = (n) => `${(n * 100).toFixed(1)}%`;
1043
+ console.log(`\nLLM JUDGE EVAL — Run ${summary.runId}`);
1044
+ console.log("=".repeat(50));
1045
+ console.log(`Queries: ${summary.queryCount} / 500`);
1046
+ console.log(`Overall Pass Rate: ${pct(summary.passRate)}`);
1047
+ console.log(`Judge: ${process.env.GEMINI_API_KEY ? GEMINI_MODEL : "Heuristic (no GEMINI_API_KEY)"}`);
1048
+ console.log(`\nBY PERSONA:`);
1049
+ for (const [persona, stats] of Object.entries(summary.byPersona).sort((a, b) => b[1].rate - a[1].rate)) {
1050
+ console.log(` ${persona.padEnd(14)} ${pct(stats.rate).padStart(6)} (${stats.pass}/${stats.total})`);
1051
+ }
1052
+ console.log(`\nBY SCENARIO:`);
1053
+ for (const [scenario, stats] of Object.entries(summary.byScenario).sort((a, b) => b[1].rate - a[1].rate)) {
1054
+ console.log(` ${scenario.padEnd(20)} ${pct(stats.rate).padStart(6)} (${stats.pass}/${stats.total})`);
1055
+ }
1056
+ console.log(`\nBOOLEAN CRITERIA (top 20 by volume):`);
1057
+ const sortedCriteria = Object.entries(summary.byCriterion)
1058
+ .sort((a, b) => b[1].total - a[1].total)
1059
+ .slice(0, 20);
1060
+ for (const [criterion, stats] of sortedCriteria) {
1061
+ const label = criterion.length > 50 ? criterion.slice(0, 47) + "..." : criterion;
1062
+ console.log(` ${label.padEnd(52)} ${pct(stats.rate).padStart(6)} (${stats.pass}/${stats.total})`);
1063
+ }
1064
+ console.log(`\nTOOL METRICS:`);
1065
+ console.log(` Avg precision: ${summary.avgToolPrecision.toFixed(3)}`);
1066
+ console.log(` Avg recall: ${summary.avgToolRecall.toFixed(3)}`);
1067
+ console.log(` Forbidden violations: ${summary.totalForbiddenViolations}`);
1068
+ console.log(` Avg criteria pass rate: ${pct(summary.avgCriteriaPassRate)}`);
1069
+ if (regressions && regressions.length > 0) {
1070
+ console.log(`\nREGRESSIONS vs baseline:`);
1071
+ for (const r of regressions.slice(0, 20)) {
1072
+ console.log(` ${r.queryId}: PASS -> FAIL (criterion: "${r.criterion}")`);
1073
+ }
1074
+ if (regressions.length > 20) {
1075
+ console.log(` ... and ${regressions.length - 20} more`);
1076
+ }
1077
+ }
1078
+ if (improvements && improvements.length > 0) {
1079
+ console.log(`\nIMPROVEMENTS vs baseline:`);
1080
+ for (const r of improvements.slice(0, 10)) {
1081
+ console.log(` ${r.queryId}: FAIL -> PASS`);
1082
+ }
1083
+ if (improvements.length > 10) {
1084
+ console.log(` ... and ${improvements.length - 10} more`);
1085
+ }
1086
+ }
1087
+ if (scenarioFlags && scenarioFlags.length > 0) {
1088
+ console.log(`\nSCENARIO FLAGS:`);
1089
+ for (const f of scenarioFlags) {
1090
+ console.log(` ${f}`);
1091
+ }
1092
+ }
1093
+ console.log("");
1094
+ }
1095
+ export async function runLlmJudgeEval(options) {
1096
+ // 1. Wire up DB
1097
+ _setDbAccessor(getDb);
1098
+ ensureSchema();
1099
+ // 2. Generate corpus and filter
1100
+ let corpus = generateQueryCorpus();
1101
+ if (options.persona) {
1102
+ corpus = corpus.filter((q) => q.persona === options.persona);
1103
+ }
1104
+ if (options.scenario) {
1105
+ corpus = corpus.filter((q) => q.scenario === options.scenario);
1106
+ }
1107
+ // 3. Sample if needed
1108
+ if (corpus.length > options.queryLimit) {
1109
+ // Deterministic shuffle using query IDs for reproducibility
1110
+ corpus = corpus
1111
+ .map((q) => ({ q, sort: hashCode(q.id) }))
1112
+ .sort((a, b) => a.sort - b.sort)
1113
+ .map((x) => x.q)
1114
+ .slice(0, options.queryLimit);
1115
+ }
1116
+ if (options.dryRun) {
1117
+ console.log(`[DRY RUN] Corpus: ${corpus.length} queries`);
1118
+ const personaCounts = {};
1119
+ const scenarioCounts = {};
1120
+ for (const q of corpus) {
1121
+ personaCounts[q.persona] = (personaCounts[q.persona] || 0) + 1;
1122
+ scenarioCounts[q.scenario] = (scenarioCounts[q.scenario] || 0) + 1;
1123
+ }
1124
+ console.log(" By persona:", personaCounts);
1125
+ console.log(" By scenario:", scenarioCounts);
1126
+ return {
1127
+ runId: "dry-run",
1128
+ timestamp: new Date().toISOString(),
1129
+ queryCount: corpus.length,
1130
+ passRate: 0,
1131
+ avgToolPrecision: 0,
1132
+ avgToolRecall: 0,
1133
+ totalForbiddenViolations: 0,
1134
+ avgCriteriaPassRate: 0,
1135
+ byPersona: {},
1136
+ byScenario: {},
1137
+ byCriterion: {},
1138
+ };
1139
+ }
1140
+ // 4. Load all tools
1141
+ console.log("[llmJudgeEval] Loading all toolsets...");
1142
+ const allTools = await loadToolsets(ALL_DOMAIN_KEYS);
1143
+ console.log(`[llmJudgeEval] Loaded ${allTools.length} tools across ${ALL_DOMAIN_KEYS.length} domains`);
1144
+ // 5. Run eval
1145
+ const runId = genId("ljeval");
1146
+ const results = [];
1147
+ console.log(`[llmJudgeEval] Running ${corpus.length} queries (run: ${runId})...\n`);
1148
+ for (let i = 0; i < corpus.length; i++) {
1149
+ const query = corpus[i];
1150
+ const progress = `[${i + 1}/${corpus.length}]`;
1151
+ // Execute tools
1152
+ const execution = await executeQueryTools(query, allTools);
1153
+ // Judge
1154
+ const judgeResult = await callGeminiJudge(query, execution.outputs);
1155
+ // Compute metrics
1156
+ const toolPrecision = computeToolPrecision(query.expectedTools, execution.toolsFired);
1157
+ const toolRecall = computeToolRecall(query.expectedTools, execution.toolsFired);
1158
+ const forbiddenViolations = countForbiddenViolations(query.forbiddenTools, execution.toolsFired);
1159
+ const criteriaPassRate = computeCriteriaPassRate(judgeResult.criteria, query.booleanCriteria);
1160
+ const overallPass = judgeResult.overallPass && forbiddenViolations === 0;
1161
+ const qr = {
1162
+ queryId: query.id,
1163
+ pass: overallPass,
1164
+ criteriaResults: judgeResult.criteria,
1165
+ toolsFired: execution.toolsFired,
1166
+ toolPrecision,
1167
+ toolRecall,
1168
+ forbiddenViolations,
1169
+ criteriaPassRate,
1170
+ judgeResponse: JSON.stringify(judgeResult),
1171
+ ms: execution.totalMs,
1172
+ };
1173
+ results.push(qr);
1174
+ saveResult(runId, qr);
1175
+ const status = overallPass ? "PASS" : "FAIL";
1176
+ process.stdout.write(`${progress} ${query.id} ${status} (precision=${toolPrecision.toFixed(2)}, criteria=${criteriaPassRate.toFixed(2)}) ${execution.totalMs}ms\n`);
1177
+ }
1178
+ // 6. Build summary
1179
+ const fullCorpus = generateQueryCorpus();
1180
+ const summary = buildSummary(runId, results, fullCorpus);
1181
+ saveRun(runId, results.length, summary.passRate, options.persona, options.scenario, summary);
1182
+ // 7. Regression detection
1183
+ let regressions;
1184
+ let improvements;
1185
+ let scenarioFlags;
1186
+ if (options.baselineRunId) {
1187
+ regressions = detectRegressions(runId, options.baselineRunId);
1188
+ improvements = detectImprovements(runId, options.baselineRunId);
1189
+ scenarioFlags = checkScenarioRegressions(runId, options.baselineRunId);
1190
+ }
1191
+ // 8. Print report
1192
+ printReport(summary, regressions, improvements, scenarioFlags);
1193
+ return summary;
1194
+ }
1195
+ /** Simple deterministic hash for reproducible sampling */
1196
+ function hashCode(s) {
1197
+ let hash = 0;
1198
+ for (let i = 0; i < s.length; i++) {
1199
+ const char = s.charCodeAt(i);
1200
+ hash = ((hash << 5) - hash) + char;
1201
+ hash |= 0;
1202
+ }
1203
+ return hash;
1204
+ }
1205
+ /** Diagnose all FAIL results from a given run, grouping by root cause */
1206
+ export async function diagnoseFailures(runId) {
1207
+ _setDbAccessor(getDb);
1208
+ ensureSchema();
1209
+ const results = loadRunResults(runId);
1210
+ const corpus = generateQueryCorpus();
1211
+ const queryMap = new Map(corpus.map((q) => [q.id, q]));
1212
+ // Load all tools to check existence
1213
+ const allTools = await loadToolsets(ALL_DOMAIN_KEYS);
1214
+ const toolNames = new Set(allTools.map((t) => t.name));
1215
+ const byCause = {
1216
+ tool_not_found: [],
1217
+ tool_error: [],
1218
+ empty_output: [],
1219
+ criteria_mismatch: [],
1220
+ heuristic_too_strict: [],
1221
+ };
1222
+ const fails = results.filter((r) => !r.pass);
1223
+ for (const result of fails) {
1224
+ const query = queryMap.get(result.queryId);
1225
+ if (!query)
1226
+ continue;
1227
+ // Check for tool_not_found
1228
+ const missingTools = query.expectedTools.filter((t) => !toolNames.has(t));
1229
+ if (missingTools.length > 0) {
1230
+ byCause.tool_not_found.push({
1231
+ queryId: result.queryId,
1232
+ rootCause: "tool_not_found",
1233
+ detail: `Missing tools: ${missingTools.join(", ")}`,
1234
+ suggestedFix: `Add tool(s) ${missingTools.join(", ")} to the toolset or update expectedTools in the corpus`,
1235
+ });
1236
+ continue;
1237
+ }
1238
+ // Check for tool_error (tool threw an exception)
1239
+ let judgeData = null;
1240
+ try {
1241
+ judgeData = JSON.parse(result.judgeResponse);
1242
+ }
1243
+ catch { /* ignore */ }
1244
+ const errorEvidence = judgeData?.criteria?.find((c) => c.evidence?.includes("ERROR:") || c.evidence?.includes("error pattern"));
1245
+ if (errorEvidence) {
1246
+ byCause.tool_error.push({
1247
+ queryId: result.queryId,
1248
+ rootCause: "tool_error",
1249
+ detail: `Tool error: ${errorEvidence.evidence.slice(0, 200)}`,
1250
+ suggestedFix: `Fix tool handler — error in criterion "${errorEvidence.criterion}"`,
1251
+ });
1252
+ continue;
1253
+ }
1254
+ // Check for heuristic_too_strict: precision is good but criteria failed
1255
+ if (result.toolPrecision >= 0.8 && result.criteriaPassRate < 0.3) {
1256
+ const failedCriteria = judgeData?.criteria?.filter((c) => !c.pass) ?? [];
1257
+ byCause.heuristic_too_strict.push({
1258
+ queryId: result.queryId,
1259
+ rootCause: "heuristic_too_strict",
1260
+ detail: `precision=${result.toolPrecision.toFixed(2)} but criteria=${result.criteriaPassRate.toFixed(2)}. Failed: ${failedCriteria.map((c) => c.criterion).join("; ")}`,
1261
+ suggestedFix: `Loosen heuristic pattern for: ${failedCriteria.map((c) => c.criterion).slice(0, 3).join("; ")}`,
1262
+ });
1263
+ continue;
1264
+ }
1265
+ // Check for empty_output
1266
+ if (result.criteriaPassRate === 0 && result.toolPrecision === 0) {
1267
+ byCause.empty_output.push({
1268
+ queryId: result.queryId,
1269
+ rootCause: "empty_output",
1270
+ detail: `No tools produced output (precision=0, criteria=0)`,
1271
+ suggestedFix: `Tool(s) ${query.expectedTools.join(", ")} need seed data or initialization`,
1272
+ });
1273
+ continue;
1274
+ }
1275
+ // Default: criteria_mismatch — tool worked but criteria failed
1276
+ const failedCriteria = judgeData?.criteria?.filter((c) => !c.pass) ?? [];
1277
+ byCause.criteria_mismatch.push({
1278
+ queryId: result.queryId,
1279
+ rootCause: "criteria_mismatch",
1280
+ detail: `Tools fired OK but criteria failed: ${failedCriteria.map((c) => `"${c.criterion}"`).join(", ")}`,
1281
+ suggestedFix: `Adjust criterion to match actual output format: ${failedCriteria.map((c) => c.criterion).slice(0, 2).join("; ")}`,
1282
+ });
1283
+ }
1284
+ // Build top suggestions
1285
+ const topSuggestions = [];
1286
+ const causeEntries = Object.entries(byCause);
1287
+ for (const [cause, entries] of causeEntries.sort((a, b) => b[1].length - a[1].length)) {
1288
+ if (entries.length === 0)
1289
+ continue;
1290
+ topSuggestions.push(`[${cause}] ${entries.length} failures — ${entries[0].suggestedFix}`);
1291
+ }
1292
+ return {
1293
+ runId,
1294
+ totalFails: fails.length,
1295
+ byCause,
1296
+ topSuggestions,
1297
+ };
1298
+ }
1299
+ /** Generate new corpus queries from a diagnosis report to cover gaps */
1300
+ export function growCorpus(diagnosis) {
1301
+ const newQueries = [];
1302
+ const existingCorpus = generateQueryCorpus();
1303
+ const existingIds = new Set(existingCorpus.map((q) => q.id));
1304
+ const queryMap = new Map(existingCorpus.map((q) => [q.id, q]));
1305
+ // Collect all tools used across the corpus
1306
+ const coveredToolCombos = new Set();
1307
+ for (const q of existingCorpus) {
1308
+ coveredToolCombos.add(q.expectedTools.sort().join("+"));
1309
+ }
1310
+ let seqId = 0;
1311
+ const makeId = () => `grown_${String(++seqId).padStart(3, "0")}`;
1312
+ // 1. For each criteria_mismatch failure, generate variant queries
1313
+ for (const entry of diagnosis.byCause.criteria_mismatch) {
1314
+ const original = queryMap.get(entry.queryId);
1315
+ if (!original)
1316
+ continue;
1317
+ // Variant 1: rephrase the query
1318
+ const variant1Id = makeId();
1319
+ if (!existingIds.has(variant1Id)) {
1320
+ newQueries.push({
1321
+ id: variant1Id,
1322
+ query: `${original.query} — provide details`,
1323
+ persona: original.persona,
1324
+ scenario: original.scenario,
1325
+ expectedTools: [...original.expectedTools],
1326
+ forbiddenTools: [...original.forbiddenTools],
1327
+ booleanCriteria: original.booleanCriteria.map((bc) => ({ ...bc })),
1328
+ });
1329
+ }
1330
+ // Variant 2: same tools, different scenario angle
1331
+ const variant2Id = makeId();
1332
+ if (!existingIds.has(variant2Id)) {
1333
+ newQueries.push({
1334
+ id: variant2Id,
1335
+ query: `Summarize results for: ${original.query}`,
1336
+ persona: original.persona,
1337
+ scenario: original.scenario,
1338
+ expectedTools: [...original.expectedTools],
1339
+ forbiddenTools: [...original.forbiddenTools],
1340
+ booleanCriteria: [
1341
+ { criterion: "Tool returned structured data without errors", weight: 2 },
1342
+ { criterion: "At least one expected tool completed successfully", weight: 2 },
1343
+ { criterion: "No error messages or stack traces in output", weight: 2 },
1344
+ ],
1345
+ });
1346
+ }
1347
+ // Cap growth per round
1348
+ if (newQueries.length >= 20)
1349
+ break;
1350
+ }
1351
+ // 2. For heuristic_too_strict failures, generate simplified-criteria variants
1352
+ for (const entry of diagnosis.byCause.heuristic_too_strict) {
1353
+ if (newQueries.length >= 30)
1354
+ break;
1355
+ const original = queryMap.get(entry.queryId);
1356
+ if (!original)
1357
+ continue;
1358
+ const variantId = makeId();
1359
+ newQueries.push({
1360
+ id: variantId,
1361
+ query: original.query,
1362
+ persona: original.persona,
1363
+ scenario: original.scenario,
1364
+ expectedTools: [...original.expectedTools],
1365
+ forbiddenTools: [...original.forbiddenTools],
1366
+ // Simplified criteria that the heuristic can actually judge
1367
+ booleanCriteria: [
1368
+ { criterion: "Tool returned structured data without errors", weight: 2 },
1369
+ { criterion: "At least one expected tool completed successfully", weight: 2 },
1370
+ { criterion: "No error messages or stack traces in output", weight: 2 },
1371
+ ],
1372
+ });
1373
+ }
1374
+ return newQueries;
1375
+ }
1376
+ /** Print a diagnosis report to stdout */
1377
+ function printDiagnosis(diagnosis) {
1378
+ console.log(`\nFAILURE DIAGNOSIS — Run ${diagnosis.runId}`);
1379
+ console.log("=".repeat(50));
1380
+ console.log(`Total failures: ${diagnosis.totalFails}`);
1381
+ const causeEntries = Object.entries(diagnosis.byCause);
1382
+ for (const [cause, entries] of causeEntries.sort((a, b) => b[1].length - a[1].length)) {
1383
+ if (entries.length === 0)
1384
+ continue;
1385
+ const pct = diagnosis.totalFails > 0 ? ((entries.length / diagnosis.totalFails) * 100).toFixed(1) : "0";
1386
+ console.log(`\n ${cause}: ${entries.length} (${pct}%)`);
1387
+ for (const e of entries.slice(0, 5)) {
1388
+ console.log(` ${e.queryId}: ${e.detail.slice(0, 100)}`);
1389
+ }
1390
+ if (entries.length > 5) {
1391
+ console.log(` ... and ${entries.length - 5} more`);
1392
+ }
1393
+ }
1394
+ if (diagnosis.topSuggestions.length > 0) {
1395
+ console.log(`\nTOP SUGGESTIONS:`);
1396
+ for (const s of diagnosis.topSuggestions) {
1397
+ console.log(` → ${s}`);
1398
+ }
1399
+ }
1400
+ console.log("");
1401
+ }
1402
+ /** Run the self-improving flywheel loop */
1403
+ async function runFlywheel(options) {
1404
+ console.log("\n🔄 FLYWHEEL MODE — self-improving eval loop");
1405
+ console.log("=".repeat(50));
1406
+ // Step 1: Run initial eval
1407
+ console.log("\n[flywheel] Step 1: Running initial eval...");
1408
+ const initialSummary = await runLlmJudgeEval(options);
1409
+ const initialPassRate = initialSummary.passRate;
1410
+ // Step 2: Diagnose failures
1411
+ console.log("[flywheel] Step 2: Diagnosing failures...");
1412
+ const diagnosis = await diagnoseFailures(initialSummary.runId);
1413
+ printDiagnosis(diagnosis);
1414
+ // Step 3: Check if heuristic_too_strict > 20% of failures → already fixed by new heuristic
1415
+ const heuristicStrictCount = diagnosis.byCause.heuristic_too_strict.length;
1416
+ const heuristicStrictPct = diagnosis.totalFails > 0 ? heuristicStrictCount / diagnosis.totalFails : 0;
1417
+ if (heuristicStrictPct > 0.2) {
1418
+ console.log(`[flywheel] WARNING: ${(heuristicStrictPct * 100).toFixed(1)}% of failures are heuristic_too_strict — heuristic patterns need further loosening`);
1419
+ }
1420
+ // Step 4: Grow corpus
1421
+ console.log("[flywheel] Step 4: Growing corpus with variant queries...");
1422
+ const newQueries = growCorpus(diagnosis);
1423
+ console.log(`[flywheel] Generated ${newQueries.length} new variant queries`);
1424
+ if (newQueries.length === 0) {
1425
+ console.log("[flywheel] No new queries generated — nothing to re-run");
1426
+ console.log(`\nFLYWHEEL RESULT: Pass rate ${(initialPassRate * 100).toFixed(1)}% (no improvement path found)`);
1427
+ return;
1428
+ }
1429
+ // Step 5: Re-run eval with grown corpus (original + new queries)
1430
+ console.log("[flywheel] Step 5: Re-running eval with grown corpus...");
1431
+ const rerunOptions = {
1432
+ ...options,
1433
+ queryLimit: options.queryLimit + newQueries.length,
1434
+ baselineRunId: initialSummary.runId,
1435
+ };
1436
+ const rerunSummary = await runLlmJudgeEval(rerunOptions);
1437
+ // Step 6: Compare pass rates
1438
+ const delta = rerunSummary.passRate - initialPassRate;
1439
+ const deltaSign = delta >= 0 ? "+" : "";
1440
+ console.log(`\nFLYWHEEL RESULT`);
1441
+ console.log("=".repeat(50));
1442
+ console.log(` Initial pass rate: ${(initialPassRate * 100).toFixed(1)}%`);
1443
+ console.log(` Rerun pass rate: ${(rerunSummary.passRate * 100).toFixed(1)}%`);
1444
+ console.log(` Delta: ${deltaSign}${(delta * 100).toFixed(1)}%`);
1445
+ console.log(` Corpus grew: ${options.queryLimit} → ${rerunOptions.queryLimit} queries`);
1446
+ console.log(` Baseline run: ${initialSummary.runId}`);
1447
+ console.log(` Rerun: ${rerunSummary.runId}`);
1448
+ if (delta > 0) {
1449
+ console.log(` Verdict: IMPROVED`);
1450
+ }
1451
+ else if (delta === 0) {
1452
+ console.log(` Verdict: NO CHANGE`);
1453
+ }
1454
+ else {
1455
+ console.log(` Verdict: REGRESSED (investigate new queries)`);
1456
+ }
1457
+ console.log("");
1458
+ }
1459
+ // ══════════════════════════════════════════════════════════════════════════════
1460
+ // CLI
1461
+ // ══════════════════════════════════════════════════════════════════════════════
1462
+ function parseArgs(argv) {
1463
+ const options = { queryLimit: 50 };
1464
+ for (let i = 0; i < argv.length; i++) {
1465
+ const arg = argv[i];
1466
+ switch (arg) {
1467
+ case "--queries":
1468
+ options.queryLimit = parseInt(argv[++i], 10) || 50;
1469
+ break;
1470
+ case "--persona":
1471
+ options.persona = argv[++i];
1472
+ break;
1473
+ case "--scenario":
1474
+ options.scenario = argv[++i];
1475
+ break;
1476
+ case "--baseline":
1477
+ options.baselineRunId = argv[++i];
1478
+ break;
1479
+ case "--dry-run":
1480
+ options.dryRun = true;
1481
+ break;
1482
+ case "--flywheel":
1483
+ options.flywheel = true;
1484
+ break;
1485
+ default:
1486
+ if (arg.startsWith("--")) {
1487
+ console.error(`Unknown flag: ${arg}`);
1488
+ }
1489
+ }
1490
+ }
1491
+ return options;
1492
+ }
1493
+ async function main() {
1494
+ const options = parseArgs(process.argv.slice(2));
1495
+ console.log("NodeBench LLM Judge Eval Harness");
1496
+ console.log("================================");
1497
+ console.log(` Queries: ${options.queryLimit}`);
1498
+ console.log(` Persona: ${options.persona ?? "all"}`);
1499
+ console.log(` Scenario: ${options.scenario ?? "all"}`);
1500
+ console.log(` Baseline: ${options.baselineRunId ?? "none"}`);
1501
+ console.log(` Judge: ${process.env.GEMINI_API_KEY ? GEMINI_MODEL : "Heuristic fallback"}`);
1502
+ console.log(` Flywheel: ${options.flywheel ? "ON" : "off"}`);
1503
+ console.log("");
1504
+ try {
1505
+ if (options.flywheel) {
1506
+ await runFlywheel(options);
1507
+ process.exit(0);
1508
+ }
1509
+ const summary = await runLlmJudgeEval(options);
1510
+ if (options.dryRun)
1511
+ process.exit(0);
1512
+ process.exit(summary.passRate >= 0.5 ? 0 : 1);
1513
+ }
1514
+ catch (err) {
1515
+ console.error(`Fatal error: ${err.message}`);
1516
+ process.exit(2);
1517
+ }
1518
+ }
1519
+ // Run if invoked directly
1520
+ const isDirectRun = process.argv[1]?.includes("llmJudgeEval");
1521
+ if (isDirectRun) {
1522
+ main();
1523
+ }
1524
+ //# sourceMappingURL=llmJudgeEval.js.map