@martinloop/mcp 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +49 -104
  2. package/dist/package-version.d.ts +1 -1
  3. package/dist/package-version.js +1 -1
  4. package/dist/prompts.d.ts +1 -1
  5. package/dist/resources.d.ts +1 -1
  6. package/dist/resources.js +2 -2
  7. package/dist/server-validation.d.ts +1 -0
  8. package/dist/server-validation.js +8 -0
  9. package/dist/server.js +87 -9
  10. package/dist/tools/doctor.d.ts +39 -1
  11. package/dist/tools/doctor.js +68 -9
  12. package/dist/tools/eval.js +3 -2
  13. package/dist/tools/get-run.d.ts +3 -0
  14. package/dist/tools/get-run.js +3 -1
  15. package/dist/tools/get-verification-results.d.ts +3 -0
  16. package/dist/tools/get-verification-results.js +3 -1
  17. package/dist/tools/plan.js +4 -2
  18. package/dist/tools/pr-tools.js +2 -1
  19. package/dist/tools/preflight.d.ts +41 -1
  20. package/dist/tools/preflight.js +74 -19
  21. package/dist/tools/run-dossier.d.ts +3 -0
  22. package/dist/tools/run-dossier.js +5 -2
  23. package/dist/tools/run-loop.d.ts +7 -2
  24. package/dist/tools/run-loop.js +67 -35
  25. package/dist/tools/run-store.js +67 -15
  26. package/dist/tools/tool-errors.js +1 -1
  27. package/dist/tools/tool-support.d.ts +8 -3
  28. package/dist/tools/tool-support.js +61 -18
  29. package/dist/tools/workflow-governance.d.ts +19 -3
  30. package/dist/tools/workflow-governance.js +107 -55
  31. package/dist/vendor/adapters/claude-cli.d.ts +45 -3
  32. package/dist/vendor/adapters/claude-cli.js +465 -45
  33. package/dist/vendor/adapters/cli-bridge.d.ts +46 -0
  34. package/dist/vendor/adapters/cli-bridge.js +147 -38
  35. package/dist/vendor/adapters/codex-launcher.d.ts +76 -0
  36. package/dist/vendor/adapters/codex-launcher.js +538 -0
  37. package/dist/vendor/adapters/index.d.ts +3 -2
  38. package/dist/vendor/adapters/index.js +3 -2
  39. package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
  40. package/dist/vendor/adapters/openai-compatible.js +50 -19
  41. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  42. package/dist/vendor/adapters/runtime-support.js +9 -1
  43. package/dist/vendor/adapters/stub-direct-provider.js +3 -0
  44. package/dist/vendor/adapters/verifier-only.d.ts +2 -0
  45. package/dist/vendor/adapters/verifier-only.js +11 -4
  46. package/dist/vendor/contracts/index.d.ts +39 -0
  47. package/dist/vendor/contracts/index.js +2 -0
  48. package/dist/vendor/core/context-integrity.js +28 -3
  49. package/dist/vendor/core/grounding.d.ts +1 -0
  50. package/dist/vendor/core/grounding.js +6 -2
  51. package/dist/vendor/core/index.d.ts +24 -3
  52. package/dist/vendor/core/index.js +113 -21
  53. package/dist/vendor/core/leash.js +85 -8
  54. package/dist/vendor/core/persistence/index.d.ts +2 -0
  55. package/dist/vendor/core/persistence/index.js +1 -0
  56. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  57. package/dist/vendor/core/persistence/integrity.js +248 -0
  58. package/dist/vendor/core/persistence/store.d.ts +7 -0
  59. package/dist/vendor/core/persistence/store.js +25 -1
  60. package/dist/vendor/core/policy.d.ts +9 -0
  61. package/dist/workflow-state.d.ts +9 -0
  62. package/dist/workflow-state.js +46 -3
  63. package/package.json +2 -2
  64. package/server.json +2 -2
package/README.md CHANGED
@@ -1,79 +1,21 @@
1
1
  # @martinloop/mcp
2
2
 
3
- Governed MCP server for AI coding agents with budgets, receipts, and review-ready evidence.
3
+ Governed MCP server for AI coding agents over local stdio.
4
4
 
5
- `@martinloop/mcp` is the standalone MartinLoop server for MCP hosts. It stays local-first and stdio-first, and it gives hosts one clear execution path: check readiness, plan the work, preflight the contract, run it, and inspect the result with enough evidence to decide what happens next.
5
+ `@martinloop/mcp@0.3.0` is the live public baseline today. `0.3.1` is the current in-repo release candidate for the next public cut.
6
6
 
