@martinloop/mcp 0.3.0 → 0.3.2

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 (48) hide show
  1. package/README.md +5 -4
  2. package/dist/package-version.d.ts +1 -1
  3. package/dist/package-version.js +1 -1
  4. package/dist/server-validation.js +2 -2
  5. package/dist/server.js +72 -10
  6. package/dist/tools/doctor.d.ts +27 -0
  7. package/dist/tools/doctor.js +39 -11
  8. package/dist/tools/get-run.d.ts +2 -1
  9. package/dist/tools/get-run.js +1 -0
  10. package/dist/tools/get-verification-results.d.ts +2 -1
  11. package/dist/tools/get-verification-results.js +1 -0
  12. package/dist/tools/plan.js +4 -2
  13. package/dist/tools/preflight.d.ts +27 -0
  14. package/dist/tools/preflight.js +44 -20
  15. package/dist/tools/run-dossier.d.ts +2 -1
  16. package/dist/tools/run-dossier.js +1 -0
  17. package/dist/tools/run-loop.d.ts +5 -1
  18. package/dist/tools/run-loop.js +20 -8
  19. package/dist/tools/run-store.js +67 -15
  20. package/dist/tools/tool-support.d.ts +2 -0
  21. package/dist/tools/tool-support.js +49 -13
  22. package/dist/tools/workflow-governance.d.ts +19 -3
  23. package/dist/tools/workflow-governance.js +107 -55
  24. package/dist/vendor/adapters/claude-cli.d.ts +20 -3
  25. package/dist/vendor/adapters/claude-cli.js +193 -33
  26. package/dist/vendor/adapters/cli-bridge.d.ts +45 -0
  27. package/dist/vendor/adapters/cli-bridge.js +107 -39
  28. package/dist/vendor/adapters/codex-launcher.d.ts +32 -0
  29. package/dist/vendor/adapters/codex-launcher.js +409 -118
  30. package/dist/vendor/adapters/openai-compatible.js +8 -2
  31. package/dist/vendor/adapters/runtime-support.js +1 -0
  32. package/dist/vendor/adapters/stub-direct-provider.js +3 -0
  33. package/dist/vendor/adapters/verifier-only.d.ts +2 -0
  34. package/dist/vendor/adapters/verifier-only.js +9 -3
  35. package/dist/vendor/contracts/index.d.ts +2 -1
  36. package/dist/vendor/contracts/index.js +14 -0
  37. package/dist/vendor/core/context-integrity.js +28 -3
  38. package/dist/vendor/core/grounding.d.ts +1 -0
  39. package/dist/vendor/core/grounding.js +6 -2
  40. package/dist/vendor/core/index.d.ts +1 -0
  41. package/dist/vendor/core/index.js +25 -6
  42. package/dist/vendor/core/leash.js +90 -8
  43. package/dist/vendor/core/persistence/integrity.d.ts +1 -1
  44. package/dist/vendor/core/persistence/integrity.js +15 -6
  45. package/dist/workflow-state.d.ts +9 -0
  46. package/dist/workflow-state.js +44 -3
  47. package/package.json +2 -2
  48. package/server.json +2 -2
package/README.md CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  Governed MCP server for AI coding agents over local stdio.
4
4
 
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.
5
+ `@martinloop/mcp@0.3.2` is the live public baseline today. `0.3.3` is the next planned public cut.
6
6
 
7
7
  This package stays local-first and stdio-first in the public OSS lane.
8
8
 
9
9
  ## Public release train
10
10
 
11
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.
12
+ - `0.3.0` is the live adoption baseline that made host setup and onboarding clearer.
13
+ - `0.3.1` is the live review and handoff release.
14
+ - `0.3.2` is the live engine-validation hotfix that fixes spend-limit requests for Gemini-backed runs.
15
+ - `0.3.3` is the planned follow-on for opt-in execution controls.
15
16
 
16
17
  ## What ships today
17
18
 
