@martinloop/mcp 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +10 -2
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +2 -0
  4. package/dist/package-version.d.ts +1 -1
  5. package/dist/package-version.js +1 -1
  6. package/dist/prompts.js +19 -1
  7. package/dist/resources.d.ts +2 -0
  8. package/dist/resources.js +202 -148
  9. package/dist/server-validation.d.ts +1 -1
  10. package/dist/server-validation.js +17 -0
  11. package/dist/server.d.ts +23 -0
  12. package/dist/server.js +161 -5
  13. package/dist/tools/doctor.js +6 -0
  14. package/dist/tools/tool-support.d.ts +1 -1
  15. package/dist/tools/tool-support.js +1 -1
  16. package/dist/vendor/adapters/claude-cli.d.ts +2 -2
  17. package/dist/vendor/adapters/claude-cli.js +22 -7
  18. package/dist/vendor/adapters/cli-bridge.d.ts +7 -0
  19. package/dist/vendor/adapters/cli-bridge.js +94 -8
  20. package/dist/vendor/adapters/codex-launcher.js +18 -7
  21. package/dist/vendor/adapters/openai-compatible.js +90 -57
  22. package/dist/vendor/contracts/index.d.ts +1 -1
  23. package/dist/vendor/contracts/index.js +1 -0
  24. package/dist/vendor/core/index.d.ts +10 -2
  25. package/dist/vendor/core/index.js +31 -3
  26. package/dist/vendor/core/persistence/memory-store.d.ts +63 -0
  27. package/dist/vendor/core/persistence/memory-store.js +115 -0
  28. package/dist/vendor/core/persistence/trace-store.d.ts +85 -0
  29. package/dist/vendor/core/persistence/trace-store.js +125 -0
  30. package/dist/vendor/core/repo-analyzer.d.ts +53 -0
  31. package/dist/vendor/core/repo-analyzer.js +263 -0
  32. package/dist/vendor/core/routing.d.ts +42 -0
  33. package/dist/vendor/core/routing.js +156 -5
  34. package/dist/vendor/core/test-integrity.d.ts +64 -0
  35. package/dist/vendor/core/test-integrity.js +197 -0
  36. package/dist/workflow-state.d.ts +1 -1
  37. package/dist/workflow-state.js +12 -4
  38. package/package.json +76 -76
  39. package/server.json +21 -21
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Append-Only Trace Store
3
+ *
4
+ * Aggregates run data over time to build institutional memory. Every governed
5
+ * run's routing decision, cost, failure class, and outcome is appended to a
6
+ * local JSONL trace file. Data is NEVER overwritten — only appended.
7
+ *
8
+ * The trace store feeds:
9
+ * - classifyRoute() with historicalDirectSuccessRate from real past runs
10
+ * - Cost prediction models with actual spend data
11
+ * - Failure pattern detection across runs
12
+ * - Team analytics and optimization recommendations
13
+ *
14
+ * Storage: `<runsRoot>/_martin/trace-log.jsonl` (one JSON object per line)
15
+ */
16
+ import { appendFile, mkdir, readFile } from "node:fs/promises";
17
+ import { join, resolve } from "node:path";
18
+ const TRACE_DIRECTORY = "_martin";
19
+ const TRACE_FILENAME = "trace-log.jsonl";
20
+ function resolveTracePath(runsRoot) {
21
+ return join(resolve(runsRoot), TRACE_DIRECTORY, TRACE_FILENAME);
22
+ }
23
+ /**
24
+ * Append a trace entry to the local trace store. Never overwrites.
25
+ */
26
+ export async function appendTraceEntry(runsRoot, entry) {
27
+ const tracePath = resolveTracePath(runsRoot);
28
+ await mkdir(join(resolve(runsRoot), TRACE_DIRECTORY), { recursive: true });
29
+ const line = JSON.stringify(entry) + "\n";
30
+ await appendFile(tracePath, line, "utf8");
31
+ }
32
+ /**
33
+ * Read all trace entries from the local trace store.
34
+ */
35
+ export async function readTraceEntries(runsRoot) {
36
+ const tracePath = resolveTracePath(runsRoot);
37
+ try {
38
+ const raw = await readFile(tracePath, "utf8");
39
+ return raw
40
+ .split("\n")
41
+ .filter((line) => line.trim().length > 0)
42
+ .map((line) => {
43
+ try {
44
+ return JSON.parse(line);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ })
50
+ .filter((entry) => entry !== null);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ /**
57
+ * Aggregate trace entries into summary statistics.
58
+ * Used by classifyRoute() to get historicalDirectSuccessRate.
59
+ */
60
+ export function aggregateTraces(entries) {
61
+ if (entries.length === 0) {
62
+ return {
63
+ totalRuns: 0,
64
+ directRuns: 0,
65
+ managerRuns: 0,
66
+ consensusRuns: 0,
67
+ directSuccessRate: 0,
68
+ managerSuccessRate: 0,
69
+ averageCostUsd: 0,
70
+ averagePreWorkBurnPct: 0,
71
+ totalSpendUsd: 0,
72
+ totalSavedUsd: 0,
73
+ topFailureClasses: [],
74
+ averageAttemptsPerRun: 0,
75
+ lastUpdated: new Date().toISOString()
76
+ };
77
+ }
78
+ const direct = entries.filter((e) => e.selectedRoute === "direct");
79
+ const manager = entries.filter((e) => e.selectedRoute === "manager");
80
+ const consensus = entries.filter((e) => e.selectedRoute === "consensus");
81
+ const directSuccesses = direct.filter((e) => e.status === "completed" && e.verificationPassed);
82
+ const managerSuccesses = manager.filter((e) => e.status === "completed" && e.verificationPassed);
83
+ const totalCost = entries.reduce((sum, e) => sum + e.actualCostUsd, 0);
84
+ const totalBudget = entries.reduce((sum, e) => sum + e.budgetUsd, 0);
85
+ const totalAttempts = entries.reduce((sum, e) => sum + e.attempts, 0);
86
+ const totalBurnPct = entries.reduce((sum, e) => sum + e.preWorkBurnPct, 0);
87
+ // Count failure classes
88
+ const failureCounts = new Map();
89
+ for (const entry of entries) {
90
+ if (entry.failureClass) {
91
+ failureCounts.set(entry.failureClass, (failureCounts.get(entry.failureClass) ?? 0) + 1);
92
+ }
93
+ }
94
+ const topFailureClasses = [...failureCounts.entries()]
95
+ .map(([failureClass, count]) => ({ failureClass, count }))
96
+ .sort((a, b) => b.count - a.count)
97
+ .slice(0, 10);
98
+ return {
99
+ totalRuns: entries.length,
100
+ directRuns: direct.length,
101
+ managerRuns: manager.length,
102
+ consensusRuns: consensus.length,
103
+ directSuccessRate: direct.length > 0 ? directSuccesses.length / direct.length : 0,
104
+ managerSuccessRate: manager.length > 0 ? managerSuccesses.length / manager.length : 0,
105
+ averageCostUsd: Math.round((totalCost / entries.length) * 100) / 100,
106
+ averagePreWorkBurnPct: Math.round(totalBurnPct / entries.length),
107
+ totalSpendUsd: Math.round(totalCost * 100) / 100,
108
+ totalSavedUsd: Math.round((totalBudget - totalCost) * 100) / 100,
109
+ topFailureClasses,
110
+ averageAttemptsPerRun: Math.round((totalAttempts / entries.length) * 10) / 10,
111
+ lastUpdated: entries[entries.length - 1]?.timestamp ?? new Date().toISOString()
112
+ };
113
+ }
114
+ /**
115
+ * Get the historical direct success rate for use in route classification.
116
+ * Returns undefined if insufficient data (< 5 runs).
117
+ */
118
+ export async function getHistoricalDirectSuccessRate(runsRoot) {
119
+ const entries = await readTraceEntries(runsRoot);
120
+ const directRuns = entries.filter((e) => e.selectedRoute === "direct");
121
+ if (directRuns.length < 5)
122
+ return undefined;
123
+ const successes = directRuns.filter((e) => e.status === "completed" && e.verificationPassed);
124
+ return successes.length / directRuns.length;
125
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Repo Style Analyzer
3
+ *
4
+ * Scans a repository to detect coding conventions, patterns, and style
5
+ * so that governed agent runs produce code matching the existing codebase.
6
+ *
7
+ * Detects:
8
+ * - Naming conventions (camelCase, snake_case, PascalCase, kebab-case)
9
+ * - Test framework (Vitest, Jest, Mocha, node:test)
10
+ * - Package manager (pnpm, npm, yarn, bun)
11
+ * - Module system (ESM, CJS, mixed)
12
+ * - File organization pattern (feature folders, type folders, flat)
13
+ * - TypeScript strictness level
14
+ * - Comment density
15
+ * - Formatting (tabs vs spaces, semicolons, quotes)
16
+ */
17
+ export interface RepoStyleProfile {
18
+ /** Detected package manager */
19
+ packageManager: "pnpm" | "npm" | "yarn" | "bun" | "unknown";
20
+ /** Detected module system */
21
+ moduleSystem: "esm" | "cjs" | "mixed" | "unknown";
22
+ /** Detected test framework */
23
+ testFramework: "vitest" | "jest" | "mocha" | "node-test" | "unknown";
24
+ /** Primary language */
25
+ language: "typescript" | "javascript" | "mixed";
26
+ /** TypeScript strict mode enabled */
27
+ typescriptStrict: boolean;
28
+ /** Dominant naming convention for files */
29
+ fileNaming: "kebab-case" | "camelCase" | "PascalCase" | "snake_case" | "mixed";
30
+ /** Dominant naming convention for variables/functions */
31
+ codeNaming: "camelCase" | "snake_case" | "mixed";
32
+ /** Uses semicolons */
33
+ semicolons: boolean;
34
+ /** Single vs double quotes */
35
+ quotes: "single" | "double" | "mixed";
36
+ /** Indentation style */
37
+ indentation: "tabs" | "2-spaces" | "4-spaces" | "mixed";
38
+ /** Average lines per file (source files) */
39
+ avgLinesPerFile: number;
40
+ /** File organization pattern */
41
+ organization: "feature-folders" | "type-folders" | "flat" | "mixed";
42
+ /** Raw signals for prompt injection */
43
+ promptDirectives: string[];
44
+ }
45
+ /**
46
+ * Analyze a repository and return a style profile.
47
+ */
48
+ export declare function analyzeRepoStyle(rootDir: string): RepoStyleProfile;
49
+ /**
50
+ * Build prompt directives from the style profile.
51
+ * These get injected into the agent's prompt to match repo conventions.
52
+ */
53
+ export declare function buildPromptDirectives(profile: RepoStyleProfile): string[];
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Repo Style Analyzer
3
+ *
4
+ * Scans a repository to detect coding conventions, patterns, and style
5
+ * so that governed agent runs produce code matching the existing codebase.
6
+ *
7
+ * Detects:
8
+ * - Naming conventions (camelCase, snake_case, PascalCase, kebab-case)
9
+ * - Test framework (Vitest, Jest, Mocha, node:test)
10
+ * - Package manager (pnpm, npm, yarn, bun)
11
+ * - Module system (ESM, CJS, mixed)
12
+ * - File organization pattern (feature folders, type folders, flat)
13
+ * - TypeScript strictness level
14
+ * - Comment density
15
+ * - Formatting (tabs vs spaces, semicolons, quotes)
16
+ */
17
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
18
+ import { join, extname } from "node:path";
19
+ /**
20
+ * Analyze a repository and return a style profile.
21
+ */
22
+ export function analyzeRepoStyle(rootDir) {
23
+ const profile = {
24
+ packageManager: detectPackageManager(rootDir),
25
+ moduleSystem: detectModuleSystem(rootDir),
26
+ testFramework: detectTestFramework(rootDir),
27
+ language: detectLanguage(rootDir),
28
+ typescriptStrict: detectTypeScriptStrict(rootDir),
29
+ fileNaming: "kebab-case",
30
+ codeNaming: "camelCase",
31
+ semicolons: true,
32
+ quotes: "double",
33
+ indentation: "2-spaces",
34
+ avgLinesPerFile: 0,
35
+ organization: "mixed",
36
+ promptDirectives: []
37
+ };
38
+ // Sample source files for style detection
39
+ const sourceFiles = collectSourceFiles(rootDir, 50);
40
+ if (sourceFiles.length > 0) {
41
+ const styleSignals = analyzeSourceFiles(sourceFiles);
42
+ profile.fileNaming = styleSignals.fileNaming;
43
+ profile.codeNaming = styleSignals.codeNaming;
44
+ profile.semicolons = styleSignals.semicolons;
45
+ profile.quotes = styleSignals.quotes;
46
+ profile.indentation = styleSignals.indentation;
47
+ profile.avgLinesPerFile = styleSignals.avgLinesPerFile;
48
+ }
49
+ profile.organization = detectOrganization(rootDir);
50
+ profile.promptDirectives = buildPromptDirectives(profile);
51
+ return profile;
52
+ }
53
+ function detectPackageManager(rootDir) {
54
+ if (existsSync(join(rootDir, "pnpm-lock.yaml")))
55
+ return "pnpm";
56
+ if (existsSync(join(rootDir, "bun.lockb")) || existsSync(join(rootDir, "bun.lock")))
57
+ return "bun";
58
+ if (existsSync(join(rootDir, "yarn.lock")))
59
+ return "yarn";
60
+ if (existsSync(join(rootDir, "package-lock.json")))
61
+ return "npm";
62
+ return "unknown";
63
+ }
64
+ function detectModuleSystem(rootDir) {
65
+ const pkgPath = join(rootDir, "package.json");
66
+ if (!existsSync(pkgPath))
67
+ return "unknown";
68
+ try {
69
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
70
+ if (pkg.type === "module")
71
+ return "esm";
72
+ if (pkg.type === "commonjs")
73
+ return "cjs";
74
+ }
75
+ catch { /* ignore */ }
76
+ return "unknown";
77
+ }
78
+ function detectTestFramework(rootDir) {
79
+ const pkgPath = join(rootDir, "package.json");
80
+ if (!existsSync(pkgPath))
81
+ return "unknown";
82
+ try {
83
+ const raw = readFileSync(pkgPath, "utf8");
84
+ if (raw.includes("vitest"))
85
+ return "vitest";
86
+ if (raw.includes("jest"))
87
+ return "jest";
88
+ if (raw.includes("mocha"))
89
+ return "mocha";
90
+ if (raw.includes("node:test"))
91
+ return "node-test";
92
+ }
93
+ catch { /* ignore */ }
94
+ if (existsSync(join(rootDir, "vitest.config.ts")) || existsSync(join(rootDir, "vitest.config.js")))
95
+ return "vitest";
96
+ if (existsSync(join(rootDir, "jest.config.ts")) || existsSync(join(rootDir, "jest.config.js")))
97
+ return "jest";
98
+ return "unknown";
99
+ }
100
+ function detectLanguage(rootDir) {
101
+ if (existsSync(join(rootDir, "tsconfig.json")) ||
102
+ existsSync(join(rootDir, "tsconfig.base.json")) ||
103
+ existsSync(join(rootDir, "tsconfig.build.json"))) {
104
+ return "typescript";
105
+ }
106
+ return "javascript";
107
+ }
108
+ function detectTypeScriptStrict(rootDir) {
109
+ const candidates = ["tsconfig.json", "tsconfig.base.json", "tsconfig.build.json"];
110
+ for (const candidate of candidates) {
111
+ const tsConfigPath = join(rootDir, candidate);
112
+ if (!existsSync(tsConfigPath))
113
+ continue;
114
+ try {
115
+ const raw = readFileSync(tsConfigPath, "utf8");
116
+ if (raw.includes('"strict": true') || raw.includes('"strict":true'))
117
+ return true;
118
+ }
119
+ catch { /* skip */ }
120
+ }
121
+ return false;
122
+ }
123
+ function collectSourceFiles(rootDir, limit) {
124
+ const results = [];
125
+ const extensions = new Set([".ts", ".tsx", ".js", ".jsx"]);
126
+ function walk(dir, depth) {
127
+ if (depth > 5 || results.length >= limit)
128
+ return;
129
+ try {
130
+ const entries = readdirSync(dir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ if (results.length >= limit)
133
+ return;
134
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
135
+ continue;
136
+ const fullPath = join(dir, entry.name);
137
+ if (entry.isDirectory()) {
138
+ walk(fullPath, depth + 1);
139
+ }
140
+ else if (entry.isFile() && extensions.has(extname(entry.name))) {
141
+ results.push(fullPath);
142
+ }
143
+ }
144
+ }
145
+ catch { /* skip */ }
146
+ }
147
+ walk(rootDir, 0);
148
+ return results;
149
+ }
150
+ function analyzeSourceFiles(files) {
151
+ let semiCount = 0;
152
+ let noSemiCount = 0;
153
+ let singleQuoteCount = 0;
154
+ let doubleQuoteCount = 0;
155
+ let tabCount = 0;
156
+ let twoSpaceCount = 0;
157
+ let fourSpaceCount = 0;
158
+ let totalLines = 0;
159
+ for (const filePath of files) {
160
+ try {
161
+ const content = readFileSync(filePath, "utf8");
162
+ const lines = content.split("\n");
163
+ totalLines += lines.length;
164
+ for (const line of lines.slice(0, 100)) {
165
+ if (line.trimEnd().endsWith(";"))
166
+ semiCount++;
167
+ else if (line.trim().length > 0)
168
+ noSemiCount++;
169
+ if (/'\w/.test(line))
170
+ singleQuoteCount++;
171
+ if (/"\w/.test(line))
172
+ doubleQuoteCount++;
173
+ if (line.startsWith("\t"))
174
+ tabCount++;
175
+ else if (line.startsWith(" ") && !line.startsWith(" "))
176
+ twoSpaceCount++;
177
+ else if (line.startsWith(" "))
178
+ fourSpaceCount++;
179
+ }
180
+ }
181
+ catch { /* skip */ }
182
+ }
183
+ const semicolons = semiCount > noSemiCount;
184
+ const quotes = singleQuoteCount > doubleQuoteCount * 2 ? "single" :
185
+ doubleQuoteCount > singleQuoteCount * 2 ? "double" : "mixed";
186
+ const indentation = tabCount > twoSpaceCount + fourSpaceCount ? "tabs" :
187
+ twoSpaceCount > fourSpaceCount * 2 ? "2-spaces" :
188
+ fourSpaceCount > twoSpaceCount * 2 ? "4-spaces" : "mixed";
189
+ return {
190
+ fileNaming: "kebab-case", // Default, hard to detect reliably without more files
191
+ codeNaming: "camelCase", // TypeScript default
192
+ semicolons,
193
+ quotes,
194
+ indentation,
195
+ avgLinesPerFile: files.length > 0 ? Math.round(totalLines / files.length) : 0
196
+ };
197
+ }
198
+ function detectOrganization(rootDir) {
199
+ const srcDir = join(rootDir, "src");
200
+ if (!existsSync(srcDir))
201
+ return "flat";
202
+ try {
203
+ const entries = readdirSync(srcDir, { withFileTypes: true });
204
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
205
+ const hasFeatureDirs = dirs.some((d) => !["utils", "lib", "types", "helpers", "common", "shared", "__tests__", "tests"].includes(d));
206
+ const hasTypeDirs = dirs.some((d) => ["components", "hooks", "services", "models", "controllers", "routes", "middleware"].includes(d));
207
+ if (hasFeatureDirs && !hasTypeDirs)
208
+ return "feature-folders";
209
+ if (hasTypeDirs && !hasFeatureDirs)
210
+ return "type-folders";
211
+ if (hasFeatureDirs && hasTypeDirs)
212
+ return "mixed";
213
+ }
214
+ catch { /* skip */ }
215
+ return "flat";
216
+ }
217
+ /**
218
+ * Build prompt directives from the style profile.
219
+ * These get injected into the agent's prompt to match repo conventions.
220
+ */
221
+ export function buildPromptDirectives(profile) {
222
+ const directives = [];
223
+ if (profile.language === "typescript") {
224
+ directives.push("Write TypeScript, not JavaScript.");
225
+ if (profile.typescriptStrict) {
226
+ directives.push("TypeScript strict mode is enabled. Use explicit types, avoid any.");
227
+ }
228
+ }
229
+ if (profile.moduleSystem === "esm") {
230
+ directives.push("Use ES module imports (import/export), not require().");
231
+ }
232
+ else if (profile.moduleSystem === "cjs") {
233
+ directives.push("Use CommonJS (require/module.exports), not ES module imports.");
234
+ }
235
+ if (profile.testFramework !== "unknown") {
236
+ directives.push(`Use ${profile.testFramework} for tests. Do not introduce a different test framework.`);
237
+ }
238
+ if (profile.semicolons) {
239
+ directives.push("Use semicolons at the end of statements.");
240
+ }
241
+ else {
242
+ directives.push("Do not use semicolons at the end of statements.");
243
+ }
244
+ if (profile.quotes === "single") {
245
+ directives.push("Use single quotes for strings.");
246
+ }
247
+ else if (profile.quotes === "double") {
248
+ directives.push("Use double quotes for strings.");
249
+ }
250
+ if (profile.indentation === "tabs") {
251
+ directives.push("Use tabs for indentation.");
252
+ }
253
+ else if (profile.indentation === "2-spaces") {
254
+ directives.push("Use 2-space indentation.");
255
+ }
256
+ else if (profile.indentation === "4-spaces") {
257
+ directives.push("Use 4-space indentation.");
258
+ }
259
+ if (profile.packageManager !== "unknown") {
260
+ directives.push(`This project uses ${profile.packageManager}. Do not use other package managers.`);
261
+ }
262
+ return directives;
263
+ }
@@ -15,6 +15,14 @@ export interface RouteDecision {
15
15
  blockedSteps: string[];
16
16
  compressed: boolean;
17
17
  compressionSummary?: string;
18
+ /**
19
+ * Recommended model tier based on task complexity and route.
20
+ * Follows cost-aware routing: direct → haiku/sonnet, manager → sonnet,
21
+ * consensus → opus. Never use a more expensive model than the task needs.
22
+ */
23
+ recommendedModelTier: "haiku" | "sonnet" | "opus";
24
+ /** Estimated cost saving vs always using sonnet */
25
+ estimatedSavingVsSonnetUsd: number;
18
26
  }
19
27
  export interface RouteClassificationInput {
20
28
  objective: string;
@@ -29,6 +37,40 @@ export interface RouteClassificationInput {
29
37
  /** Historical success rate for similar tasks with direct execution (0-1). */
30
38
  historicalDirectSuccessRate?: number;
31
39
  }
40
+ /**
41
+ * Resolve a tier to a concrete model ID for a specific engine.
42
+ * Allows downstream adapters to switch models automatically.
43
+ */
44
+ export declare function resolveModelForTier(tier: RouteDecision["recommendedModelTier"], engine: "claude" | "codex" | "gemini" | "openai" | string): string;
45
+ /**
46
+ * Available engine registry — describes what's installed and its capability.
47
+ * Populated by doctor/preflight from PATH and env var detection.
48
+ */
49
+ export interface AvailableEngine {
50
+ id: string;
51
+ available: boolean;
52
+ /** Cost tier: cheap < mid < expensive */
53
+ costTier: "cheap" | "mid" | "expensive";
54
+ /** Capability tier: determines which task types this engine can handle */
55
+ capabilityTier: "haiku" | "sonnet" | "opus";
56
+ }
57
+ /**
58
+ * Select the best available engine for a given tier requirement.
59
+ *
60
+ * When multiple engines are available, picks the cheapest that meets
61
+ * the capability requirement. Falls back gracefully:
62
+ * - If required tier unavailable, upgrades to next tier of same engine
63
+ * - If only one engine family available, uses its best equivalent
64
+ *
65
+ * @param requiredTier - Minimum capability tier needed
66
+ * @param availableEngines - What's installed and authenticated
67
+ * @returns Best engine ID and concrete model to use
68
+ */
69
+ export declare function selectBestEngine(requiredTier: RouteDecision["recommendedModelTier"], availableEngines: AvailableEngine[]): {
70
+ engineId: string;
71
+ model: string;
72
+ reasoning: string;
73
+ };
32
74
  export declare function classifyRoute(input: RouteClassificationInput): RouteDecision;
33
75
  /**
34
76
  * Checks whether a run's Pre Work Burn exceeds the routing policy limits.