7
- The root `martin-loop` package and the standalone `@martinloop/mcp` package move on separate version lines. For the current root release, see [MartinLoop 0.2.8 release notes](../../docs/release/OSS-0.2.8-RELEASE-NOTES.md).
7
+ This package stays local-first and stdio-first in the public OSS lane.
8
8
 
9
- ## What is new in 0.2.7
9
+ ## Public release train
10
10
 
11
- - better onboarding inside MCP, including guide resources for command mapping, IDE setup, and operating rules
12
- - a stricter governed run sequence, so `martin_run` refuses to start until matching `martin_doctor`, `martin_plan`, and `martin_preflight` receipts exist for the same task
13
- - cleaner review and handoff surfaces, especially around dossier, eval, and publish-readiness guidance
11
+ - `0.2.7` made the guided MCP workflow easier to adopt and harder to misuse.
12
+ - `0.3.0` is the live adoption baseline that made host setup and onboarding clearer.
13
+ - `0.3.1` is the review and handoff release candidate currently staged in-repo.
14
+ - `0.3.2` remains the planned follow-on for opt-in execution controls.
14
15
 
15
- If you are installing MartinLoop for the first time, start with the root CLI first:
16
+ ## What ships today
16
17
 
17
- ```sh
18
- npx martin-loop start
19
- npx martin-loop tour
20
- npx martin-loop doctor
21
- ```
22
-
23
- ## Install
24
-
25
- Run the packaged server directly:
26
-
27
- ```sh
28
- npx -y @martinloop/mcp
29
- ```
30
-
31
- Add it to Codex:
32
-
33
- ```sh
34
- codex mcp add martin-loop -- npx -y @martinloop/mcp
35
- ```
36
-
37
- Add it to Claude Code:
38
-
39
- ```sh
40
- claude mcp add --transport stdio --scope user martin-loop -- npx -y @martinloop/mcp
41
- claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp
42
- ```
43
-
44
- Generate host config from the root CLI:
45
-
46
- ```sh
47
- npx martin-loop mcp print-config --host codex --transport stdio --profile minimal
48
- npx martin-loop mcp print-config --host claude --transport stdio --profile diagnostic
49
- npx martin-loop mcp print-config --host gemini --transport stdio --profile full-local
50
- npx martin-loop mcp print-config --host generic --transport stdio --profile github-review
51
- ```
52
-
53
- Registry/server identifier: `io.github.Keesan12/martin-loop`
54
-
55
- ## Recommended Flow
56
-
57
- 1. `martin_doctor`
58
- 2. `martin_plan`
59
- 3. `martin_preflight`
60
- 4. `martin_run`
61
- 5. `martin_status` or `martin_logs`
62
- 6. `martin_dossier` or the `martin_get_*` tools
63
- 7. `martin_eval`
64
- 8. `martin_pr_summary` or `martin_review_pr` when a host is preparing GitHub review output
65
-
66
- `martin_run` is the primary coding execution entrypoint. In `0.2.7`, it hard-blocks until the matching readiness, planning, and preflight receipts exist for the same task. That keeps the default flow honest for both humans and agents.
67
-
68
- ## Profiles
69
-
70
- - `minimal`: read-heavy default for safe host setup
71
- - `diagnostic`: deeper inspection and evaluator support
72
- - `full-local`: includes execution and run-control helpers for local workflows
73
- - `github-review`: adds PR review helpers for GitHub-oriented hosts
74
- - `starter` and `full`: compatibility aliases that map onto the same discovery surface
75
-
76
- ## Tools
18
+ ### Tools
77
19
 
78
20
  - `martin_doctor`
79
21
  - `martin_plan`
@@ -97,7 +39,7 @@ Registry/server identifier: `io.github.Keesan12/martin-loop`
97
39
  - `martin_create_pr`
98
40
  - `martin_review_pr`
99
41
 
100
- ## Resources
42
+ ### Resources
101
43
 
102
44
  - `martin://server/health`
103
45
  - `martin://runs/recent`
@@ -114,19 +56,9 @@ Registry/server identifier: `io.github.Keesan12/martin-loop`
114
56
  - `martin://agent/next-step`
115
57
  - `martin://guides/mcp-usage`
116
58
  - `martin://guides/agent-start`
117
- - `martin://guides/command-map`
118
- - `martin://guides/ide-onboarding`
119
- - `martin://guides/operating-rules`
120
59
  - `martin://guides/publish-readiness`