@@ -1 +1 @@
1
- export declare const MARTIN_MCP_PACKAGE_VERSION = "0.3.0";
1
+ export declare const MARTIN_MCP_PACKAGE_VERSION = "0.3.2";
@@ -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.3.0";
3
+ export const MARTIN_MCP_PACKAGE_VERSION = "0.3.2";
@@ -156,7 +156,7 @@ function validateRunInput(args) {
156
156
  "workspaceId",
157
157
  "projectId"
158
158
  ]);
159
- const engine = optionalEnum(record.engine, "engine", ["claude", "codex"]);
159
+ const engine = optionalEnum(record.engine, "engine", ["claude", "codex", "gemini"]);
160
160
  return {
161
161
  objective: requireString(record.objective, "objective"),
162
162
  ...(record.workingDirectory !== undefined
@@ -246,7 +246,7 @@ function validateDoctorInput(args) {
246
246
  ...(record.runsDir !== undefined
247
247
  ? { runsDir: resolveSafeRunsRootPath(requireString(record.runsDir, "runsDir")) }
248
248
  : {}),
249
- ...optionalEnumAsObject(record.engine, "engine", ["claude", "codex"])
249
+ ...optionalEnumAsObject(record.engine, "engine", ["claude", "codex", "gemini"])
250
250
  };
251
251
  }
252
252
  function validatePreflightInput(args) {
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" }
@@ -142,6 +143,17 @@ const verificationSchema = {
142
143
  },
143
144
  required: ["status", "eventCount", "ledgerEventCount", "warnings"]
144
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
+ };
145
157
  const artifactSummarySchema = {
146
158
  type: "object",
147
159
  additionalProperties: true,
@@ -391,6 +403,7 @@ const doctorOutputSchema = {
391
403
  },
392
404
  required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
393
405
  },
406
+ receiptScope: receiptScopeSchema,
394
407
  engines: {
395
408
  type: "object",
396
409
  additionalProperties: true
@@ -408,7 +421,7 @@ const doctorOutputSchema = {
408
421
  },
409
422
  warnings: stringArraySchema
410
423
  },
411
- required: ["status", "summary", "server", "environment", "engines", "runStore", "warnings"]
424
+ required: ["status", "summary", "server", "environment", "receiptScope", "engines", "runStore", "warnings"]
412
425
  };
413
426
  const preflightOutputSchema = {
414
427
  type: "object",
@@ -417,6 +430,7 @@ const preflightOutputSchema = {
417
430
  ok: { type: "boolean" },
418
431
  summary: { type: "string" },
419
432
  warnings: stringArraySchema,
433
+ receiptScope: receiptScopeSchema,
420
434
  readiness: {
421
435
  type: "object",
422
436
  additionalProperties: false,
@@ -492,7 +506,7 @@ const preflightOutputSchema = {
492
506
  required: ["requestedEngine", "engineAvailability", "runsRoot", "pathScope", "expectedRunLayout"]
493
507
  }
494
508
  },
495
- required: ["ok", "summary", "warnings", "readiness", "normalized", "execution"]
509
+ required: ["ok", "summary", "warnings", "receiptScope", "readiness", "normalized", "execution"]
496
510
  };
497
511
  const listRunsOutputSchema = {
498
512
  type: "object",
@@ -725,7 +739,7 @@ export function createMartinMcpServer(serverInfo) {
725
739
  },
726
740
  engine: {
727
741
  type: "string",
728
- enum: ["claude", "codex"],
742
+ enum: ["claude", "codex", "gemini"],
729
743
  description: "Which agent CLI to use. Defaults to claude."
730
744
  },
731
745
  model: {
@@ -857,7 +871,7 @@ export function createMartinMcpServer(serverInfo) {
857
871
  },
858
872
  engine: {
859
873
  type: "string",
860
- enum: ["claude", "codex"],
874
+ enum: ["claude", "codex", "gemini"],
861
875
  description: "Optional engine to highlight in diagnostics."
862
876
  }
863
877
  }
@@ -920,7 +934,7 @@ export function createMartinMcpServer(serverInfo) {
920
934
  },
921
935
  engine: {
922
936
  type: "string",
923
- enum: ["claude", "codex"],
937
+ enum: ["claude", "codex", "gemini"],
924
938
  description: "Which agent CLI would be used. Defaults to claude."
925
939
  },
926
940
  model: {
@@ -1397,6 +1411,36 @@ export function createMartinMcpServer(serverInfo) {
1397
1411
  try {
1398
1412
  if (name === "martin_run") {
1399
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
+ }
1400
1444
  const output = await runLoopTool(input);
1401
1445
  return createToolSuccessResult(output, `Run ${output.loopId} is ${output.status}/${output.lifecycleState} after ${output.attempts} attempt(s); spend ${output.costUsd.toFixed(2)} USD.`);
1402
1446
  }
@@ -1417,7 +1461,8 @@ export function createMartinMcpServer(serverInfo) {
1417
1461
  runsRoot: output.environment.runsRoot,
1418
1462
  step: "doctor",
1419
1463
  workingDirectory: output.environment.workingDirectory,
1420
- engine: input.engine
1464
+ engine: input.engine,
1465
+ receiptScope: output.receiptScope
1421
1466
  }).catch(() => { });
1422
1467
  return createToolSuccessResult(output, output.summary);
1423
1468
  }
@@ -1428,7 +1473,13 @@ export function createMartinMcpServer(serverInfo) {
1428
1473
  runsRoot: resolveRunsRoot(process.env),
1429
1474
  step: "plan",
1430
1475
  workingDirectory: output.workingDirectory,
1431
- 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
+ }
1432
1483
  }).catch(() => { });
