@martinloop/mcp 0.5.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -14
- package/dist/resources.js +15 -3
- package/dist/server.js +40 -8
- package/dist/tools/doctor.js +5 -4
- package/dist/tools/get-status.d.ts +2 -0
- package/dist/tools/get-status.js +1 -0
- package/dist/tools/preflight.js +1 -1
- package/dist/tools/run-loop.d.ts +6 -1
- package/dist/tools/run-loop.js +22 -7
- package/dist/tools/tool-errors.js +1 -1
- package/dist/tools/tool-response.js +9 -2
- package/dist/tools/tool-support.d.ts +3 -2
- package/dist/tools/tool-support.js +5 -3
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +59 -34
- package/dist/vendor/adapters/codex-capabilities.d.ts +60 -0
- package/dist/vendor/adapters/codex-capabilities.js +237 -0
- package/dist/vendor/adapters/codex-cli.d.ts +22 -0
- package/dist/vendor/adapters/codex-cli.js +62 -0
- package/dist/vendor/adapters/codex-host.d.ts +104 -0
- package/dist/vendor/adapters/codex-host.js +577 -0
- package/dist/vendor/adapters/codex-launcher.d.ts +7 -135
- package/dist/vendor/adapters/codex-launcher.js +17 -766
- package/dist/vendor/adapters/index.d.ts +4 -3
- package/dist/vendor/adapters/index.js +4 -3
- package/dist/vendor/adapters/openai-compatible.d.ts +2 -3
- package/dist/vendor/adapters/openai-compatible.js +33 -7
- package/dist/vendor/adapters/verifier-only.d.ts +9 -0
- package/dist/vendor/adapters/verifier-only.js +68 -0
- package/dist/vendor/contracts/index.d.ts +16 -6
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +6 -0
- package/dist/vendor/contracts/verified-handoff.js +5 -0
- package/dist/vendor/core/grounding.d.ts +2 -1
- package/dist/vendor/core/grounding.js +40 -5
- package/dist/vendor/core/index.d.ts +4 -6
- package/dist/vendor/core/index.js +25 -19
- package/dist/vendor/core/persistence/runs-reader.d.ts +2 -0
- package/dist/vendor/core/policy.js +6 -6
- package/dist/vendor/core/red-blue/red-phase.d.ts +2 -1
- package/dist/vendor/core/red-blue/red-phase.js +3 -3
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +2 -4
- package/dist/vendor/core/red-blue/risk-tiers.js +1 -3
- package/dist/vendor/core/routing.d.ts +0 -42
- package/dist/vendor/core/routing.js +4 -154
- package/dist/vendor/core/verified-handoff.d.ts +6 -1
- package/dist/vendor/core/verified-handoff.js +28 -0
- package/package.json +6 -2
- package/server.json +2 -2
- package/dist/vendor/adapters/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/stub-direct-provider.js +0 -12
|
@@ -20,6 +20,7 @@ export interface MockModelClient {
|
|
|
20
20
|
findings: RedFinding[];
|
|
21
21
|
tokensUsed: number;
|
|
22
22
|
costUsd: number;
|
|
23
|
+
model?: string;
|
|
23
24
|
}>;
|
|
24
25
|
}
|
|
25
26
|
export interface RunRedPhaseOptions {
|
|
@@ -47,7 +48,7 @@ export interface PatchInput {
|
|
|
47
48
|
*
|
|
48
49
|
* - baseline: programmatic probes only, no model call
|
|
49
50
|
* - high_risk: paranoid programmatic scan, no model call
|
|
50
|
-
* - release_critical: paranoid scan + one
|
|
51
|
+
* - release_critical: paranoid scan + one configured model-client call
|
|
51
52
|
*/
|
|
52
53
|
export declare function runRedPhase(patch: PatchInput, tier: RiskTier, blueBudgetUsd: number, options?: RunRedPhaseOptions): Promise<RedFindings>;
|
|
53
54
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PROBE_COUNTS,
|
|
1
|
+
import { PROBE_COUNTS, resolveRedBudgetPolicy } from "./risk-tiers.js";
|
|
2
2
|
// ─── Programmatic probes ──────────────────────────────────────────────────────
|
|
3
3
|
function runProgrammaticProbes(patch, paranoid) {
|
|
4
4
|
const findings = [];
|
|
@@ -62,7 +62,7 @@ function runProgrammaticProbes(patch, paranoid) {
|
|
|
62
62
|
*
|
|
63
63
|
* - baseline: programmatic probes only, no model call
|
|
64
64
|
* - high_risk: paranoid programmatic scan, no model call
|
|
65
|
-
* - release_critical: paranoid scan + one
|
|
65
|
+
* - release_critical: paranoid scan + one configured model-client call
|
|
66
66
|
*/
|
|
67
67
|
export async function runRedPhase(patch, tier, blueBudgetUsd, options = {}) {
|
|
68
68
|
const policy = resolveRedBudgetPolicy(tier, blueBudgetUsd);
|
|
@@ -77,7 +77,7 @@ export async function runRedPhase(patch, tier, blueBudgetUsd, options = {}) {
|
|
|
77
77
|
const result = await options.modelClient.complete(prompt);
|
|
78
78
|
findings = [...findings, ...result.findings];
|
|
79
79
|
modelCallMade = true;
|
|
80
|
-
modelUsed =
|
|
80
|
+
modelUsed = result.model;
|
|
81
81
|
budgetUsedUsd += result.costUsd;
|
|
82
82
|
}
|
|
83
83
|
const result = {
|
|
@@ -4,7 +4,7 @@ export interface RedBudgetPolicy {
|
|
|
4
4
|
blueBudgetUsd: number;
|
|
5
5
|
/** Cap on Red phase spend: 30% / 100% / 150% of Blue */
|
|
6
6
|
redBudgetCapUsd: number;
|
|
7
|
-
/** Only release_critical permits
|
|
7
|
+
/** Only release_critical permits an optional configured model-client call. */
|
|
8
8
|
modelCallAllowed: boolean;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
@@ -15,8 +15,6 @@ export declare function resolveRedBudgetPolicy(tier: RiskTier, blueBudgetUsd: nu
|
|
|
15
15
|
* Probe counts per tier.
|
|
16
16
|
* baseline = standard 6-probe sweep
|
|
17
17
|
* high_risk = paranoid 12-probe sweep
|
|
18
|
-
* release_critical = paranoid 12-probe sweep + model
|
|
18
|
+
* release_critical = paranoid 12-probe sweep + configured model client
|
|
19
19
|
*/
|
|
20
20
|
export declare const PROBE_COUNTS: Record<RiskTier, number>;
|
|
21
|
-
/** The only model ever permitted in the Red phase. */
|
|
22
|
-
export declare const RED_PHASE_MODEL: "claude-haiku-4-5-20251001";
|
|
@@ -21,12 +21,10 @@ export function resolveRedBudgetPolicy(tier, blueBudgetUsd) {
|
|
|
21
21
|
* Probe counts per tier.
|
|
22
22
|
* baseline = standard 6-probe sweep
|
|
23
23
|
* high_risk = paranoid 12-probe sweep
|
|
24
|
-
* release_critical = paranoid 12-probe sweep + model
|
|
24
|
+
* release_critical = paranoid 12-probe sweep + configured model client
|
|
25
25
|
*/
|
|
26
26
|
export const PROBE_COUNTS = {
|
|
27
27
|
baseline: 6,
|
|
28
28
|
high_risk: 12,
|
|
29
29
|
release_critical: 12
|
|
30
30
|
};
|
|
31
|
-
/** The only model ever permitted in the Red phase. */
|
|
32
|
-
export const RED_PHASE_MODEL = "claude-haiku-4-5-20251001";
|
|
@@ -15,14 +15,6 @@ 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;
|
|
26
18
|
}
|
|
27
19
|
export interface RouteClassificationInput {
|
|
28
20
|
objective: string;
|
|
@@ -37,40 +29,6 @@ export interface RouteClassificationInput {
|
|
|
37
29
|
/** Historical success rate for similar tasks with direct execution (0-1). */
|
|
38
30
|
historicalDirectSuccessRate?: number;
|
|
39
31
|
}
|
|
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
|
-
};
|
|
74
32
|
export declare function classifyRoute(input: RouteClassificationInput): RouteDecision;
|
|
75
33
|
/**
|
|
76
34
|
* Checks whether a run's Pre Work Burn exceeds the routing policy limits.
|
|
@@ -84,144 +84,6 @@ function calculateDirectConfidence(signals, input) {
|
|
|
84
84
|
// ---------------------------------------------------------------------------
|
|
85
85
|
// Public API
|
|
86
86
|
// ---------------------------------------------------------------------------
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
// Cost-aware model tier routing
|
|
89
|
-
//
|
|
90
|
-
// Engine-agnostic tier system — each engine maps tiers to real model IDs.
|
|
91
|
-
// Never pay for reasoning/orchestration on simple tasks.
|
|
92
|
-
//
|
|
93
|
-
// Tier → concrete model per engine:
|
|
94
|
-
// haiku → claude-haiku-4-5, gpt-4o-mini, gemini-2.5-flash, deepseek-chat, qwen3-8b, llama-3.1-8b
|
|
95
|
-
// sonnet → claude-sonnet-4-6, gpt-4o, gemini-2.5-pro, deepseek-r1, qwen3-32b, llama-3.3-70b
|
|
96
|
-
// opus → claude-opus-4-6, o3, gemini-2.5-ultra, deepseek-r1-671b, qwen3-235b, llama-3.1-405b
|
|
97
|
-
//
|
|
98
|
-
// Pricing reference (USD per 1M tokens, approx 2026):
|
|
99
|
-
// haiku: $0.80 input / $4.00 output (~1x baseline)
|
|
100
|
-
// sonnet: $3.00 input / $15.00 output (~4x haiku)
|
|
101
|
-
// opus: $15.00 input / $75.00 output (~19x haiku)
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
const SONNET_AVG_COST_PER_USD_BUDGET = 0.35; // sonnet uses ~35% of budget on average
|
|
104
|
-
const HAIKU_AVG_COST_PER_USD_BUDGET = 0.10; // haiku uses ~10% — ~3.5x cheaper
|
|
105
|
-
function selectModelTier(mode) {
|
|
106
|
-
// Route already encodes complexity. Direct = passed the simplicity check → haiku.
|
|
107
|
-
if (mode === "direct")
|
|
108
|
-
return "haiku";
|
|
109
|
-
if (mode === "manager")
|
|
110
|
-
return "sonnet";
|
|
111
|
-
return "opus";
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Resolve a tier to a concrete model ID for a specific engine.
|
|
115
|
-
* Allows downstream adapters to switch models automatically.
|
|
116
|
-
*/
|
|
117
|
-
export function resolveModelForTier(tier, engine) {
|
|
118
|
-
const matrix = {
|
|
119
|
-
claude: {
|
|
120
|
-
haiku: "claude-haiku-4-5-20251001",
|
|
121
|
-
sonnet: "claude-sonnet-4-6",
|
|
122
|
-
opus: "claude-opus-4-6"
|
|
123
|
-
},
|
|
124
|
-
codex: {
|
|
125
|
-
haiku: "gpt-4o-mini",
|
|
126
|
-
sonnet: "gpt-4.1",
|
|
127
|
-
opus: "o3"
|
|
128
|
-
},
|
|
129
|
-
gemini: {
|
|
130
|
-
haiku: "gemini-2.5-flash",
|
|
131
|
-
sonnet: "gemini-2.5-pro",
|
|
132
|
-
opus: "gemini-2.5-ultra"
|
|
133
|
-
},
|
|
134
|
-
openai: {
|
|
135
|
-
haiku: "gpt-4o-mini",
|
|
136
|
-
sonnet: "gpt-4o",
|
|
137
|
-
opus: "o3"
|
|
138
|
-
},
|
|
139
|
-
deepseek: {
|
|
140
|
-
haiku: "deepseek-chat",
|
|
141
|
-
sonnet: "deepseek-r1",
|
|
142
|
-
opus: "deepseek-r1"
|
|
143
|
-
},
|
|
144
|
-
qwen: {
|
|
145
|
-
haiku: "qwen/qwen3-8b",
|
|
146
|
-
sonnet: "qwen/qwen3-32b",
|
|
147
|
-
opus: "qwen/qwen3-235b-a22b"
|
|
148
|
-
},
|
|
149
|
-
llama: {
|
|
150
|
-
haiku: "meta-llama/llama-3.1-8b-instruct",
|
|
151
|
-
sonnet: "meta-llama/llama-3.3-70b-instruct",
|
|
152
|
-
opus: "meta-llama/llama-3.1-405b-instruct"
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
const engineKey = engine.toLowerCase().split("-")[0] ?? engine;
|
|
156
|
-
const claudeMatrix = matrix.claude;
|
|
157
|
-
return matrix[engineKey]?.[tier] ?? claudeMatrix?.[tier] ?? "claude-sonnet-4-6";
|
|
158
|
-
}
|
|
159
|
-
function estimateSavingVsSonnet(tier, budgetUsd) {
|
|
160
|
-
if (tier === "sonnet")
|
|
161
|
-
return 0;
|
|
162
|
-
if (tier === "haiku")
|
|
163
|
-
return Math.round((SONNET_AVG_COST_PER_USD_BUDGET - HAIKU_AVG_COST_PER_USD_BUDGET) * budgetUsd * 100) / 100;
|
|
164
|
-
// opus costs more than sonnet — negative saving
|
|
165
|
-
return Math.round(-1 * SONNET_AVG_COST_PER_USD_BUDGET * budgetUsd * 0.5 * 100) / 100;
|
|
166
|
-
}
|
|
167
|
-
/**
|
|
168
|
-
* Select the best available engine for a given tier requirement.
|
|
169
|
-
*
|
|
170
|
-
* When multiple engines are available, picks the cheapest that meets
|
|
171
|
-
* the capability requirement. Falls back gracefully:
|
|
172
|
-
* - If required tier unavailable, upgrades to next tier of same engine
|
|
173
|
-
* - If only one engine family available, uses its best equivalent
|
|
174
|
-
*
|
|
175
|
-
* @param requiredTier - Minimum capability tier needed
|
|
176
|
-
* @param availableEngines - What's installed and authenticated
|
|
177
|
-
* @returns Best engine ID and concrete model to use
|
|
178
|
-
*/
|
|
179
|
-
export function selectBestEngine(requiredTier, availableEngines) {
|
|
180
|
-
const available = availableEngines.filter((e) => e.available);
|
|
181
|
-
if (available.length === 0) {
|
|
182
|
-
// Nothing detected — default to claude (user must install)
|
|
183
|
-
return {
|
|
184
|
-
engineId: "claude",
|
|
185
|
-
model: resolveModelForTier(requiredTier, "claude"),
|
|
186
|
-
reasoning: "No engines detected. Defaulting to claude. Run martin doctor for setup instructions."
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
const tierRank = {
|
|
190
|
-
haiku: 0, sonnet: 1, opus: 2
|
|
191
|
-
};
|
|
192
|
-
const requiredRank = tierRank[requiredTier];
|
|
193
|
-
// Find the cheapest engine that meets or exceeds the required tier
|
|
194
|
-
const eligible = available
|
|
195
|
-
.filter((e) => tierRank[e.capabilityTier] >= requiredRank)
|
|
196
|
-
.sort((a, b) => {
|
|
197
|
-
// Sort by cost (cheap first), then by exact tier match
|
|
198
|
-
const costOrder = { cheap: 0, mid: 1, expensive: 2 };
|
|
199
|
-
if (costOrder[a.costTier] !== costOrder[b.costTier]) {
|
|
200
|
-
return costOrder[a.costTier] - costOrder[b.costTier];
|
|
201
|
-
}
|
|
202
|
-
// Prefer exact tier match over overqualified
|
|
203
|
-
const aExact = a.capabilityTier === requiredTier ? 0 : 1;
|
|
204
|
-
const bExact = b.capabilityTier === requiredTier ? 0 : 1;
|
|
205
|
-
return aExact - bExact;
|
|
206
|
-
});
|
|
207
|
-
if (eligible.length > 0) {
|
|
208
|
-
const best = eligible[0];
|
|
209
|
-
const model = resolveModelForTier(requiredTier, best.id);
|
|
210
|
-
return {
|
|
211
|
-
engineId: best.id,
|
|
212
|
-
model,
|
|
213
|
-
reasoning: `Selected ${best.id} (${model}) as cheapest available engine for ${requiredTier}-tier task.`
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
// All available engines are below required tier — upgrade to best available
|
|
217
|
-
const best = available.sort((a, b) => tierRank[b.capabilityTier] - tierRank[a.capabilityTier])[0];
|
|
218
|
-
const model = resolveModelForTier(best.capabilityTier, best.id);
|
|
219
|
-
return {
|
|
220
|
-
engineId: best.id,
|
|
221
|
-
model,
|
|
222
|
-
reasoning: `No ${requiredTier}-tier engine available. Upgrading to ${best.id} (${model}) — best available.`
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
87
|
export function classifyRoute(input) {
|
|
226
88
|
const policy = { ...DEFAULT_ROUTING_POLICY, ...input.policy };
|
|
227
89
|
const signals = extractComplexitySignals(input);
|
|
@@ -230,7 +92,6 @@ export function classifyRoute(input) {
|
|
|
230
92
|
const blockedSteps = [];
|
|
231
93
|
// Policy override: forced mode
|
|
232
94
|
if (policy.mode === "direct") {
|
|
233
|
-
const tier = selectModelTier("direct");
|
|
234
95
|
return {
|
|
235
96
|
selectedMode: "direct",
|
|
236
97
|
confidence: 1,
|
|
@@ -239,13 +100,10 @@ export function classifyRoute(input) {
|
|
|
239
100
|
reason: ["Policy forces direct execution mode."],
|
|
240
101
|
blockedSteps: ["manager", "planner", "router", "consensus"],
|
|
241
102
|
compressed: true,
|
|
242
|
-
compressionSummary: "Policy: direct mode — skipping all orchestration."
|
|
243
|
-
recommendedModelTier: tier,
|
|
244
|
-
estimatedSavingVsSonnetUsd: estimateSavingVsSonnet(tier, input.budgetUsd)
|
|
103
|
+
compressionSummary: "Policy: direct mode — skipping all orchestration."
|
|
245
104
|
};
|
|
246
105
|
}
|
|
247
106
|
if (policy.mode === "consensus") {
|
|
248
|
-
const tier = selectModelTier("consensus");
|
|
249
107
|
return {
|
|
250
108
|
selectedMode: "consensus",
|
|
251
109
|
confidence: 0.5,
|
|
@@ -253,9 +111,7 @@ export function classifyRoute(input) {
|
|
|
253
111
|
expectedPreworkBurnPct: 35,
|
|
254
112
|
reason: ["Policy forces consensus mode."],
|
|
255
113
|
blockedSteps: [],
|
|
256
|
-
compressed: false
|
|
257
|
-
recommendedModelTier: tier,
|
|
258
|
-
estimatedSavingVsSonnetUsd: estimateSavingVsSonnet(tier, input.budgetUsd)
|
|
114
|
+
compressed: false
|
|
259
115
|
};
|
|
260
116
|
}
|
|
261
117
|
// Adaptive routing
|
|
@@ -268,7 +124,6 @@ export function classifyRoute(input) {
|
|
|
268
124
|
if (signals.scopeRestricted)
|
|
269
125
|
reason.push("Path scope is restricted.");
|
|
270
126
|
blockedSteps.push("manager", "consensus");
|
|
271
|
-
const tier = selectModelTier("direct");
|
|
272
127
|
return {
|
|
273
128
|
selectedMode: "direct",
|
|
274
129
|
confidence: directConfidence,
|
|
@@ -277,9 +132,7 @@ export function classifyRoute(input) {
|
|
|
277
132
|
reason,
|
|
278
133
|
blockedSteps,
|
|
279
134
|
compressed: true,
|
|
280
|
-
compressionSummary: `Compressed from manager→planner→worker to direct worker. Estimated savings: ${String(Math.round(input.budgetUsd * 0.3 * 100) / 100)} USD
|
|
281
|
-
recommendedModelTier: tier,
|
|
282
|
-
estimatedSavingVsSonnetUsd: estimateSavingVsSonnet(tier, input.budgetUsd)
|
|
135
|
+
compressionSummary: `Compressed from manager→planner→worker to direct worker. Estimated savings: ${String(Math.round(input.budgetUsd * 0.3 * 100) / 100)} USD.`
|
|
283
136
|
};
|
|
284
137
|
}
|
|
285
138
|
// Complex task — needs orchestration
|
|
@@ -293,7 +146,6 @@ export function classifyRoute(input) {
|
|
|
293
146
|
reason.push(`Touches ${String(signals.fileCount)}+ files.`);
|
|
294
147
|
const needsConsensus = signals.hasSecurityKeywords && signals.hasMigrationKeywords;
|
|
295
148
|
const mode = needsConsensus ? "consensus" : "manager";
|
|
296
|
-
const tier = selectModelTier(mode);
|
|
297
149
|
return {
|
|
298
150
|
selectedMode: mode,
|
|
299
151
|
confidence: directConfidence,
|
|
@@ -301,9 +153,7 @@ export function classifyRoute(input) {
|
|
|
301
153
|
expectedPreworkBurnPct: needsConsensus ? 40 : 25,
|
|
302
154
|
reason,
|
|
303
155
|
blockedSteps: needsConsensus ? [] : ["consensus"],
|
|
304
|
-
compressed: false
|
|
305
|
-
recommendedModelTier: tier,
|
|
306
|
-
estimatedSavingVsSonnetUsd: estimateSavingVsSonnet(tier, input.budgetUsd)
|
|
156
|
+
compressed: false
|
|
307
157
|
};
|
|
308
158
|
}
|
|
309
159
|
function estimateDirectCost(budgetUsd) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoopRecord, ReceiptIntegritySummary, TestIntegrityStatus, TestIntegrityVerdict, VerifiedHandoffOutcome, VerifiedHandoffRecoveryV1, VerifiedHandoffRequirementV1, VerifiedHandoffScopeV1, VerifiedHandoffTestIntegrityV1, VerifiedHandoffV1 } from "../contracts/index.js";
|
|
1
|
+
import type { ExecutionMode, LoopRecord, ReceiptIntegritySummary, TestIntegrityStatus, TestIntegrityVerdict, VerifiedHandoffOutcome, VerifiedHandoffRecoveryV1, VerifiedHandoffRequirementV1, VerifiedHandoffScopeV1, VerifiedHandoffTestIntegrityV1, VerifiedHandoffV1 } from "../contracts/index.js";
|
|
2
2
|
export declare function toTestIntegrityVerdict(status: TestIntegrityStatus): TestIntegrityVerdict;
|
|
3
3
|
export interface VerifierExecutionBinding {
|
|
4
4
|
runId: string;
|
|
@@ -30,8 +30,13 @@ export declare function resolveVerifiedHandoffOutcome(input: {
|
|
|
30
30
|
changedFileCount?: number;
|
|
31
31
|
definitionOfDonePreSatisfied?: boolean;
|
|
32
32
|
evidenceContradicted?: boolean;
|
|
33
|
+
governanceClaimEligible?: boolean;
|
|
33
34
|
unresolvedWorkCount: number;
|
|
34
35
|
}): VerifiedHandoffOutcome;
|
|
36
|
+
export declare function deriveVerifiedHandoffExecutionBoundary(loop: LoopRecord): {
|
|
37
|
+
executionMode: ExecutionMode;
|
|
38
|
+
governanceClaimEligible: boolean;
|
|
39
|
+
};
|
|
35
40
|
export interface BuildVerifiedHandoffInput {
|
|
36
41
|
loop: LoopRecord;
|
|
37
42
|
generatedAt?: string;
|
|
@@ -57,6 +57,7 @@ export function resolveVerifiedHandoffOutcome(input) {
|
|
|
57
57
|
return "STOPPED";
|
|
58
58
|
}
|
|
59
59
|
if (input.executionStatus === "failed" ||
|
|
60
|
+
input.governanceClaimEligible === false ||
|
|
60
61
|
input.evidenceContradicted === true ||
|
|
61
62
|
(input.mutationRequired === true &&
|
|
62
63
|
(input.changedFileCount ?? 0) === 0 &&
|
|
@@ -80,6 +81,29 @@ export function resolveVerifiedHandoffOutcome(input) {
|
|
|
80
81
|
}
|
|
81
82
|
return "NEEDS_REVIEW";
|
|
82
83
|
}
|
|
84
|
+
const SIMULATED_ADAPTER_PATTERN = /(?:^|:)(?:stub|fixture|synthetic)(?::|$)/u;
|
|
85
|
+
function isExecutionMode(value) {
|
|
86
|
+
return value === "governed" || value === "verification_only" || value === "simulated";
|
|
87
|
+
}
|
|
88
|
+
export function deriveVerifiedHandoffExecutionBoundary(loop) {
|
|
89
|
+
const adapterIds = loop.attempts.map((attempt) => attempt.adapterId);
|
|
90
|
+
const hasSimulatedAdapter = adapterIds.some((adapterId) => adapterId === "direct:proof:no-mutation" ||
|
|
91
|
+
SIMULATED_ADAPTER_PATTERN.test(adapterId));
|
|
92
|
+
const hasVerifierOnlyAdapter = adapterIds.some((adapterId) => adapterId === "direct:verifier:verify-only");
|
|
93
|
+
const persistedMode = isExecutionMode(loop.metadata["executionMode"])
|
|
94
|
+
? loop.metadata["executionMode"]
|
|
95
|
+
: undefined;
|
|
96
|
+
const groundingEvidenceAvailable = loop.metadata["groundingEvidenceStatus"] !== "unavailable";
|
|
97
|
+
const executionMode = hasSimulatedAdapter
|
|
98
|
+
? "simulated"
|
|
99
|
+
: hasVerifierOnlyAdapter
|
|
100
|
+
? "verification_only"
|
|
101
|
+
: persistedMode ?? (adapterIds.length > 0 ? "governed" : "simulated");
|
|
102
|
+
return {
|
|
103
|
+
executionMode,
|
|
104
|
+
governanceClaimEligible: executionMode === "governed" && adapterIds.length > 0 && groundingEvidenceAvailable,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
83
107
|
function toEvidenceStatus(status) {
|
|
84
108
|
switch (status) {
|
|
85
109
|
case "passed":
|
|
@@ -132,6 +156,7 @@ export function buildVerifiedHandoff(input) {
|
|
|
132
156
|
summary: input.testIntegrity?.summary ??
|
|
133
157
|
"Test integrity was not evaluated for this run.",
|
|
134
158
|
};
|
|
159
|
+
const executionBoundary = deriveVerifiedHandoffExecutionBoundary(input.loop);
|
|
135
160
|
const outcome = resolveVerifiedHandoffOutcome({
|
|
136
161
|
lifecycleState: input.loop.lifecycleState,
|
|
137
162
|
executionStatus: input.executionStatus,
|
|
@@ -143,6 +168,7 @@ export function buildVerifiedHandoff(input) {
|
|
|
143
168
|
changedFileCount: scope.changedFiles.length,
|
|
144
169
|
definitionOfDonePreSatisfied: input.definitionOfDonePreSatisfied,
|
|
145
170
|
evidenceContradicted: input.evidenceContradicted ?? input.verification.status === "contradicted",
|
|
171
|
+
governanceClaimEligible: executionBoundary.governanceClaimEligible,
|
|
146
172
|
unresolvedWorkCount: unresolvedWork.length,
|
|
147
173
|
});
|
|
148
174
|
const handoffId = `vh_${createHash("sha256")
|
|
@@ -163,6 +189,8 @@ export function buildVerifiedHandoff(input) {
|
|
|
163
189
|
verificationPlan: input.loop.task.verificationPlan,
|
|
164
190
|
},
|
|
165
191
|
outcome,
|
|
192
|
+
executionMode: executionBoundary.executionMode,
|
|
193
|
+
governanceClaimEligible: executionBoundary.governanceClaimEligible,
|
|
166
194
|
sourceStatus: {
|
|
167
195
|
status: input.loop.status,
|
|
168
196
|
lifecycleState: input.loop.lifecycleState,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -59,7 +59,10 @@
|
|
|
59
59
|
"test": "vitest run --maxWorkers=1",
|
|
60
60
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
61
61
|
"start": "node dist/server.js",
|
|
62
|
-
"inspect:live": "node ./scripts/inspect-live.mjs"
|
|
62
|
+
"inspect:live": "node ./scripts/inspect-live.mjs",
|
|
63
|
+
"mcpb:build": "node ./mcpb/build-mcpb.mjs",
|
|
64
|
+
"mcpb:validate": "mcpb validate ./dist-mcpb/martinloop",
|
|
65
|
+
"mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
|
|
63
66
|
},
|
|
64
67
|
"dependencies": {
|
|
65
68
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -71,6 +74,7 @@
|
|
|
71
74
|
"ts-morph": "^21.0.0"
|
|
72
75
|
},
|
|
73
76
|
"devDependencies": {
|
|
77
|
+
"@anthropic-ai/mcpb": "2.1.2",
|
|
74
78
|
"@martin/contracts": "workspace:*"
|
|
75
79
|
}
|
|
76
80
|
}
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"url": "https://github.com/Keesan12/martin-loop",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.5.
|
|
10
|
+
"version": "0.5.3",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "@martinloop/mcp",
|
|
15
|
-
"version": "0.5.
|
|
15
|
+
"version": "0.5.3",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
|
|
2
|
-
export interface StubDirectProviderAdapterOptions {
|
|
3
|
-
providerId: string;
|
|
4
|
-
model: string;
|
|
5
|
-
label?: string;
|
|
6
|
-
responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
|
|
7
|
-
}
|
|
8
|
-
export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
-
export function createStubDirectProviderAdapter(options) {
|
|
3
|
-
return createDirectProviderAdapter({
|
|
4
|
-
providerId: options.providerId,
|
|
5
|
-
model: options.model,
|
|
6
|
-
label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
|
|
7
|
-
capabilities: {
|
|
8
|
-
workspaceMutations: false
|
|
9
|
-
},
|
|
10
|
-
responder: options.responder
|
|
11
|
-
});
|
|
12
|
-
}
|