121
60
 
122
- ## Resource Templates
123
-
124
- - `martin://runs/{loopId}`
125
- - `martin://runs/{loopId}/dossier`
126
- - `martin://runs/{loopId}/attempts/{attemptIndex}`
127
- - `martin://runs/{loopId}/verification`
128
-
129
- ## Prompts
61
+ ### Prompts
130
62
 
131
63
  - `martin_start`
132
64
  - `martin_preflight`
@@ -138,40 +70,56 @@ Registry/server identifier: `io.github.Keesan12/martin-loop`
138
70
  - `martin_debug_failed_run`
139
71
  - `martin_publish_readiness_review`
140
72
  - `martin_triage_run_store`
141
- - `safe_bug_fix`
142
- - `write_tests_first`
143
- - `small_refactor`
144
- - `security_review`
145
- - `pr_review`
146
- - `release_check`
147
73
 
148
- ## Runtime Model
74
+ ## Recommended flow
149
75
 
150
- - `martin_run` is the primary coding execution entrypoint.
151
- - `martin_run` now blocks until the same task has matching `martin_doctor`, `martin_plan`, and `martin_preflight` receipts.
152
- - `martin_plan`, `martin_doctor`, `martin_preflight`, `martin_status`, `martin_logs`, `martin_dossier`, `martin_eval`, and the `martin_get_*` family are planning or inspection surfaces.
153
- - `martin_pause`, `martin_cancel`, `martin_continue`, and `martin_create_pr` are explicit follow-on control helpers and stay out of the default `minimal` profile.
154
- - Live runs require `claude` or `codex` on `PATH`.
155
- - Stub or smoke flows use `MARTIN_LIVE=false`.
156
- - Paths stay bounded to the configured workspace root and runs root.
76
+ 1. `martin_doctor`
77
+ 2. `martin_plan`
78
+ 3. `martin_preflight`
79
+ 4. `martin_run`
80
+ 5. `martin_status` or `martin_logs`
81
+ 6. `martin_dossier`
82
+ 7. `martin_eval`
157
83
 
158
- ## Debugging
84
+ ## Install
159
85
 
160
- Use the live handshake inspector before you blame a host config:
86
+ ```sh
87
+ npx -y @martinloop/mcp
88
+ ```
89
+
90
+ Codex:
161
91
 
162
92
  ```sh
163
- pnpm --filter @martinloop/mcp inspect:live
93
+ codex mcp add martin-loop -- npx -y @martinloop/mcp
164
94
  ```
165
95
 
166
- If you want the official MCP Inspector UI, point it at the same stdio launch command:
96
+ Claude Code:
167
97
 
168
98
  ```sh
169
- npx @modelcontextprotocol/inspector --command npx --args "-y,@martinloop/mcp"
99
+ # macOS/Linux
100
+ claude mcp add --transport stdio --scope user martin-loop -- npx -y @martinloop/mcp
101
+
102
+ # Windows PowerShell or cmd.exe
103
+ claude mcp add --transport stdio --scope user martin-loop -- cmd /c npx -y @martinloop/mcp
170
104
  ```
171
105
 
172
- ## Verification
106
+ If you want generated host config, use the MartinLoop CLI:
173
107
 
174
- From the repository root:
108
+ ```sh
109
+ martin mcp print-config --host codex --transport stdio --profile minimal
110
+ martin mcp print-config --host claude --transport stdio --profile diagnostic
111
+ martin mcp print-config --host gemini --transport stdio --profile full-local
112
+ martin mcp print-config --host generic --transport stdio --profile github-review
113
+ ```
114
+
115
+ ## Compatibility posture
116
+
117
+ - `martin_run` remains the single execution entrypoint.
118
+ - Read-only inspection stays available without execution-capable profiles.
119
+ - The OSS package stays focused on local stdio workflows. Hosted and team features live on a separate product track.
120
+ - Later `0.3.x` releases should widen adoption and guidance without blurring those boundaries.
121
+
122
+ ## Verification
175
123
 
176
124
  ```sh
177
125
  pnpm --filter @martinloop/mcp lint
@@ -180,7 +128,4 @@ pnpm --filter @martinloop/mcp build
180
128
  pnpm --filter @martinloop/mcp smoke:pack
181
129
  pnpm --filter @martinloop/mcp smoke:published:pack
182
130
  pnpm --filter @martinloop/mcp verify:release
183
- pnpm --filter @martin/cli verify:hosts:live
184
131
  ```