1433
1484
  return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1434
1485
  }
@@ -1442,7 +1493,11 @@ export function createMartinMcpServer(serverInfo) {
1442
1493
  workingDirectory: output.normalized.workingDirectory,
1443
1494
  objective: output.normalized.objective,
1444
1495
  engine: output.normalized.engine,
1445
- 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
1446
1501
  }).catch(() => { });
1447
1502
  }
1448
1503
  return createToolSuccessResult(output, output.summary);
@@ -1532,6 +1587,13 @@ export function createMartinMcpServer(serverInfo) {
1532
1587
  });
1533
1588
  return server;
1534
1589
  }
1590
+ function normalizeRunBudget(input) {
1591
+ return normalizeLoopBudget({
1592
+ maxUsd: input.maxUsd,
1593
+ maxIterations: input.maxIterations,
1594
+ maxTokens: input.maxTokens
1595
+ });
1596
+ }
1535
1597
  export async function connectMartinMcpStdioServer() {
1536
1598
  const server = createMartinMcpServer();
1537
1599
  const transport = new StdioServerTransport();
@@ -27,14 +27,41 @@ export interface MartinDoctorOutput {
27
27
  repoRoot: string;
28
28
  runsRoot: string;
29
29
  };
30
+ receiptScope: {
31
+ invocationRoot: string;
32
+ workingDirectory: string;
33
+ repoRoot: string;
34
+ runsRoot: string;
35
+ };
30
36
  engines: Record<MartinEngine, {
31
37
  available: boolean;
32
38
  detail: string;
33
39
  resolvedPath?: string;
40
+ candidatePaths?: string[];
41
+ selectedPath?: string;
34
42
  hostPlatform?: CodexHostPlatform;
43
+ installKind?: string;
35
44
  nativeInstallValid?: boolean;
45
+ invocationMode?: string;
46
+ sandboxMode?: string;
47
+ sandboxCompatible?: boolean;
48
+ nativeDependencyStatus?: string;
49
+ nativeDependencyPackage?: string;
36
50
  launchReady?: boolean;
37
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
+ }>;
38
65
  }>;
39
66
  requestedEngine?: MartinEngine;
