@martinloop/mcp 0.2.7 → 0.3.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.
Files changed (52) 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 +18 -2
  10. package/dist/tools/doctor.d.ts +12 -1
  11. package/dist/tools/doctor.js +37 -6
  12. package/dist/tools/eval.js +3 -2
  13. package/dist/tools/get-run.d.ts +2 -0
  14. package/dist/tools/get-run.js +2 -1
  15. package/dist/tools/get-verification-results.d.ts +2 -0
  16. package/dist/tools/get-verification-results.js +2 -1
  17. package/dist/tools/pr-tools.js +2 -1
  18. package/dist/tools/preflight.d.ts +14 -1
  19. package/dist/tools/preflight.js +36 -5
  20. package/dist/tools/run-dossier.d.ts +2 -0
  21. package/dist/tools/run-dossier.js +4 -2
  22. package/dist/tools/run-loop.d.ts +3 -2
  23. package/dist/tools/run-loop.js +48 -28
  24. package/dist/tools/tool-errors.js +1 -1
  25. package/dist/tools/tool-support.d.ts +6 -3
  26. package/dist/tools/tool-support.js +12 -5
  27. package/dist/vendor/adapters/claude-cli.d.ts +25 -0
  28. package/dist/vendor/adapters/claude-cli.js +279 -19
  29. package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
  30. package/dist/vendor/adapters/cli-bridge.js +44 -3
  31. package/dist/vendor/adapters/codex-launcher.d.ts +44 -0
  32. package/dist/vendor/adapters/codex-launcher.js +247 -0
  33. package/dist/vendor/adapters/index.d.ts +3 -2
  34. package/dist/vendor/adapters/index.js +3 -2
  35. package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
  36. package/dist/vendor/adapters/openai-compatible.js +44 -19
  37. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  38. package/dist/vendor/adapters/runtime-support.js +8 -1
  39. package/dist/vendor/adapters/verifier-only.js +4 -3
  40. package/dist/vendor/contracts/index.d.ts +39 -0
  41. package/dist/vendor/contracts/index.js +2 -0
  42. package/dist/vendor/core/index.d.ts +23 -3
  43. package/dist/vendor/core/index.js +88 -15
  44. package/dist/vendor/core/persistence/index.d.ts +2 -0
  45. package/dist/vendor/core/persistence/index.js +1 -0
  46. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  47. package/dist/vendor/core/persistence/integrity.js +239 -0
  48. package/dist/vendor/core/persistence/store.d.ts +7 -0
  49. package/dist/vendor/core/persistence/store.js +25 -1
  50. package/dist/vendor/core/policy.d.ts +9 -0
  51. package/package.json +1 -1
  52. 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.2.7` is the current public baseline. It gives hosts one bounded execution entrypoint, strong read-only inspection, clear next-step guidance, and a safer default MartinLoop workflow.
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 next adoption release.
13
+ - `0.3.1` is planned for review and handoff controls.
14
+ - `0.3.2` is planned 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.0";
@@ -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.0";
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
@@ -122,6 +122,22 @@ const verificationSchema = {
122
122
  latestAttemptIndex: { type: "integer" },
123
123
  completedAt: { type: "string" },
124
124
  summary: { type: "string" },
125
+ steps: {
126
+ type: "array",
127
+ items: {
128
+ type: "object",
129
+ additionalProperties: true,
130
+ properties: {
131
+ command: { type: "string" },
132
+ launched: { type: "boolean" },
133
+ exitCode: { type: "integer" },
134
+ timedOut: { type: "boolean" },
135
+ fastFail: { type: "boolean" },
136
+ detail: { type: "string" }
137
+ },
138
+ required: ["command", "launched"]
139
+ }
140
+ },
125
141
  warnings: stringArraySchema
126
142
  },
127
143
  required: ["status", "eventCount", "ledgerEventCount", "warnings"]
@@ -370,7 +386,7 @@ const doctorOutputSchema = {
370
386
  workspaceRoot: { type: "string" },
371
387
  workingDirectory: { type: "string" },
372
388
  runsRoot: { type: "string" },
373
- mode: { type: "string", enum: ["live", "stub"] },
389
+ mode: { type: "string", enum: ["live", "proof"] },
374
390
  liveMode: { type: "boolean" }
375
391
  },
376
392
  required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
@@ -405,7 +421,7 @@ const preflightOutputSchema = {
405
421
  type: "object",
406
422
  additionalProperties: false,
407
423
  properties: {
408
- mode: { type: "string", enum: ["live", "stub"] },
424
+ mode: { type: "string", enum: ["live", "proof"] },
409
425
  liveMode: { type: "boolean" },
410
426
  engineReady: { type: "boolean" }
411
427
  },
@@ -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,23 @@ 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
+ };
23
30
  engines: Record<MartinEngine, {
24
31
  available: boolean;
25
32
  detail: string;
26
33
  resolvedPath?: string;
34
+ hostPlatform?: CodexHostPlatform;
35
+ nativeInstallValid?: boolean;
36
+ launchReady?: boolean;
37
+ probeSummary?: string;
27
38
  }>;
28
39
  requestedEngine?: MartinEngine;
29
40
  runStore: {
@@ -1,3 +1,4 @@
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
4
  import { getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
@@ -5,9 +6,17 @@ import { buildReadinessReport, inspectRepoSignals } from "./workflow-governance.
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
11
  const claude = getEngineAvailability("claude");
10
- const codex = getEngineAvailability("codex");
12
+ const codex = resolveCliCommandAvailability("codex");
13
+ const gemini = getEngineAvailability("gemini");
14
+ const codexProbe = executionMode.liveMode && codex.available
15
+ ? probeCodexLaunch({
16
+ workingDirectory,
17
+ availability: codex
18
+ })
19
+ : undefined;
11
20
  const runStore = await inspectRunsRoot(runsRoot);
12
21
  const signals = inspectRepoSignals(workingDirectory);
13
22
  const readiness = buildReadinessReport(signals, runStore);
@@ -15,14 +24,17 @@ export async function martinDoctorTool(input) {
15
24
  if (!runStore.exists) {
16
25
  warnings.push("Configured Martin runs root does not exist yet.");
17
26
  }
18
- if (executionMode.liveMode && !claude.available && !codex.available) {
19
- warnings.push("Neither claude nor codex is currently available on PATH for live runs.");
27
+ if (executionMode.liveMode && !claude.available && !codex.available && !gemini.available) {
28
+ warnings.push("None of claude, codex, or gemini is currently available on PATH for live runs.");
20
29
  }
21
30
  if (input.engine && executionMode.liveMode) {
22
- const selected = input.engine === "claude" ? claude : codex;
31
+ const selected = input.engine === "claude" ? claude : input.engine === "gemini" ? gemini : codex;
23
32
  if (!selected.available) {
24
33
  warnings.push(`Requested engine '${input.engine}' is not available on PATH.`);
25
34
  }
35
+ if (input.engine === "codex" && codexProbe && !codexProbe.ok) {
36
+ warnings.push(codexProbe.summary);
37
+ }
26
38
  }
27
39
  warnings.push(...runStore.warnings);
28
40
  const status = warnings.length === 0 ? "ok" : "degraded";
@@ -38,12 +50,18 @@ export async function martinDoctorTool(input) {
38
50
  platform: process.platform
39
51
  },
40
52
  environment: {
41
- workspaceRoot: resolveSafeRepoRoot(),
53
+ workspaceRoot,
42
54
  workingDirectory,
43
55
  runsRoot,
44
56
  mode: executionMode.mode,
45
57
  liveMode: executionMode.liveMode
46
58
  },
59
+ scope: {
60
+ invocationRoot: workspaceRoot,
61
+ workingDirectory,
62
+ repoRoot: workingDirectory,
63
+ runsRoot
64
+ },
47
65
  engines: {
48
66
  claude: {
49
67
  available: claude.available,
@@ -53,7 +71,20 @@ export async function martinDoctorTool(input) {
53
71
  codex: {
54
72
  available: codex.available,
55
73
  detail: codex.detail,
56
- ...(codex.resolvedPath ? { resolvedPath: codex.resolvedPath } : {})
74
+ ...(codex.resolvedPath ? { resolvedPath: codex.resolvedPath } : {}),
75
+ ...(codexProbe
76
+ ? {
77
+ hostPlatform: codexProbe.diagnosis.hostPlatform,
78
+ nativeInstallValid: codexProbe.diagnosis.nativeInstallValid,
79
+ launchReady: codexProbe.ok,
80
+ probeSummary: codexProbe.summary
81
+ }
82
+ : {})
83
+ },
84
+ gemini: {
85
+ available: gemini.available,
86
+ detail: gemini.detail,
87
+ ...(gemini.resolvedPath ? { resolvedPath: gemini.resolvedPath } : {})
57
88
  }
58
89
  },
59
90
  ...(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 ?? [],
@@ -1,4 +1,5 @@
1
1
  import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetRunInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -12,6 +13,7 @@ export interface MartinGetRunOutput {
12
13
  budget: ReturnType<typeof buildBudgetSnapshot>;
13
14
  cost: ReturnType<typeof buildCostSnapshot>;
14
15
  verification: ReturnType<typeof buildVerificationSummary>;
16
+ receiptIntegrity: ReceiptIntegritySummary;
15
17
  artifacts: ReturnType<typeof buildArtifactSummary>;
16
18
  inspection: {
17
19
  runsRoot: string;
@@ -1,4 +1,4 @@
1
- import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
1
+ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, resolveReceiptIntegrity, buildVerificationSummary } from "./tool-support.js";
2
2
  import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
3
3
  export async function martinGetRunTool(input) {
4
4
  const detail = await loadDetailedLoopRecord(input);
@@ -11,6 +11,7 @@ export async function martinGetRunTool(input) {
11
11
  budget: buildBudgetSnapshot(detail.loop.budget),
12
12
  cost: buildCostSnapshot(detail.loop.cost),
13
13
  verification,
14
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
14
15
  artifacts: buildArtifactSummary(detail.loop),
15
16
  inspection: {
16
17
  runsRoot: detail.runsRoot,
@@ -1,4 +1,5 @@
1
1
  import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetVerificationResultsInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -9,6 +10,7 @@ export interface MartinGetVerificationResultsOutput {
9
10
  sourceKind: "file" | "loop_id" | "latest" | "runs_root";
10
11
  loop: ReturnType<typeof buildLoopPreview>;
11
12
  verification: ReturnType<typeof buildVerificationSummary>;
13
+ receiptIntegrity: ReceiptIntegritySummary;
12
14
  warnings: string[];
13
15
  }
14
16
  export declare function martinGetVerificationResultsTool(input: MartinGetVerificationResultsInput): Promise<MartinGetVerificationResultsOutput>;
@@ -1,4 +1,4 @@
1
- import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
1
+ import { buildLoopPreview, buildVerificationSummary, resolveReceiptIntegrity } from "./tool-support.js";
2
2
  import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
3
3
  export async function martinGetVerificationResultsTool(input) {
4
4
  const detail = await loadDetailedLoopRecord(input);
@@ -9,6 +9,7 @@ export async function martinGetVerificationResultsTool(input) {
9
9
  sourceKind: detail.sourceKind,
10
10
  loop: buildLoopPreview(detail.loop),
11
11
  verification,
12
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
12
13
  warnings: [...detail.warnings, ...verification.warnings]
13
14
  };
14
15
  }
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { loadDetailedLoopRecord } from "./run-store.js";
3
3
  import { martinRunDossierTool } from "./run-dossier.js";
4
4
  import { martinEvalTool } from "./eval.js";
5
+ import { resolveTrustedLoopRepoRoot } from "../server-validation.js";
5
6
  import { detectCliAvailability } from "./tool-support.js";
6
7
  import { MartinToolError } from "./tool-errors.js";
7
8
  export async function martinPrSummaryTool(input) {
@@ -20,7 +21,7 @@ export async function martinPrSummaryTool(input) {
20
21
  export async function martinCreatePrTool(input) {
21
22
  const summary = await martinPrSummaryTool(input);
22
23
  const detail = await loadDetailedLoopRecord(input);
23
- const repoRoot = detail.loop.task?.repoRoot ?? process.cwd();
24
+ const repoRoot = resolveTrustedLoopRepoRoot(detail.loop.task?.repoRoot);
24
25
  const branch = readGitValue(repoRoot, ["branch", "--show-current"]);
25
26
  const title = input.title?.trim() || summary.title;
26
27
  if (!input.execute) {
@@ -1,3 +1,4 @@
1
+ import { type CodexHostPlatform } from "../vendor/adapters/index.js";
1
2
  import { type MartinEngine } from "./tool-support.js";
2
3
  import { buildPolicyPackDefinition, type MartinPlanProposal, type MartinPolicyPack, type MartinRiskAssessment, type MartinRunContract } from "./workflow-governance.js";
3
4
  export interface MartinPreflightInput {
@@ -23,8 +24,14 @@ export interface MartinPreflightOutput {
23
24
  ok: boolean;
24
25
  summary: string;
25
26
  warnings: string[];
27
+ scope: {
28
+ invocationRoot: string;
29
+ workingDirectory: string;
30
+ repoRoot: string;
31
+ runsRoot: string;
32
+ };
26
33
  readiness: {
27
- mode: "live" | "stub";
34
+ mode: "live" | "proof";
28
35
  liveMode: boolean;
29
36
  engineReady: boolean;
30
37
  };
@@ -52,6 +59,12 @@ export interface MartinPreflightOutput {
52
59
  detail: string;
53
60
  resolvedPath?: string;
54
61
  };
62
+ codexDiagnostics?: {
63
+ hostPlatform: CodexHostPlatform;
64
+ nativeInstallValid: boolean;
65
+ launchReady: boolean;
66
+ summary: string;
67
+ };
55
68
  runsRoot: string;
56
69
  pathScope: {
57
70
  repoRoot: string;
@@ -1,3 +1,4 @@
1
+ import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
1
2
  import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
2
3
  import { resolveRunsRoot } from "../vendor/core/index.js";
3
4
  import { resolveSafeRepoRoot } from "../server-validation.js";
@@ -5,10 +6,17 @@ import { formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-s
5
6
  import { buildPlanProposal, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
6
7
  export async function martinPreflightTool(input) {
7
8
  const executionMode = resolveExecutionMode();
9
+ const workspaceRoot = resolveSafeRepoRoot();
8
10
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
9
11
  const signals = inspectRepoSignals(workingDirectory);
10
12
  const engine = input.engine ?? "claude";
11
- const engineAvailability = getEngineAvailability(engine);
13
+ const engineAvailability = engine === "codex" ? resolveCliCommandAvailability("codex") : getEngineAvailability(engine);
14
+ const codexProbe = executionMode.liveMode && engine === "codex" && engineAvailability.available
15
+ ? probeCodexLaunch({
16
+ workingDirectory,
17
+ availability: engineAvailability
18
+ })
19
+ : undefined;
12
20
  const warnings = [];
13
21
  const allowedPaths = input.allowedPaths ?? [];
14
22
  const deniedPaths = input.deniedPaths ?? [];
@@ -20,11 +28,14 @@ export async function martinPreflightTool(input) {
20
28
  ...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
21
29
  };
22
30
  if (!executionMode.liveMode) {
23
- warnings.push("Stub mode is active; preflight only proves configuration shape, not live CLI readiness.");
31
+ warnings.push("Proof mode is active; preflight only proves configuration shape, not live CLI readiness.");
24
32
  }
25
33
  else if (!engineAvailability.available) {
26
34
  warnings.push(`Requested engine '${engine}' is not available on PATH.`);
27
35
  }
36
+ else if (engine === "codex" && codexProbe && !codexProbe.ok) {
37
+ warnings.push(codexProbe.summary);
38
+ }
28
39
  if ((input.verificationPlan?.length ?? 0) === 0) {
29
40
  warnings.push("No verificationPlan was provided; Martin can run, but completion confidence will be lower.");
30
41
  }
@@ -37,17 +48,27 @@ export async function martinPreflightTool(input) {
37
48
  const plan = buildPlanProposal(workingDirectory, input);
38
49
  const runContract = buildRunContract(workingDirectory, input);
39
50
  const policy = buildPolicyPackDefinition(input.policyPack, signals);
40
- const ok = !executionMode.liveMode || engineAvailability.available;
51
+ const engineReady = !executionMode.liveMode ||
52
+ (engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
53
+ const ok = engineReady;
41
54
  return {
42
55
  ok,
43
56
  summary: ok
44
57
  ? `Preflight ready for ${engine} in ${workingDirectory} with a ${formatUsd(budget.maxUsd)} budget cap and ${runContract.risk.level} risk.`
45
- : `Preflight blocked: ${engine} is not available for live execution.`,
58
+ : `Preflight blocked: ${engine === "codex" && codexProbe && !codexProbe.ok
59
+ ? codexProbe.summary
60
+ : `${engine} is not available for live execution.`}`,
46
61
  warnings,
62
+ scope: {
63
+ invocationRoot: workspaceRoot,
64
+ workingDirectory,
65
+ repoRoot: workingDirectory,
66
+ runsRoot: resolveRunsRoot(process.env)
67
+ },
47
68
  readiness: {
48
69
  mode: executionMode.mode,
49
70
  liveMode: executionMode.liveMode,
50
- engineReady: !executionMode.liveMode || engineAvailability.available
71
+ engineReady
51
72
  },
52
73
  normalized: {
53
74
  objective: input.objective,
@@ -70,6 +91,16 @@ export async function martinPreflightTool(input) {
70
91
  ? { resolvedPath: engineAvailability.resolvedPath }
71
92
  : {})
72
93
  },
94
+ ...(codexProbe
95
+ ? {
96
+ codexDiagnostics: {
97
+ hostPlatform: codexProbe.diagnosis.hostPlatform,
98
+ nativeInstallValid: codexProbe.diagnosis.nativeInstallValid,
99
+ launchReady: codexProbe.ok,
100
+ summary: codexProbe.summary
101
+ }
102
+ }
103
+ : {}),
73
104
  runsRoot: resolveRunsRoot(process.env),
74
105
  pathScope: {
75
106
  repoRoot: workingDirectory,