185
-
186
- See [MCP setup](../../docs/getting-started/mcp.md), [MCP tool reference](../../docs/reference/mcp-tools.md), and [MCP compatibility](../../docs/reference/mcp-compatibility.md).
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.2.7";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.3.1";
@@ -1,3 +1,3 @@
1
1
  // Keep this aligned with packages/mcp/package.json during version bumps so the
2
2
  // runtime does not depend on package.json being present in every hosted bundle.
3
- export const MARTIN_MCP_PACKAGE_VERSION = "0.2.7";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.3.1";
package/dist/prompts.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface MartinGetPromptInput {
5
5
  arguments?: Record<string, string>;
6
6
  runsDir?: string;
7
7
  workingDirectory?: string;
8
- engine?: "claude" | "codex";
8
+ engine?: "claude" | "codex" | "gemini";
9
9
  }
10
10
  export declare function listMartinPrompts(): {
11
11
  prompts: Prompt[];
@@ -26,7 +26,7 @@ export interface MartinReadResourceInput {
26
26
  uri: string;
27
27
  runsDir?: string;
28
28
  workingDirectory?: string;
29
- engine?: "claude" | "codex";
29
+ engine?: "claude" | "codex" | "gemini";
30
30
  }
31
31
  export declare function listMartinResources(): {
32
32
  resources: Resource[];
package/dist/resources.js CHANGED
@@ -338,7 +338,7 @@ async function loadLatestRunForCompactResource(runsRoot) {
338
338
  empty: true,
339
339
  warnings: [
340
340
  "No Martin run records were found yet.",
341
- "Run `npx martin-loop demo`, then run a governed task or set MARTIN_LIVE=false for a no-spend local proof run."
341
+ "Run `npx martin-loop demo`, then run `npx martin-loop run ... --proof --verify <command>` for a no-spend local proof pass."
342
342
  ]
343
343
  };
344
344
  }
@@ -561,7 +561,7 @@ function compactEmptyState(kind, runsRoot, warnings) {
561
561
  status: "empty",
562
562
  runsRoot,
563
563
  summary: "No Martin run records are available yet.",
564
- nextStep: "Run `martin doctor`, create the demo workspace with `npx martin-loop demo`, then run a no-spend stub task with MARTIN_LIVE=false.",
564
+ nextStep: "Run `npx martin-loop doctor`, create the demo workspace with `npx martin-loop demo`, then run `npx martin-loop run ... --proof --verify <command>`.",
565
565
  warnings
566
566
  };
567
567
  }
@@ -2,6 +2,7 @@ type ToolName = "martin_run" | "martin_inspect" | "martin_status" | "martin_doct
2
2
  export { sanitizeToolErrorMessage } from "./tools/tool-errors.js";
3
3
  export declare function validateToolInput(name: ToolName, args: unknown): unknown;
4
4
  export declare function resolveSafeRepoRoot(repoRoot?: string, workspaceRoot?: string): string;
5
+ export declare function resolveTrustedLoopRepoRoot(repoRoot?: string, workspaceRoot?: string): string;
5
6
  export declare function resolveSafeRunsJsonPath(file: string, runsRoot?: string): string;
6
7
  export declare function resolveSafeRunsPath(file: string, runsRoot?: string): string;
7
8
  export declare function resolveSafeRunsRootPath(runsRoot?: string, fallbackRunsRoot?: string): string;
@@ -57,6 +57,14 @@ export function resolveSafeRepoRoot(repoRoot, workspaceRoot = process.env.MARTIN
57
57
  });
58
58
  return candidate;
59
59
  }