40
67
  runStore: {
@@ -1,26 +1,40 @@
1
1
  import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
2
2
  import { resolveRunsRoot } from "../vendor/core/index.js";
3
3
  import { resolveSafeRepoRoot, resolveSafeRunsRootPath } from "../server-validation.js";
4
- import { getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
4
+ import { createSkippedCliAvailability, getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
5
5
  import { buildReadinessReport, inspectRepoSignals } from "./workflow-governance.js";
6
6
  export async function martinDoctorTool(input) {
7
7
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
8
8
  const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
9
9
  const workspaceRoot = resolveSafeRepoRoot();
10
10
  const executionMode = resolveExecutionMode();
11
- const claude = getEngineAvailability("claude");
12
- const codex = resolveCliCommandAvailability("codex");
13
- const gemini = getEngineAvailability("gemini");
14
- const codexProbe = executionMode.liveMode && codex.available
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
15
21
  ? probeCodexLaunch({
16
22
  workingDirectory,
17
23
  availability: codex
18
24
  })
19
25
  : undefined;
20
26
  const runStore = await inspectRunsRoot(runsRoot);
21
- const signals = inspectRepoSignals(workingDirectory);
27
+ const signals = inspectRepoSignals(workingDirectory, {
28
+ includeHostAvailability: executionMode.liveMode
29
+ });
22
30
  const readiness = buildReadinessReport(signals, runStore);
23
31
  const warnings = [];
32
+ const receiptScope = {
33
+ invocationRoot: workspaceRoot,
34
+ workingDirectory,
35
+ repoRoot: workingDirectory,
36
+ runsRoot
37
+ };
24
38
  if (!runStore.exists) {
25
39
  warnings.push("Configured Martin runs root does not exist yet.");
26
40
  }
@@ -57,11 +71,9 @@ export async function martinDoctorTool(input) {
57
71
  liveMode: executionMode.liveMode
58
72
  },
59
73
  scope: {
60
- invocationRoot: workspaceRoot,
61
- workingDirectory,
62
- repoRoot: workingDirectory,
63
- runsRoot
74
+ ...receiptScope
64
75
  },
76
+ receiptScope,
65
77
  engines: {
66
78
  claude: {
67
79
  available: claude.available,
@@ -72,12 +84,28 @@ export async function martinDoctorTool(input) {
72
84
  available: codex.available,
73
85
  detail: codex.detail,
74
86
  ...(codex.resolvedPath ? { resolvedPath: codex.resolvedPath } : {}),
87
+ ...(codex.candidatePaths?.length ? { candidatePaths: codex.candidatePaths } : {}),
75
88
  ...(codexProbe
76
89
  ? {
90
+ selectedPath: codexProbe.command,
77
91
  hostPlatform: codexProbe.diagnosis.hostPlatform,
92
+ installKind: codexProbe.diagnosis.installKind,
78
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
+ : {}),
79
103
  launchReady: codexProbe.ok,
80
- probeSummary: codexProbe.summary
104
+ probeSummary: codexProbe.summary,
105
+ ...(codexProbe.diagnosis.remediation ? { remediation: codexProbe.diagnosis.remediation } : {}),
106
+ ...(codexProbe.candidateProbeResults?.length
107
+ ? { candidateProbeResults: codexProbe.candidateProbeResults }
108
+ : {})
81
109
  }
82
110
  : {})
83
111
  },
@@ -1,5 +1,5 @@
1
1
  import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
- import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
+ import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
3
3
  export interface MartinGetRunInput {
4
4
  file?: string;
5
5
  loopId?: string;
@@ -14,6 +14,7 @@ export interface MartinGetRunOutput {
14
14
  cost: ReturnType<typeof buildCostSnapshot>;
15
15
  verification: ReturnType<typeof buildVerificationSummary>;
16
16
  receiptIntegrity: ReceiptIntegritySummary;
17
+ receiptScope?: ReceiptScope;
17
18
  artifacts: ReturnType<typeof buildArtifactSummary>;
18
19
  inspection: {
19
20
  runsRoot: string;
@@ -12,6 +12,7 @@ export async function martinGetRunTool(input) {
12
12
  cost: buildCostSnapshot(detail.loop.cost),
13
13
  verification,
14
14
  receiptIntegrity: resolveReceiptIntegrity(detail.loop),
15
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
15
16
  artifacts: buildArtifactSummary(detail.loop),
16
17
  inspection: {
17
18
  runsRoot: detail.runsRoot,
@@ -1,5 +1,5 @@
1
1
  import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
- import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
2
+ import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
3
3
  export interface MartinGetVerificationResultsInput {
4
4
  file?: string;
5
5
  loopId?: string;
@@ -11,6 +11,7 @@ export interface MartinGetVerificationResultsOutput {
11
11
  loop: ReturnType<typeof buildLoopPreview>;
12
12
  verification: ReturnType<typeof buildVerificationSummary>;
13
13
  receiptIntegrity: ReceiptIntegritySummary;
14
+ receiptScope?: ReceiptScope;
14
15
  warnings: string[];
15
16
  }
16
17
  export declare function martinGetVerificationResultsTool(input: MartinGetVerificationResultsInput): Promise<MartinGetVerificationResultsOutput>;
@@ -10,6 +10,7 @@ export async function martinGetVerificationResultsTool(input) {
10
10
  loop: buildLoopPreview(detail.loop),
11
11
  verification,
12
12
  receiptIntegrity: resolveReceiptIntegrity(detail.loop),
13
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
13
14
  warnings: [...detail.warnings, ...verification.warnings]
14
15
  };
15
16
  }
@@ -1,8 +1,10 @@
1
1
  import { resolveSafeRepoRoot } from "../server-validation.js";
2
- import { buildPlanProposal } from "./workflow-governance.js";
2
+ import { buildPlanProposal, inspectRepoSignals } from "./workflow-governance.js";
3
3
  export async function martinPlanTool(input) {
4
4
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
5
- const proposal = buildPlanProposal(workingDirectory, input);
5
+ const proposal = buildPlanProposal(workingDirectory, input, {
6
+ signals: inspectRepoSignals(workingDirectory, { includeHostAvailability: false })
7
+ });
6
8
  return {
7
9
  workingDirectory,
8
10
  ...proposal
@@ -24,6 +24,12 @@ export interface MartinPreflightOutput {
24
24
  ok: boolean;
25
25
  summary: string;
26
26
  warnings: string[];
27
+ receiptScope: {
28
+ invocationRoot: string;
29
+ workingDirectory: string;
30
+ repoRoot: string;
31
+ runsRoot: string;
32
+ };
27
33
  scope: {
28
34
  invocationRoot: string;
29
35
  workingDirectory: string;
@@ -58,12 +64,33 @@ export interface MartinPreflightOutput {
58
64
  available: boolean;
59
65
  detail: string;
60
66
  resolvedPath?: string;
67
+ candidatePaths?: string[];
61
68
  };
62
69
  codexDiagnostics?: {
70
+ selectedPath?: string;
63
71
  hostPlatform: CodexHostPlatform;
72
+ installKind: string;
64
73
  nativeInstallValid: boolean;
74
+ invocationMode: string;
75
+ sandboxMode: string;
76
+ sandboxCompatible: boolean;
77
+ nativeDependencyStatus?: string;
78
+ nativeDependencyPackage?: string;
65
79
  launchReady: boolean;
66
80
  summary: string;
81
+ remediation?: string;
82
+ candidateProbeResults?: Array<{
83
+ path: string;
84
+ installKind: string;
85
+ invocationMode: string;
86
+ nativeInstallValid: boolean;
87
+ sandboxCompatible: boolean;
88
+ launchReady: boolean;
89
+ summary: string;
90
+ remediation?: string;
91
+ nativeDependencyStatus?: string;
92
+ nativeDependencyPackage?: string;
93
+ }>;
67
94
  };
68
95
  runsRoot: string;
69
96
  pathScope: {
@@ -1,16 +1,21 @@
1
1
  import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
2
- import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
3
2
  import { resolveRunsRoot } from "../vendor/core/index.js";
4
3
  import { resolveSafeRepoRoot } from "../server-validation.js";
5
- import { formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
6
- import { buildPlanProposal, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
4
+ import { createSkippedCliAvailability, formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
5
+ import { buildPlanProposal, normalizeLoopBudget, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
7
6
  export async function martinPreflightTool(input) {
8
7
  const executionMode = resolveExecutionMode();
9
8
  const workspaceRoot = resolveSafeRepoRoot();
10
9
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
11
- const signals = inspectRepoSignals(workingDirectory);
10
+ const signals = inspectRepoSignals(workingDirectory, {
11
+ includeHostAvailability: executionMode.liveMode
12
+ });
12
13
  const engine = input.engine ?? "claude";
13
- const engineAvailability = engine === "codex" ? resolveCliCommandAvailability("codex") : getEngineAvailability(engine);
14
+ const engineAvailability = executionMode.liveMode
15
+ ? engine === "codex"
16
+ ? resolveCliCommandAvailability("codex")
17
+ : getEngineAvailability(engine)
18
+ : createSkippedCliAvailability(engine);
14
19
  const codexProbe = executionMode.liveMode && engine === "codex" && engineAvailability.available
15
20
  ? probeCodexLaunch({
16
21
  workingDirectory,
@@ -21,12 +26,11 @@ export async function martinPreflightTool(input) {
21
26
  const allowedPaths = input.allowedPaths ?? [];
22
27
  const deniedPaths = input.deniedPaths ?? [];
23
28
  const overlappingScopes = allowedPaths.filter((candidate) => deniedPaths.includes(candidate));
24
- const budget = {
25
- ...DEFAULT_BUDGET,
26
- ...(input.maxUsd !== undefined ? { maxUsd: input.maxUsd } : {}),
27
- ...(input.maxIterations !== undefined ? { maxIterations: input.maxIterations } : {}),
28
- ...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
29
- };
29
+ const budget = normalizeLoopBudget({
30
+ maxUsd: input.maxUsd,
31
+ maxIterations: input.maxIterations,
32
+ maxTokens: input.maxTokens
33
+ });
30
34
  if (!executionMode.liveMode) {
31
35
  warnings.push("Proof mode is active; preflight only proves configuration shape, not live CLI readiness.");
32
36
  }
@@ -45,12 +49,18 @@ export async function martinPreflightTool(input) {
45
49
  if (overlappingScopes.length > 0) {
46
50
  warnings.push(`Some path patterns appear in both allowedPaths and deniedPaths: ${overlappingScopes.join(", ")}.`);
47
51
  }
48
- const plan = buildPlanProposal(workingDirectory, input);
49
- const runContract = buildRunContract(workingDirectory, input);
52
+ const plan = buildPlanProposal(workingDirectory, input, { signals });
53
+ const runContract = buildRunContract(workingDirectory, input, { signals, plan });
50
54
  const policy = buildPolicyPackDefinition(input.policyPack, signals);
51
55
  const engineReady = !executionMode.liveMode ||
52
56
  (engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
53
57
  const ok = engineReady;
58
+ const receiptScope = {
59
+ invocationRoot: workspaceRoot,
60
+ workingDirectory,
61
+ repoRoot: workingDirectory,
62
+ runsRoot: resolveRunsRoot(process.env)
63
+ };
54
64
  return {
55
65
  ok,
56
66
  summary: ok
@@ -59,11 +69,9 @@ export async function martinPreflightTool(input) {
59
69
  ? codexProbe.summary
60
70
  : `${engine} is not available for live execution.`}`,
61
71
  warnings,
72
+ receiptScope,
62
73
  scope: {
63
- invocationRoot: workspaceRoot,
64
- workingDirectory,
65
- repoRoot: workingDirectory,
66
- runsRoot: resolveRunsRoot(process.env)
74
+ ...receiptScope
67
75
  },
68
76
  readiness: {
69
77
  mode: executionMode.mode,
@@ -87,17 +95,33 @@ export async function martinPreflightTool(input) {
87
95
  engineAvailability: {
88
96
  available: engineAvailability.available,
89
97
  detail: engineAvailability.detail,
90
- ...(engineAvailability.resolvedPath
91
- ? { resolvedPath: engineAvailability.resolvedPath }
98
+ ...(engineAvailability.resolvedPath ? { resolvedPath: engineAvailability.resolvedPath } : {}),
99
+ ...(engineAvailability.candidatePaths?.length
100
+ ? { candidatePaths: engineAvailability.candidatePaths }
92
101
  : {})
93
102
  },
94
103
  ...(codexProbe
95
104
  ? {
96
105
  codexDiagnostics: {
106
+ selectedPath: codexProbe.command,
97
107
  hostPlatform: codexProbe.diagnosis.hostPlatform,
108
+ installKind: codexProbe.diagnosis.installKind,
98
109
  nativeInstallValid: codexProbe.diagnosis.nativeInstallValid,
110
+ invocationMode: codexProbe.diagnosis.invocationMode,
111
+ sandboxMode: codexProbe.diagnosis.sandboxMode,
112
+ sandboxCompatible: codexProbe.diagnosis.sandboxCompatible,
113
+ ...(codexProbe.diagnosis.nativeDependencyStatus
114
+ ? { nativeDependencyStatus: codexProbe.diagnosis.nativeDependencyStatus }
115
+ : {}),
116
+ ...(codexProbe.diagnosis.nativeDependencyPackage
117
+ ? { nativeDependencyPackage: codexProbe.diagnosis.nativeDependencyPackage }
118
+ : {}),
99
119
  launchReady: codexProbe.ok,
100
- summary: codexProbe.summary
120
+ summary: codexProbe.summary,
121
+ ...(codexProbe.diagnosis.remediation ? { remediation: codexProbe.diagnosis.remediation } : {}),
122
+ ...(codexProbe.candidateProbeResults?.length
123
+ ? { candidateProbeResults: codexProbe.candidateProbeResults }
124
+ : {})
101
125
  }
102
126
  }
103
127
  : {}),
@@ -2,7 +2,7 @@ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildEven
2
2
  import { readRunControlState } from "./run-controls.js";
3
3
  import { martinEvalTool } from "./eval.js";
4
4
  import { assessRunRisk } from "./workflow-governance.js";
5
- import type { ReceiptIntegritySummary } from "../vendor/contracts/index.js";
5
+ import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
6
6
  export interface MartinRunDossierInput {
7
7
  file?: string;
8
8
  loopId?: string;
@@ -17,6 +17,7 @@ export interface MartinRunDossierOutput {
17
17
  budget: ReturnType<typeof buildBudgetSnapshot>;
18
18
  cost: ReturnType<typeof buildCostSnapshot>;
19
19
  receiptIntegrity: ReceiptIntegritySummary;
20
+ receiptScope?: ReceiptScope;
20
21
  attempts: Array<{
21
22
  index: number;
22
23
  attemptId?: string;
@@ -53,6 +53,7 @@ export async function martinRunDossierTool(input) {
53
53
  budget: buildBudgetSnapshot(detail.loop.budget),
54
54
  cost: buildCostSnapshot(detail.loop.cost),
55
55
  receiptIntegrity: resolveReceiptIntegrity(detail.loop),
56
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
56
57
  attempts,
57
58
  verification,
58
59
  artifacts: buildArtifactSummary(detail.loop),
@@ -1,4 +1,6 @@
1
- import { type LoopBudget, type ReceiptScope } from "../vendor/contracts/index.js";
1
+ import { type SpawnLike } from "../vendor/adapters/index.js";
2
+ import { type RunStore } from "../vendor/core/index.js";
3
+ import type { LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
2
4
  import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
3
5
  export interface RunLoopInput {
4
6
  objective: string;
@@ -41,4 +43,6 @@ export interface RunLoopOutput {
41
43
  artifacts: ReturnType<typeof buildArtifactSummary>;
42
44
  };
43
45
  }
46
+ export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
47
+ export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
44
48
  export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;