@martinloop/mcp 0.1.3 → 0.2.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.
package/dist/server.js CHANGED
@@ -2,10 +2,12 @@
2
2
  /**
3
3
  * Martin Loop MCP Server
4
4
  *
5
- * Exposes three tools over the Model Context Protocol (stdio transport):
6
- * martin_run execute a full Martin loop on a coding task
7
- * martin_inspectsummarise a saved loop record file
8
- * martin_status return cost and pressure state from a loop record
5
+ * Exposes a governed local MCP cockpit over stdio:
6
+ * martin_doctor inspect local readiness and run-store health
7
+ * martin_preflightnormalize a proposed run contract before execution
8
+ * martin_run execute a full Martin loop on a coding task
9
+ * martin_inspect — summarise a saved loop record file
10
+ * martin_status — return cost and pressure state from a loop record
9
11
  *
10
12
  * Setup (Claude Code):
11
13
  * macOS/Linux: claude mcp add --scope user martin-loop -- npx @martinloop/mcp
@@ -20,19 +22,117 @@
20
22
  import { createRequire } from "node:module";
21
23
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
24
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
25
+ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
26
+ import { martinDoctorTool } from "./tools/doctor.js";
27
+ import { getAttemptTool } from "./tools/get-attempt.js";
28
+ import { getRunTool } from "./tools/get-run.js";
24
29
  import { getStatusTool } from "./tools/get-status.js";
30
+ import { getVerificationResultsTool } from "./tools/get-verification-results.js";
25
31
  import { inspectLoopTool } from "./tools/inspect-loop.js";
32
+ import { listRunsTool } from "./tools/list-runs.js";
33
+ import { martinPreflightTool } from "./tools/preflight.js";
34
+ import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
35
+ import { listMartinResourceTemplates, listMartinResources, readMartinResource } from "./resources.js";
26
36
  import { runLoopTool } from "./tools/run-loop.js";
37
+ import { runDossierTool } from "./tools/run-dossier.js";
27
38
  import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
28
39
  const require = createRequire(import.meta.url);
29
40
  const packageJson = require("../package.json");
30
- const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {} } });
41
+ const server = new Server({ name: "martin-loop", version: packageJson.version }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
31
42
  // ---------------------------------------------------------------------------
32
43
  // Tool manifest
33
44
  // ---------------------------------------------------------------------------
34
45
  server.setRequestHandler(ListToolsRequestSchema, () => ({
35
46
  tools: [
47
+ {
48
+ name: "martin_doctor",
49
+ description: "Inspect Martin MCP readiness without changing code. Reports workspace roots, run-store visibility, execution mode, and whether claude or codex is available on PATH.",
50
+ inputSchema: {
51
+ type: "object",
52
+ additionalProperties: false,
53
+ properties: {
54
+ workingDirectory: {
55
+ type: "string",
56
+ description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
57
+ },
58
+ runsDir: {
59
+ type: "string",
60
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
61
+ },
62
+ engine: {
63
+ type: "string",
64
+ enum: ["claude", "codex"],
65
+ description: "Optional engine to emphasize in the readiness report."
66
+ }
67
+ }
68
+ }
69
+ },
70
+ {
71
+ name: "martin_preflight",
72
+ description: "Validate and normalize a proposed martin_run contract before execution. Reports the effective budget, path scope, engine readiness, and expected run-store layout.",
73
+ inputSchema: {
74
+ type: "object",
75
+ additionalProperties: false,
76
+ properties: {
77
+ objective: {
78
+ type: "string",
79
+ description: "The coding task to complete. Be specific about what needs to change."
80
+ },
81
+ workingDirectory: {
82
+ type: "string",
83
+ description: "Optional repo-root override resolved under the MCP workspace root (or current working directory). Must stay within that safe root."
84
+ },
85
+ engine: {
86
+ type: "string",
87
+ enum: ["claude", "codex"],
88
+ description: "Which agent CLI to use. Defaults to 'claude'."
89
+ },
90
+ model: {
91
+ type: "string",
92
+ description: "Model override passed to the CLI (e.g. 'claude-opus-4-6', 'o3')."
93
+ },
94
+ maxUsd: {
95
+ type: "number",
96
+ exclusiveMinimum: 0,
97
+ description: "Hard budget ceiling in USD. Defaults to 25."
98
+ },
99
+ maxIterations: {
100
+ type: "integer",
101
+ exclusiveMinimum: 0,
102
+ description: "Maximum number of loop attempts. Defaults to 8."
103
+ },
104
+ maxTokens: {
105
+ type: "integer",
106
+ exclusiveMinimum: 0,
107
+ description: "Maximum total tokens across all attempts. Defaults to 80000."
108
+ },
109
+ verificationPlan: {
110
+ type: "array",
111
+ items: { type: "string" },
112
+ description: "Shell commands that must all exit 0 for the task to be considered complete (e.g. ['pnpm test', 'pnpm build'])."
113
+ },
114
+ allowedPaths: {
115
+ type: "array",
116
+ items: { type: "string" },
117
+ description: "Repo-relative path globs Martin may modify, such as ['src/**', 'tests/**']. Absolute paths and '..' traversal are rejected."
118
+ },
119
+ deniedPaths: {
120
+ type: "array",
121
+ items: { type: "string" },
122
+ description: "Repo-relative path globs Martin must never modify, such as ['.env', 'docs/security/**']. Absolute paths and '..' traversal are rejected."
123
+ },
124
+ workspaceId: {
125
+ type: "string",
126
+ description: "Workspace identifier for telemetry. Defaults to 'ws_mcp'."
127
+ },
128
+ projectId: {
129
+ type: "string",
130
+ description: "Project identifier for telemetry. Defaults to 'proj_mcp'."
131
+ }
132
+ },
133
+ required: ["objective"]
134
+ }
135
+ },
36
136
  {
37
137
  name: "martin_run",
38
138
  description: "Execute a full Martin Loop on a coding task. Martin spawns the selected agent CLI (claude or codex), runs the task, classifies failures, and retries within the specified budget. Returns the loop outcome including lifecycle state, attempt count, and spend.",
@@ -152,15 +252,155 @@ server.setRequestHandler(ListToolsRequestSchema, () => ({
152
252
  { required: ["latest"] }
153
253
  ]
154
254
  }
255
+ },
256
+ {
257
+ name: "martin_list_runs",
258
+ description: "List recent Martin Loop runs from the local run store with budget, verifier, and lifecycle summaries. Read-only.",
259
+ inputSchema: {
260
+ type: "object",
261
+ additionalProperties: false,
262
+ properties: {
263
+ runsDir: {
264
+ type: "string",
265
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
266
+ },
267
+ limit: {
268
+ type: "integer",
269
+ exclusiveMinimum: 0,
270
+ description: "Maximum number of runs to return. Defaults to 20."
271
+ }
272
+ }
273
+ }
274
+ },
275
+ {
276
+ name: "martin_get_run",
277
+ description: "Load a read-only run dossier by loopId or latest run selector, including task, budget, cost, and attempts.",
278
+ inputSchema: {
279
+ type: "object",
280
+ additionalProperties: false,
281
+ properties: {
282
+ loopId: {
283
+ type: "string",
284
+ description: "Martin loop ID under the run store."
285
+ },
286
+ runsDir: {
287
+ type: "string",
288
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
289
+ },
290
+ latest: {
291
+ const: true,
292
+ description: "When true, loads the most recently updated loop record in the runs directory."
293
+ }
294
+ },
295
+ oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
296
+ }
297
+ },
298
+ {
299
+ name: "martin_get_attempt",
300
+ description: "Load read-only attempt evidence for a single Martin Loop attempt by loopId and attempt index.",
301
+ inputSchema: {
302
+ type: "object",
303
+ additionalProperties: false,
304
+ properties: {
305
+ loopId: {
306
+ type: "string",
307
+ description: "Martin loop ID under the run store."
308
+ },
309
+ attemptIndex: {
310
+ type: "integer",
311
+ exclusiveMinimum: 0,
312
+ description: "1-based attempt index to inspect."
313
+ },
314
+ runsDir: {
315
+ type: "string",
316
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
317
+ }
318
+ },
319
+ required: ["loopId", "attemptIndex"]
320
+ }
321
+ },
322
+ {
323
+ name: "martin_get_verification_results",
324
+ description: "Extract verifier completion events for a run by loopId or latest selector. Read-only.",
325
+ inputSchema: {
326
+ type: "object",
327
+ additionalProperties: false,
328
+ properties: {
329
+ loopId: {
330
+ type: "string",
331
+ description: "Martin loop ID under the run store."
332
+ },
333
+ runsDir: {
334
+ type: "string",
335
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
336
+ },
337
+ latest: {
338
+ const: true,
339
+ description: "When true, loads the most recently updated loop record in the runs directory."
340
+ }
341
+ },
342
+ oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
343
+ }
344
+ },
345
+ {
346
+ name: "martin_run_dossier",
347
+ description: "Build a compact read-only dossier for review: summary, budget, attempts, and verification evidence.",
348
+ inputSchema: {
349
+ type: "object",
350
+ additionalProperties: false,
351
+ properties: {
352
+ loopId: {
353
+ type: "string",
354
+ description: "Martin loop ID under the run store."
355
+ },
356
+ runsDir: {
357
+ type: "string",
358
+ description: "Optional runs-root override resolved under the default Martin runs root. Defaults to MARTIN_RUNS_DIR or ~/.martin/runs."
359
+ },
360
+ latest: {
361
+ const: true,
362
+ description: "When true, loads the most recently updated loop record in the runs directory."
363
+ }
364
+ },
365
+ oneOf: [{ required: ["loopId"] }, { required: ["latest"] }]
366
+ }
155
367
  }
156
368
  ]
157
369
  }));
158
370
  // ---------------------------------------------------------------------------
371
+ // Resources and prompts
372
+ // ---------------------------------------------------------------------------
373
+ server.setRequestHandler(ListResourcesRequestSchema, () => ({
374
+ resources: listMartinResources()
375
+ }));
376
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
377
+ resourceTemplates: listMartinResourceTemplates()
378
+ }));
379
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
380
+ return readMartinResource(request.params.uri);
381
+ });
382
+ server.setRequestHandler(ListPromptsRequestSchema, () => ({
383
+ prompts: listMartinPrompts()
384
+ }));
385
+ server.setRequestHandler(GetPromptRequestSchema, (request) => {
386
+ return getMartinPrompt(request.params.name, request.params.arguments ?? {});
387
+ });
388
+ // ---------------------------------------------------------------------------
159
389
  // Tool dispatch
160
390
  // ---------------------------------------------------------------------------
161
391
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
162
392
  const { name, arguments: args } = request.params;
163
393
  try {
394
+ if (name === "martin_doctor") {
395
+ const input = validateToolInput("martin_doctor", args);
396
+ const output = await martinDoctorTool(input);
397
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
398
+ }
399
+ if (name === "martin_preflight") {
400
+ const input = validateToolInput("martin_preflight", args);
401
+ const output = await martinPreflightTool(input);
402
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
403
+ }
164
404
  if (name === "martin_run") {
165
405
  const input = validateToolInput("martin_run", args);
166
406
  const output = await runLoopTool(input);
@@ -176,6 +416,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
176
416
  const output = await getStatusTool(input);
177
417
  return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
178
418
  }
419
+ if (name === "martin_list_runs") {
420
+ const input = validateToolInput("martin_list_runs", args);
421
+ const output = await listRunsTool(input);
422
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
423
+ }
424
+ if (name === "martin_get_run") {
425
+ const input = validateToolInput("martin_get_run", args);
426
+ const output = await getRunTool(input);
427
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
428
+ }
429
+ if (name === "martin_get_attempt") {
430
+ const input = validateToolInput("martin_get_attempt", args);
431
+ const output = await getAttemptTool(input);
432
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
433
+ }
434
+ if (name === "martin_get_verification_results") {
435
+ const input = validateToolInput("martin_get_verification_results", args);
436
+ const output = await getVerificationResultsTool(input);
437
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
438
+ }
439
+ if (name === "martin_run_dossier") {
440
+ const input = validateToolInput("martin_run_dossier", args);
441
+ const output = await runDossierTool(input);
442
+ return { content: [{ type: "text", text: JSON.stringify(output, null, 2) }] };
443
+ }
179
444
  return {
180
445
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
181
446
  isError: true
@@ -0,0 +1,69 @@
1
+ import { type LoopRunRecord } from "../vendor/core/index.js";
2
+ export interface RunSelectorInput {
3
+ loopId?: string;
4
+ runsDir?: string;
5
+ latest?: boolean;
6
+ }
7
+ export interface RunSummary {
8
+ loopId: string;
9
+ title: string;
10
+ objective: string;
11
+ status: string;
12
+ lifecycleState: string;
13
+ createdAt: string;
14
+ updatedAt: string;
15
+ attempts: number;
16
+ costUsd: number;
17
+ avoidedUsd: number;
18
+ pressure: string;
19
+ shouldStop: boolean;
20
+ verificationCount: number;
21
+ }
22
+ export interface VerificationResultSummary {
23
+ eventId?: string;
24
+ timestamp?: string;
25
+ lifecycleState?: string;
26
+ passed?: boolean;
27
+ summary?: string;
28
+ }
29
+ export declare function summarizeRun(loop: LoopRunRecord): RunSummary;
30
+ export declare function listRunSummaries(input?: {
31
+ runsDir?: string;
32
+ limit?: number;
33
+ }): Promise<RunSummary[]>;
34
+ export declare function loadSelectedRun(input: RunSelectorInput): Promise<LoopRunRecord>;
35
+ export declare function extractVerificationResults(loop: LoopRunRecord): VerificationResultSummary[];
36
+ export declare function getAttempt(loop: LoopRunRecord, attemptIndex: number): import("../vendor/core/index.js").LoopAttemptRecord;
37
+ export declare function buildRunDossier(loop: LoopRunRecord): {
38
+ loopId: string;
39
+ generatedAt: string;
40
+ sections: ({
41
+ kind: string;
42
+ content: {
43
+ title: string;
44
+ objective: string;
45
+ };
46
+ } | {
47
+ kind: string;
48
+ content: {
49
+ budget: {
50
+ maxUsd: number;
51
+ softLimitUsd: number;
52
+ maxIterations: number;
53
+ maxTokens: number;
54
+ };
55
+ cost: {
56
+ actualUsd: number;
57
+ tokensIn: number;
58
+ tokensOut: number;
59
+ avoidedUsd?: number;
60
+ };
61
+ };
62
+ } | {
63
+ kind: string;
64
+ content: import("../vendor/core/index.js").LoopAttemptRecord[];
65
+ } | {
66
+ kind: string;
67
+ content: VerificationResultSummary[];
68
+ })[];
69
+ };
@@ -0,0 +1,108 @@
1
+ import { evaluateCostGovernor } from "../vendor/core/index.js";
2
+ import { loadLoopRecordForStatus, loadLoopRecordsForInspect } from "./run-store.js";
3
+ export function summarizeRun(loop) {
4
+ const costState = evaluateCostGovernor({
5
+ budget: loop.budget,
6
+ cost: {
7
+ actualUsd: loop.cost.actualUsd,
8
+ avoidedUsd: loop.cost.avoidedUsd ?? 0,
9
+ tokensIn: loop.cost.tokensIn,
10
+ tokensOut: loop.cost.tokensOut
11
+ },
12
+ attemptsUsed: loop.attempts.length
13
+ });
14
+ return {
15
+ loopId: loop.loopId,
16
+ title: loop.task.title,
17
+ objective: loop.task.objective,
18
+ status: loop.status,
19
+ lifecycleState: loop.lifecycleState,
20
+ createdAt: loop.createdAt,
21
+ updatedAt: loop.updatedAt,
22
+ attempts: loop.attempts.length,
23
+ costUsd: loop.cost.actualUsd,
24
+ avoidedUsd: loop.cost.avoidedUsd ?? 0,
25
+ pressure: costState.pressure,
26
+ shouldStop: costState.shouldStop,
27
+ verificationCount: extractVerificationResults(loop).length
28
+ };
29
+ }
30
+ export async function listRunSummaries(input = {}) {
31
+ const inspection = await loadLoopRecordsForInspect({ runsDir: input.runsDir });
32
+ const summaries = inspection.loops.map((loop) => summarizeRun(loop));
33
+ summaries.sort((left, right) => {
34
+ const leftTime = Date.parse(left.updatedAt ?? left.createdAt);
35
+ const rightTime = Date.parse(right.updatedAt ?? right.createdAt);
36
+ return (Number.isFinite(rightTime) ? rightTime : 0) - (Number.isFinite(leftTime) ? leftTime : 0);
37
+ });
38
+ return summaries.slice(0, input.limit ?? 20);
39
+ }
40
+ export async function loadSelectedRun(input) {
41
+ const selectors = [input.loopId ? "loopId" : null, input.latest ? "latest" : null].filter(Boolean);
42
+ if (selectors.length !== 1) {
43
+ throw new Error("Provide exactly one of loopId or latest.");
44
+ }
45
+ const source = await loadLoopRecordForStatus({
46
+ ...(input.loopId ? { loopId: input.loopId } : {}),
47
+ ...(input.latest ? { latest: true } : {}),
48
+ ...(input.runsDir ? { runsDir: input.runsDir } : {})
49
+ });
50
+ return source.loop;
51
+ }
52
+ export function extractVerificationResults(loop) {
53
+ const events = "events" in loop && Array.isArray(loop.events) ? loop.events : [];
54
+ return events
55
+ .filter((event) => event?.type === "verification.completed")
56
+ .map((event) => {
57
+ const payload = isRecord(event.payload) ? event.payload : {};
58
+ return {
59
+ ...(typeof event.eventId === "string" ? { eventId: event.eventId } : {}),
60
+ ...(typeof event.timestamp === "string" ? { timestamp: event.timestamp } : {}),
61
+ ...(typeof event.lifecycleState === "string" ? { lifecycleState: event.lifecycleState } : {}),
62
+ ...(typeof payload.passed === "boolean" ? { passed: payload.passed } : {}),
63
+ ...(typeof payload.summary === "string" ? { summary: payload.summary } : {})
64
+ };
65
+ });
66
+ }
67
+ export function getAttempt(loop, attemptIndex) {
68
+ const attempt = loop.attempts.find((candidate) => candidate.index === attemptIndex);
69
+ if (!attempt) {
70
+ throw new Error("Attempt not found.");
71
+ }
72
+ return attempt;
73
+ }
74
+ export function buildRunDossier(loop) {
75
+ return {
76
+ loopId: loop.loopId,
77
+ generatedAt: new Date().toISOString(),
78
+ sections: [
79
+ {
80
+ kind: "summary",
81
+ content: summarizeRun(loop)
82
+ },
83
+ {
84
+ kind: "task",
85
+ content: loop.task
86
+ },
87
+ {
88
+ kind: "budget",
89
+ content: {
90
+ budget: loop.budget,
91
+ cost: loop.cost
92
+ }
93
+ },
94
+ {
95
+ kind: "attempts",
96
+ content: loop.attempts
97
+ },
98
+ {
99
+ kind: "verification",
100
+ content: extractVerificationResults(loop)
101
+ }
102
+ ]
103
+ };
104
+ }
105
+ function isRecord(value) {
106
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
107
+ }
108
+ //# sourceMappingURL=cockpit-support.js.map
@@ -0,0 +1,35 @@
1
+ import { type LoopPreview, type MartinEngine } from "./tool-support.js";
2
+ export interface MartinDoctorInput {
3
+ workingDirectory?: string;
4
+ runsDir?: string;
5
+ engine?: MartinEngine;
6
+ }
7
+ export interface MartinDoctorOutput {
8
+ status: "ok" | "degraded";
9
+ summary: string;
10
+ server: {
11
+ name: "martin-loop";
12
+ nodeVersion: string;
13
+ platform: NodeJS.Platform;
14
+ };
15
+ environment: {
16
+ workspaceRoot: string;
17
+ workingDirectory: string;
18
+ runsRoot: string;
19
+ mode: "live" | "stub";
20
+ liveMode: boolean;
21
+ };
22
+ engines: Record<MartinEngine, {
23
+ available: boolean;
24
+ detail: string;
25
+ resolvedPath?: string;
26
+ }>;
27
+ requestedEngine?: MartinEngine;
28
+ runStore: {
29
+ exists: boolean;
30
+ loopCount: number;
31
+ latestRun?: LoopPreview;
32
+ };
33
+ warnings: string[];
34
+ }
35
+ export declare function martinDoctorTool(input: MartinDoctorInput): Promise<MartinDoctorOutput>;
@@ -0,0 +1,56 @@
1
+ import { resolveRunsRoot } from "../vendor/core/index.js";
2
+ import { resolveSafeRepoRoot, resolveSafeRunsRootPath } from "../server-validation.js";
3
+ import { getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
4
+ export async function martinDoctorTool(input) {
5
+ const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
6
+ const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
7
+ const executionMode = resolveExecutionMode();
8
+ const claude = getEngineAvailability("claude");
9
+ const codex = getEngineAvailability("codex");
10
+ const runStore = await inspectRunsRoot(runsRoot);
11
+ const warnings = [];
12
+ if (!runStore.exists) {
13
+ warnings.push("Configured Martin runs root does not exist yet.");
14
+ }
15
+ if (executionMode.liveMode && !claude.available && !codex.available) {
16
+ warnings.push("Neither claude nor codex is currently available on PATH for live runs.");
17
+ }
18
+ if (input.engine && executionMode.liveMode) {
19
+ const selected = input.engine === "claude" ? claude : codex;
20
+ if (!selected.available) {
21
+ warnings.push(`Requested engine '${input.engine}' is not available on PATH.`);
22
+ }
23
+ }
24
+ warnings.push(...runStore.warnings);
25
+ const status = warnings.length === 0 ? "ok" : "degraded";
26
+ return {
27
+ status,
28
+ summary: status === "ok"
29
+ ? `Doctor passed: ${runStore.loopCount} run(s) visible in ${runsRoot}.`
30
+ : `Doctor found ${warnings.length} issue(s); review warnings before live execution.`,
31
+ server: {
32
+ name: "martin-loop",
33
+ nodeVersion: process.version,
34
+ platform: process.platform
35
+ },
36
+ environment: {
37
+ workspaceRoot: resolveSafeRepoRoot(),
38
+ workingDirectory,
39
+ runsRoot,
40
+ mode: executionMode.mode,
41
+ liveMode: executionMode.liveMode
42
+ },
43
+ engines: {
44
+ claude,
45
+ codex
46
+ },
47
+ ...(input.engine ? { requestedEngine: input.engine } : {}),
48
+ runStore: {
49
+ exists: runStore.exists,
50
+ loopCount: runStore.loopCount,
51
+ ...(runStore.latestRun ? { latestRun: runStore.latestRun } : {})
52
+ },
53
+ warnings
54
+ };
55
+ }
56
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,8 @@
1
+ import { getAttempt } from "./cockpit-support.js";
2
+ export interface GetAttemptInput {
3
+ loopId: string;
4
+ attemptIndex: number;
5
+ runsDir?: string;
6
+ }
7
+ export type GetAttemptOutput = ReturnType<typeof getAttempt>;
8
+ export declare function getAttemptTool(input: GetAttemptInput): Promise<GetAttemptOutput>;
@@ -0,0 +1,6 @@
1
+ import { getAttempt, loadSelectedRun } from "./cockpit-support.js";
2
+ export async function getAttemptTool(input) {
3
+ const loop = await loadSelectedRun({ loopId: input.loopId, runsDir: input.runsDir });
4
+ return getAttempt(loop, input.attemptIndex);
5
+ }
6
+ //# sourceMappingURL=get-attempt.js.map
@@ -0,0 +1,17 @@
1
+ import type { LoopRunRecord } from "../vendor/core/index.js";
2
+ import { summarizeRun } from "./cockpit-support.js";
3
+ export interface GetRunInput {
4
+ loopId?: string;
5
+ runsDir?: string;
6
+ latest?: boolean;
7
+ }
8
+ export interface GetRunOutput {
9
+ summary: ReturnType<typeof summarizeRun>;
10
+ task: LoopRunRecord["task"];
11
+ budget: LoopRunRecord["budget"];
12
+ cost: LoopRunRecord["cost"];
13
+ attempts: LoopRunRecord["attempts"];
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+ export declare function getRunTool(input: GetRunInput): Promise<GetRunOutput>;
@@ -0,0 +1,14 @@
1
+ import { loadSelectedRun, summarizeRun } from "./cockpit-support.js";
2
+ export async function getRunTool(input) {
3
+ const loop = await loadSelectedRun(input);
4
+ return {
5
+ summary: summarizeRun(loop),
6
+ task: loop.task,
7
+ budget: loop.budget,
8
+ cost: loop.cost,
9
+ attempts: loop.attempts,
10
+ createdAt: loop.createdAt,
11
+ updatedAt: loop.updatedAt
12
+ };
13
+ }
14
+ //# sourceMappingURL=get-run.js.map
@@ -0,0 +1,11 @@
1
+ import { type VerificationResultSummary } from "./cockpit-support.js";
2
+ export interface GetVerificationResultsInput {
3
+ loopId?: string;
4
+ runsDir?: string;
5
+ latest?: boolean;
6
+ }
7
+ export interface GetVerificationResultsOutput {
8
+ loopId: string;
9
+ results: VerificationResultSummary[];
10
+ }
11
+ export declare function getVerificationResultsTool(input: GetVerificationResultsInput): Promise<GetVerificationResultsOutput>;
@@ -0,0 +1,9 @@
1
+ import { extractVerificationResults, loadSelectedRun } from "./cockpit-support.js";
2
+ export async function getVerificationResultsTool(input) {
3
+ const loop = await loadSelectedRun(input);
4
+ return {
5
+ loopId: loop.loopId,
6
+ results: extractVerificationResults(loop)
7
+ };
8
+ }
9
+ //# sourceMappingURL=get-verification-results.js.map