60
+ export function resolveTrustedLoopRepoRoot(repoRoot, workspaceRoot = process.env.MARTIN_MCP_WORKSPACE_ROOT ?? process.cwd()) {
61
+ try {
62
+ return resolveSafeRepoRoot(repoRoot, workspaceRoot);
63
+ }
64
+ catch {
65
+ throw invalidPathError("Run record points outside the trusted workspace.", "Inspect or promote only loop records that were created under the current workspace root.");
66
+ }
67
+ }
60
68
  export function resolveSafeRunsJsonPath(file, runsRoot = resolveRunsRoot(process.env)) {
61
69
  const baseRoot = resolve(runsRoot);
62
70
  const candidate = resolve(baseRoot, file);
package/dist/server.js CHANGED
@@ -44,8 +44,9 @@ import { martinTriageRunsTool } from "./tools/triage-runs.js";
44
44
  import { runLoopTool } from "./tools/run-loop.js";
45
45
  import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
46
46
  import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
47
- import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
48
- import { recordMcpWorkflowStep } from "./workflow-state.js";
47
+ import { normalizeLoopBudget } from "./tools/workflow-governance.js";
48
+ import { resolveSafeRepoRoot, sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
49
+ import { evaluateMcpRunGate, recordMcpWorkflowStep } from "./workflow-state.js";
49
50
  const stringArraySchema = {
50
51
  type: "array",
51
52
  items: { type: "string" }
@@ -122,10 +123,37 @@ const verificationSchema = {
122
123
  latestAttemptIndex: { type: "integer" },
123
124
  completedAt: { type: "string" },
124
125
  summary: { type: "string" },
126
+ steps: {
127
+ type: "array",
128
+ items: {
129
+ type: "object",
130
+ additionalProperties: true,
131
+ properties: {
132
+ command: { type: "string" },
133
+ launched: { type: "boolean" },
134
+ exitCode: { type: "integer" },
135
+ timedOut: { type: "boolean" },
136
+ fastFail: { type: "boolean" },
137
+ detail: { type: "string" }
138
+ },
139
+ required: ["command", "launched"]
140
+ }
141
+ },
125
142
  warnings: stringArraySchema
126
143
  },
127
144
  required: ["status", "eventCount", "ledgerEventCount", "warnings"]
128
145
  };
146
+ const receiptScopeSchema = {
147
+ type: "object",
148
+ additionalProperties: false,
149
+ properties: {
150
+ invocationRoot: { type: "string" },
151
+ workingDirectory: { type: "string" },
152
+ repoRoot: { type: "string" },
153
+ runsRoot: { type: "string" }
154
+ },
155
+ required: ["invocationRoot", "workingDirectory", "repoRoot", "runsRoot"]
156
+ };
129
157
  const artifactSummarySchema = {
130
158
  type: "object",
131
159
  additionalProperties: true,
@@ -370,11 +398,12 @@ const doctorOutputSchema = {
370
398
  workspaceRoot: { type: "string" },
371
399
  workingDirectory: { type: "string" },
372
400
  runsRoot: { type: "string" },
373
- mode: { type: "string", enum: ["live", "stub"] },
401
+ mode: { type: "string", enum: ["live", "proof"] },
374
402
  liveMode: { type: "boolean" }
375
403
  },
376
404
  required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
377
405
  },
406
+ receiptScope: receiptScopeSchema,
378
407
  engines: {
379
408
  type: "object",
380
409
  additionalProperties: true
@@ -392,7 +421,7 @@ const doctorOutputSchema = {
392
421
  },
393
422
  warnings: stringArraySchema
394
423
  },
395
- required: ["status", "summary", "server", "environment", "engines", "runStore", "warnings"]
424
+ required: ["status", "summary", "server", "environment", "receiptScope", "engines", "runStore", "warnings"]
396
425
  };
397
426
  const preflightOutputSchema = {
398
427
  type: "object",
@@ -401,11 +430,12 @@ const preflightOutputSchema = {
401
430
  ok: { type: "boolean" },
402
431
  summary: { type: "string" },
403
432
  warnings: stringArraySchema,
433
+ receiptScope: receiptScopeSchema,
404
434
  readiness: {
405
435
  type: "object",
406
436
  additionalProperties: false,
407
437
  properties: {
408
- mode: { type: "string", enum: ["live", "stub"] },
438
+ mode: { type: "string", enum: ["live", "proof"] },
409
439
  liveMode: { type: "boolean" },
410
440
  engineReady: { type: "boolean" }
411
441
  },
@@ -476,7 +506,7 @@ const preflightOutputSchema = {
476
506
  required: ["requestedEngine", "engineAvailability", "runsRoot", "pathScope", "expectedRunLayout"]
477
507
  }
478
508
  },
479
- required: ["ok", "summary", "warnings", "readiness", "normalized", "execution"]
509
+ required: ["ok", "summary", "warnings", "receiptScope", "readiness", "normalized", "execution"]
480
510
  };
481
511
  const listRunsOutputSchema = {
482
512
  type: "object",
@@ -1381,6 +1411,36 @@ export function createMartinMcpServer(serverInfo) {
1381
1411
  try {
1382
1412
  if (name === "martin_run") {
1383
1413
  const input = validateToolInput("martin_run", args);
1414
+ const runsRoot = resolveRunsRoot(process.env);
1415
+ const workingDirectory = input.workingDirectory ?? resolveSafeRepoRoot();
1416
+ const receiptScope = {
1417
+ invocationRoot: resolveSafeRepoRoot(),
1418
+ workingDirectory,
1419
+ repoRoot: workingDirectory,
1420
+ runsRoot
1421
+ };
1422
+ const gate = await evaluateMcpRunGate({
1423
+ runsRoot,
1424
+ workingDirectory,
1425
+ objective: input.objective,
1426
+ engine: input.engine,
1427
+ verificationPlan: input.verificationPlan,
1428
+ receiptScope,
1429
+ allowedPaths: input.allowedPaths,
1430
+ deniedPaths: input.deniedPaths,
1431
+ budget: normalizeRunBudget(input)
1432
+ });
1433
+ if (!gate.allowed) {
1434
+ throw new MartinToolError("policy_blocked", gate.summary, {
1435
+ category: "policy_blocked",
1436
+ suggestion: gate.nextAction,
1437
+ retryable: false,
1438
+ details: {
1439
+ missingSteps: gate.missingSteps,
1440
+ receiptScope
1441
+ }
1442
+ });
1443
+ }
1384
1444
  const output = await runLoopTool(input);
1385
1445
  return createToolSuccessResult(output, `Run ${output.loopId} is ${output.status}/${output.lifecycleState} after ${output.attempts} attempt(s); spend ${output.costUsd.toFixed(2)} USD.`);
1386
1446
  }
@@ -1401,7 +1461,8 @@ export function createMartinMcpServer(serverInfo) {
1401
1461
  runsRoot: output.environment.runsRoot,
1402
1462
  step: "doctor",
1403
1463
  workingDirectory: output.environment.workingDirectory,
1404
- engine: input.engine
1464
+ engine: input.engine,
1465
+ receiptScope: output.receiptScope
1405
1466
  }).catch(() => { });
1406
1467
  return createToolSuccessResult(output, output.summary);
1407
1468
  }
@@ -1412,7 +1473,13 @@ export function createMartinMcpServer(serverInfo) {
1412
1473
  runsRoot: resolveRunsRoot(process.env),
1413
1474
  step: "plan",
1414
1475
  workingDirectory: output.workingDirectory,
1415
- objective: output.objective
1476
+ objective: output.objective,
1477
+ receiptScope: {
1478
+ invocationRoot: resolveSafeRepoRoot(),
1479
+ workingDirectory: output.workingDirectory,
1480
+ repoRoot: output.workingDirectory,
1481
+ runsRoot: resolveRunsRoot(process.env)
1482
+ }
1416
1483
  }).catch(() => { });
1417
1484
  return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1418
1485
  }
@@ -1426,7 +1493,11 @@ export function createMartinMcpServer(serverInfo) {
1426
1493
  workingDirectory: output.normalized.workingDirectory,
1427
1494
  objective: output.normalized.objective,
1428
1495
  engine: output.normalized.engine,
1429
- verificationPlan: output.normalized.verificationPlan
1496
+ verificationPlan: output.normalized.verificationPlan,
1497
+ receiptScope: output.receiptScope,
1498
+ allowedPaths: output.normalized.allowedPaths,
1499
+ deniedPaths: output.normalized.deniedPaths,
1500
+ budget: output.normalized.budget
1430
1501
  }).catch(() => { });
1431
1502
  }
1432
1503
  return createToolSuccessResult(output, output.summary);
@@ -1516,6 +1587,13 @@ export function createMartinMcpServer(serverInfo) {
1516
1587
  });
1517
1588
  return server;
1518
1589
  }
1590
+ function normalizeRunBudget(input) {
1591
+ return normalizeLoopBudget({
1592
+ maxUsd: input.maxUsd,
1593
+ maxIterations: input.maxIterations,
1594
+ maxTokens: input.maxTokens
1595
+ });
1596
+ }
1519
1597
  export async function connectMartinMcpStdioServer() {
1520
1598
  const server = createMartinMcpServer();
1521
1599
  const transport = new StdioServerTransport();
@@ -1,3 +1,4 @@
1
+ import { type CodexHostPlatform } from "../vendor/adapters/index.js";
1
2
  import { type LoopPreview, type MartinEngine } from "./tool-support.js";
2
3
  import { type MartinReadinessReport } from "./workflow-governance.js";
3
4
  export interface MartinDoctorInput {
@@ -17,13 +18,50 @@ export interface MartinDoctorOutput {
17
18
  workspaceRoot: string;
18
19
  workingDirectory: string;
19
20
  runsRoot: string;
20
- mode: "live" | "stub";
21
+ mode: "live" | "proof";
21
22
  liveMode: boolean;
22
23
  };
24
+ scope: {
25
+ invocationRoot: string;
26
+ workingDirectory: string;
27
+ repoRoot: string;
28
+ runsRoot: string;
29
+ };
30
+ receiptScope: {
31
+ invocationRoot: string;
32
+ workingDirectory: string;
33
+ repoRoot: string;
34
+ runsRoot: string;
35
+ };
23
36
  engines: Record<MartinEngine, {
24
37
  available: boolean;
25
38
  detail: string;
26
39
  resolvedPath?: string;
40
+ candidatePaths?: string[];
41
+ selectedPath?: string;
42
+ hostPlatform?: CodexHostPlatform;
43
+ installKind?: string;
44
+ nativeInstallValid?: boolean;
45
+ invocationMode?: string;
46
+ sandboxMode?: string;
47
+ sandboxCompatible?: boolean;
48
+ nativeDependencyStatus?: string;
49
+ nativeDependencyPackage?: string;
50
+ launchReady?: boolean;
51
+ probeSummary?: string;
52
+ remediation?: string;
53
+ candidateProbeResults?: Array<{
54
+ path: string;
55
+ installKind: string;
56
+ invocationMode: string;
57
+ nativeInstallValid: boolean;
58
+ sandboxCompatible: boolean;
59
+ launchReady: boolean;
60
+ summary: string;
61
+ remediation?: string;
62
+ nativeDependencyStatus?: string;
63
+ nativeDependencyPackage?: string;
64
+ }>;
27
65
  }>;
28
66
  requestedEngine?: MartinEngine;
29
67
  runStore: {
@@ -1,28 +1,54 @@
1
+ import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
1
2
  import { resolveRunsRoot } from "../vendor/core/index.js";
2
3
  import { resolveSafeRepoRoot, resolveSafeRunsRootPath } from "../server-validation.js";
3
- import { getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
4
+ import { createSkippedCliAvailability, getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
4
5
  import { buildReadinessReport, inspectRepoSignals } from "./workflow-governance.js";
5
6
  export async function martinDoctorTool(input) {
6
7
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
7
8
  const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
9
+ const workspaceRoot = resolveSafeRepoRoot();
8
10
  const executionMode = resolveExecutionMode();
9
- const claude = getEngineAvailability("claude");
10
- const codex = getEngineAvailability("codex");
11
+ const claude = executionMode.liveMode
12
+ ? getEngineAvailability("claude")
13
+ : createSkippedCliAvailability("claude");
14
+ const codex = executionMode.liveMode
15
+ ? resolveCliCommandAvailability("codex")
16
+ : createSkippedCliAvailability("codex");
17
+ const gemini = executionMode.liveMode
18
+ ? getEngineAvailability("gemini")
19
+ : createSkippedCliAvailability("gemini");
20
+ const codexProbe = executionMode.liveMode && input.engine === "codex" && codex.available
21
+ ? probeCodexLaunch({
22
+ workingDirectory,
23
+ availability: codex
24
+ })
25
+ : undefined;
11
26
  const runStore = await inspectRunsRoot(runsRoot);
12
- const signals = inspectRepoSignals(workingDirectory);
27
+ const signals = inspectRepoSignals(workingDirectory, {
28
+ includeHostAvailability: executionMode.liveMode
29
+ });
13
30
  const readiness = buildReadinessReport(signals, runStore);
14
31
  const warnings = [];
32
+ const receiptScope = {
33
+ invocationRoot: workspaceRoot,
34
+ workingDirectory,
35
+ repoRoot: workingDirectory,
36
+ runsRoot
37
+ };
15
38
  if (!runStore.exists) {
16
39
  warnings.push("Configured Martin runs root does not exist yet.");
17
40
  }
18
- if (executionMode.liveMode && !claude.available && !codex.available) {
19
- warnings.push("Neither claude nor codex is currently available on PATH for live runs.");
41
+ if (executionMode.liveMode && !claude.available && !codex.available && !gemini.available) {
42
+ warnings.push("None of claude, codex, or gemini is currently available on PATH for live runs.");
20
43
  }
21
44
  if (input.engine && executionMode.liveMode) {
22
- const selected = input.engine === "claude" ? claude : codex;
45
+ const selected = input.engine === "claude" ? claude : input.engine === "gemini" ? gemini : codex;
23
46
  if (!selected.available) {
24
47
  warnings.push(`Requested engine '${input.engine}' is not available on PATH.`);
25
48
  }
49
+ if (input.engine === "codex" && codexProbe && !codexProbe.ok) {
50
+ warnings.push(codexProbe.summary);
51
+ }
26
52
  }
27
53
  warnings.push(...runStore.warnings);
28
54
  const status = warnings.length === 0 ? "ok" : "degraded";
@@ -38,12 +64,16 @@ export async function martinDoctorTool(input) {
38
64
  platform: process.platform
39
65
  },
40
66
  environment: {
41
- workspaceRoot: resolveSafeRepoRoot(),
67
+ workspaceRoot,
42
68
  workingDirectory,
43
69
  runsRoot,
44
70
  mode: executionMode.mode,
45
71
  liveMode: executionMode.liveMode
46
72
  },
73
+ scope: {
74
+ ...receiptScope
75
+ },
76
+ receiptScope,
47
77
  engines: {
48
78
  claude: {
49
79
  available: claude.available,
@@ -53,7 +83,36 @@ export async function martinDoctorTool(input) {
53
83
  codex: {
54
84
  available: codex.available,
55
85
  detail: codex.detail,
56
- ...(codex.resolvedPath ? { resolvedPath: codex.resolvedPath } : {})
86
+ ...(codex.resolvedPath ? { resolvedPath: codex.resolvedPath } : {}),
87
+ ...(codex.candidatePaths?.length ? { candidatePaths: codex.candidatePaths } : {}),
88
+ ...(codexProbe
89
+ ? {
90
+ selectedPath: codexProbe.command,
91
+ hostPlatform: codexProbe.diagnosis.hostPlatform,
92
+ installKind: codexProbe.diagnosis.installKind,
93
+ nativeInstallValid: codexProbe.diagnosis.nativeInstallValid,
94
+ invocationMode: codexProbe.diagnosis.invocationMode,
95
+ sandboxMode: codexProbe.diagnosis.sandboxMode,
96
+ sandboxCompatible: codexProbe.diagnosis.sandboxCompatible,
97
+ ...(codexProbe.diagnosis.nativeDependencyStatus
98
+ ? { nativeDependencyStatus: codexProbe.diagnosis.nativeDependencyStatus }
99
+ : {}),
100
+ ...(codexProbe.diagnosis.nativeDependencyPackage
101
+ ? { nativeDependencyPackage: codexProbe.diagnosis.nativeDependencyPackage }
102
+ : {}),
103
+ launchReady: codexProbe.ok,
104
+ probeSummary: codexProbe.summary,
105
+ ...(codexProbe.diagnosis.remediation ? { remediation: codexProbe.diagnosis.remediation } : {}),
106
+ ...(codexProbe.candidateProbeResults?.length
107
+ ? { candidateProbeResults: codexProbe.candidateProbeResults }
108
+ : {})
109
+ }
110
+ : {})
111
+ },
112
+ gemini: {
113
+ available: gemini.available,
114
+ detail: gemini.detail,
115
+ ...(gemini.resolvedPath ? { resolvedPath: gemini.resolvedPath } : {})
57
116
  }
58
117
  },
59
118
  ...(input.engine ? { requestedEngine: input.engine } : {}),
@@ -1,12 +1,13 @@
1
1
  import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
2
+ import { resolveTrustedLoopRepoRoot } from "../server-validation.js";
2
3
  import { buildVerificationSummary } from "./tool-support.js";
3
4
  import { assessRunRisk, inspectRepoSignals } from "./workflow-governance.js";
4
5
  export async function martinEvalTool(input) {
5
6
  const detail = await loadDetailedLoopRecord(input);
6
7
  const ledgerEvents = await readLedgerEvents(detail);
7
8
  const verification = buildVerificationSummary(detail.loop, ledgerEvents);
8
- const repoRoot = detail.loop.task?.repoRoot;
9
- const signals = inspectRepoSignals(repoRoot ?? process.cwd());
9
+ const repoRoot = resolveTrustedLoopRepoRoot(detail.loop.task?.repoRoot);
10
+ const signals = inspectRepoSignals(repoRoot);
10
11
  const risk = assessRunRisk({
11
12
  objective: detail.loop.task?.objective ?? detail.loop.loopId,
12
13
  allowedPaths: detail.loop.task?.allowedPaths ?? [],