@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
@@ -1,4 +1,5 @@
1
1
  import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetRunInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -12,6 +13,8 @@ export interface MartinGetRunOutput {
12
13
  budget: ReturnType<typeof buildBudgetSnapshot>;
13
14
  cost: ReturnType<typeof buildCostSnapshot>;
14
15
  verification: ReturnType<typeof buildVerificationSummary>;
16
+ receiptIntegrity: ReceiptIntegritySummary;
17
+ receiptScope?: ReceiptScope;
15
18
  artifacts: ReturnType<typeof buildArtifactSummary>;
16
19
  inspection: {
17
20
  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,8 @@ 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),
15
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
14
16
  artifacts: buildArtifactSummary(detail.loop),
15
17
  inspection: {
16
18
  runsRoot: detail.runsRoot,
@@ -1,4 +1,5 @@
1
1
  import { buildLoopPreview, buildVerificationSummary } from "./tool-support.js";
2
+ import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
2
3
  export interface MartinGetVerificationResultsInput {
3
4
  file?: string;
4
5
  loopId?: string;
@@ -9,6 +10,8 @@ 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;
14
+ receiptScope?: ReceiptScope;
12
15
  warnings: string[];
13
16
  }
14
17
  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,8 @@ export async function martinGetVerificationResultsTool(input) {
9
9
  sourceKind: detail.sourceKind,
10
10
  loop: buildLoopPreview(detail.loop),
11
11
  verification,
12
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
13
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
12
14
  warnings: [...detail.warnings, ...verification.warnings]
13
15
  };
14
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
@@ -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,20 @@ export interface MartinPreflightOutput {
23
24
  ok: boolean;
24
25
  summary: string;
25
26
  warnings: string[];
27
+ receiptScope: {
28
+ invocationRoot: string;
29
+ workingDirectory: string;
30
+ repoRoot: string;
31
+ runsRoot: string;
32
+ };
33
+ scope: {
34
+ invocationRoot: string;
35
+ workingDirectory: string;
36
+ repoRoot: string;
37
+ runsRoot: string;
38
+ };
26
39
  readiness: {
27
- mode: "live" | "stub";
40
+ mode: "live" | "proof";
28
41
  liveMode: boolean;
29
42
  engineReady: boolean;
30
43
  };
@@ -51,6 +64,33 @@ export interface MartinPreflightOutput {
51
64
  available: boolean;
52
65
  detail: string;
53
66
  resolvedPath?: string;
67
+ candidatePaths?: string[];
68
+ };
69
+ codexDiagnostics?: {
70
+ selectedPath?: string;
71
+ hostPlatform: CodexHostPlatform;
72
+ installKind: string;
73
+ nativeInstallValid: boolean;
74
+ invocationMode: string;
75
+ sandboxMode: string;
76
+ sandboxCompatible: boolean;
77
+ nativeDependencyStatus?: string;
78
+ nativeDependencyPackage?: string;
79
+ launchReady: boolean;
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
+ }>;
54
94
  };
55
95
  runsRoot: string;
56
96
  pathScope: {
@@ -1,30 +1,45 @@
1
- import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
1
+ import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
2
2
  import { resolveRunsRoot } from "../vendor/core/index.js";
3
3
  import { resolveSafeRepoRoot } from "../server-validation.js";
4
- import { formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
5
- 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";
6
6
  export async function martinPreflightTool(input) {
7
7
  const executionMode = resolveExecutionMode();
8
+ const workspaceRoot = resolveSafeRepoRoot();
8
9
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
9
- const signals = inspectRepoSignals(workingDirectory);
10
+ const signals = inspectRepoSignals(workingDirectory, {
11
+ includeHostAvailability: executionMode.liveMode
12
+ });
10
13
  const engine = input.engine ?? "claude";
11
- const engineAvailability = getEngineAvailability(engine);
14
+ const engineAvailability = executionMode.liveMode
15
+ ? engine === "codex"
16
+ ? resolveCliCommandAvailability("codex")
17
+ : getEngineAvailability(engine)
18
+ : createSkippedCliAvailability(engine);
19
+ const codexProbe = executionMode.liveMode && engine === "codex" && engineAvailability.available
20
+ ? probeCodexLaunch({
21
+ workingDirectory,
22
+ availability: engineAvailability
23
+ })
24
+ : undefined;
12
25
  const warnings = [];
13
26
  const allowedPaths = input.allowedPaths ?? [];
14
27
  const deniedPaths = input.deniedPaths ?? [];
15
28
  const overlappingScopes = allowedPaths.filter((candidate) => deniedPaths.includes(candidate));
16
- const budget = {
17
- ...DEFAULT_BUDGET,
18
- ...(input.maxUsd !== undefined ? { maxUsd: input.maxUsd } : {}),
19
- ...(input.maxIterations !== undefined ? { maxIterations: input.maxIterations } : {}),
20
- ...(input.maxTokens !== undefined ? { maxTokens: input.maxTokens } : {})
21
- };
29
+ const budget = normalizeLoopBudget({
30
+ maxUsd: input.maxUsd,
31
+ maxIterations: input.maxIterations,
32
+ maxTokens: input.maxTokens
33
+ });
22
34
  if (!executionMode.liveMode) {
23
- warnings.push("Stub mode is active; preflight only proves configuration shape, not live CLI readiness.");
35
+ warnings.push("Proof mode is active; preflight only proves configuration shape, not live CLI readiness.");
24
36
  }
25
37
  else if (!engineAvailability.available) {
26
38
  warnings.push(`Requested engine '${engine}' is not available on PATH.`);
27
39
  }
40
+ else if (engine === "codex" && codexProbe && !codexProbe.ok) {
41
+ warnings.push(codexProbe.summary);
42
+ }
28
43
  if ((input.verificationPlan?.length ?? 0) === 0) {
29
44
  warnings.push("No verificationPlan was provided; Martin can run, but completion confidence will be lower.");
30
45
  }
@@ -34,20 +49,34 @@ export async function martinPreflightTool(input) {
34
49
  if (overlappingScopes.length > 0) {
35
50
  warnings.push(`Some path patterns appear in both allowedPaths and deniedPaths: ${overlappingScopes.join(", ")}.`);
36
51
  }
37
- const plan = buildPlanProposal(workingDirectory, input);
38
- const runContract = buildRunContract(workingDirectory, input);
52
+ const plan = buildPlanProposal(workingDirectory, input, { signals });
53
+ const runContract = buildRunContract(workingDirectory, input, { signals, plan });
39
54
  const policy = buildPolicyPackDefinition(input.policyPack, signals);
40
- const ok = !executionMode.liveMode || engineAvailability.available;
55
+ const engineReady = !executionMode.liveMode ||
56
+ (engineAvailability.available && (engine !== "codex" || codexProbe?.ok !== false));
57
+ const ok = engineReady;
58
+ const receiptScope = {
59
+ invocationRoot: workspaceRoot,
60
+ workingDirectory,
61
+ repoRoot: workingDirectory,
62
+ runsRoot: resolveRunsRoot(process.env)
63
+ };
41
64
  return {
42
65
  ok,
43
66
  summary: ok
44
67
  ? `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.`,
68
+ : `Preflight blocked: ${engine === "codex" && codexProbe && !codexProbe.ok
69
+ ? codexProbe.summary
70
+ : `${engine} is not available for live execution.`}`,
46
71
  warnings,
72
+ receiptScope,
73
+ scope: {
74
+ ...receiptScope
75
+ },
47
76
  readiness: {
48
77
  mode: executionMode.mode,
49
78
  liveMode: executionMode.liveMode,
50
- engineReady: !executionMode.liveMode || engineAvailability.available
79
+ engineReady
51
80
  },
52
81
  normalized: {
53
82
  objective: input.objective,
@@ -66,10 +95,36 @@ export async function martinPreflightTool(input) {
66
95
  engineAvailability: {
67
96
  available: engineAvailability.available,
68
97
  detail: engineAvailability.detail,
69
- ...(engineAvailability.resolvedPath
70
- ? { resolvedPath: engineAvailability.resolvedPath }
98
+ ...(engineAvailability.resolvedPath ? { resolvedPath: engineAvailability.resolvedPath } : {}),
99
+ ...(engineAvailability.candidatePaths?.length
100
+ ? { candidatePaths: engineAvailability.candidatePaths }
71
101
  : {})
72
102
  },
103
+ ...(codexProbe
104
+ ? {
105
+ codexDiagnostics: {
106
+ selectedPath: codexProbe.command,
107
+ hostPlatform: codexProbe.diagnosis.hostPlatform,
108
+ installKind: codexProbe.diagnosis.installKind,
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
+ : {}),
119
+ launchReady: codexProbe.ok,
120
+ summary: codexProbe.summary,
121
+ ...(codexProbe.diagnosis.remediation ? { remediation: codexProbe.diagnosis.remediation } : {}),
122
+ ...(codexProbe.candidateProbeResults?.length
123
+ ? { candidateProbeResults: codexProbe.candidateProbeResults }
124
+ : {})
125
+ }
126
+ }
127
+ : {}),
73
128
  runsRoot: resolveRunsRoot(process.env),
74
129
  pathScope: {
75
130
  repoRoot: workingDirectory,
@@ -2,6 +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, ReceiptScope } from "../vendor/contracts/index.js";
5
6
  export interface MartinRunDossierInput {
6
7
  file?: string;
7
8
  loopId?: string;
@@ -15,6 +16,8 @@ export interface MartinRunDossierOutput {
15
16
  loop: ReturnType<typeof buildLoopPreview>;
16
17
  budget: ReturnType<typeof buildBudgetSnapshot>;
17
18
  cost: ReturnType<typeof buildCostSnapshot>;
19
+ receiptIntegrity: ReceiptIntegritySummary;
20
+ receiptScope?: ReceiptScope;
18
21
  attempts: Array<{
19
22
  index: number;
20
23
  attemptId?: string;
@@ -1,4 +1,5 @@
1
- import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildEventSummaries, buildLoopPreview, buildSuggestedPromptNames, buildSuggestedResourceUris, buildVerificationSummary } from "./tool-support.js";
1
+ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildEventSummaries, buildLoopPreview, resolveReceiptIntegrity, buildSuggestedPromptNames, buildSuggestedResourceUris, buildVerificationSummary } from "./tool-support.js";
2
+ import { resolveTrustedLoopRepoRoot } from "../server-validation.js";
2
3
  import { loadDetailedLoopRecord, readAttemptArtifactFiles, readLedgerEvents } from "./run-store.js";
3
4
  import { readRunControlState } from "./run-controls.js";
4
5
  import { martinEvalTool } from "./eval.js";
@@ -9,7 +10,7 @@ export async function martinRunDossierTool(input) {
9
10
  const verification = buildVerificationSummary(detail.loop, ledgerEvents);
10
11
  const control = await readRunControlState(detail);
11
12
  const evaluation = await martinEvalTool(input);
12
- const repoRoot = detail.loop.task?.repoRoot ?? process.cwd();
13
+ const repoRoot = resolveTrustedLoopRepoRoot(detail.loop.task?.repoRoot);
13
14
  const risk = assessRunRisk({
14
15
  objective: detail.loop.task?.objective ?? detail.loop.loopId,
15
16
  allowedPaths: detail.loop.task?.allowedPaths ?? [],
@@ -51,6 +52,8 @@ export async function martinRunDossierTool(input) {
51
52
  loop: buildLoopPreview(detail.loop),
52
53
  budget: buildBudgetSnapshot(detail.loop.budget),
53
54
  cost: buildCostSnapshot(detail.loop.cost),
55
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
56
+ ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
54
57
  attempts,
55
58
  verification,
56
59
  artifacts: buildArtifactSummary(detail.loop),
@@ -1,9 +1,11 @@
1
- import { type LoopBudget } 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;
5
7
  workingDirectory?: string;
6
- engine?: "claude" | "codex";
8
+ engine?: "claude" | "codex" | "gemini";
7
9
  model?: string;
8
10
  maxUsd?: number;
9
11
  maxIterations?: number;
@@ -35,9 +37,12 @@ export interface RunLoopOutput {
35
37
  runDirectory: string;
36
38
  loopRecordPath: string;
37
39
  ledgerPath: string;
40
+ receiptScope: ReceiptScope;
38
41
  loop: ReturnType<typeof buildLoopPreview>;
39
42
  verification: ReturnType<typeof buildVerificationSummary>;
40
43
  artifacts: ReturnType<typeof buildArtifactSummary>;
41
44
  };
42
45
  }
46
+ export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
47
+ export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
43
48
  export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;
@@ -1,10 +1,17 @@
1
- import { createClaudeCliAdapter, createCodexCliAdapter, createStubDirectProviderAdapter } from "../vendor/adapters/index.js";
1
+ import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, probeCodexLaunch, resolveCliCommandAvailability, createVerifierOnlyAdapter } from "../vendor/adapters/index.js";
2
2
  import { createFileRunStore, evaluateCostGovernor, resolveRunsRoot, runMartin } from "../vendor/core/index.js";
3
- import { DEFAULT_BUDGET } from "../vendor/contracts/index.js";
4
3
  import { normalizeSafePathPatterns, resolveSafeRepoRoot } from "../server-validation.js";
5
- import { evaluateMcpRunGate } from "../workflow-state.js";
6
4
  import { MartinToolError } from "./tool-errors.js";
7
5
  import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, buildRunRecordPaths, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
6
+ import { normalizeLoopBudget } from "./workflow-governance.js";
7
+ let proofModeVerifierSpawnImpl;
8
+ let runStoreOverrideForTests;
9
+ export function __setProofModeVerifierSpawnImplForTests(spawnImpl) {
10
+ proofModeVerifierSpawnImpl = spawnImpl;
11
+ }
12
+ export function __setRunStoreOverrideForTests(store) {
13
+ runStoreOverrideForTests = store;
14
+ }
8
15
  export async function runLoopTool(input) {
9
16
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
10
17
  const engine = input.engine ?? "claude";
@@ -12,38 +19,64 @@ export async function runLoopTool(input) {
12
19
  const allowedPaths = normalizeSafePathPatterns(input.allowedPaths, "allowedPaths");
13
20
  const deniedPaths = normalizeSafePathPatterns(input.deniedPaths, "deniedPaths");
14
21
  const executionMode = resolveExecutionMode();
15
- const engineAvailability = getEngineAvailability(engine);
22
+ const workspaceRoot = resolveSafeRepoRoot();
16
23
  const runsRoot = resolveRunsRoot(process.env);
17
- const gate = await evaluateMcpRunGate({
18
- runsRoot,
24
+ const receiptScope = {
25
+ invocationRoot: workspaceRoot,
19
26
  workingDirectory,
20
- objective: input.objective,
21
- engine,
22
- verificationPlan: input.verificationPlan
23
- });
24
- if (!gate.allowed) {
25
- throw new MartinToolError("policy_blocked", gate.summary, {
26
- category: "policy_blocked",
27
- suggestion: gate.nextAction,
28
- retryable: false,
29
- details: {
30
- missingSteps: gate.missingSteps,
31
- nextAction: gate.nextAction
27
+ repoRoot: workingDirectory,
28
+ runsRoot
29
+ };
30
+ let codexCommandOverride;
31
+ if (executionMode.liveMode) {
32
+ if (engine === "codex") {
33
+ const engineAvailability = resolveCliCommandAvailability("codex");
34
+ if (!engineAvailability.available) {
35
+ throw new MartinToolError("engine_unavailable", `Engine '${engine}' is not available on PATH.`, {
36
+ category: "environment",
37
+ suggestion: "Install the requested CLI or set MARTIN_LIVE=false for a no-spend proof run.",
38
+ retryable: false
39
+ });
32
40
  }
33
- });
34
- }
35
- if (executionMode.liveMode && !engineAvailability.available) {
36
- throw new MartinToolError("engine_unavailable", `Engine '${engine}' is not available on PATH.`, {
37
- category: "environment",
38
- suggestion: "Install the requested CLI or set MARTIN_LIVE=false for stub execution.",
39
- retryable: false
40
- });
41
+ const codexProbe = probeCodexLaunch({
42
+ workingDirectory,
43
+ availability: engineAvailability
44
+ });
45
+ if (!codexProbe.ok) {
46
+ throw new MartinToolError("engine_unavailable", codexProbe.summary, {
47
+ category: "environment",
48
+ suggestion: "Run martin_doctor or martin_preflight with engine='codex' before retrying live governed work.",
49
+ retryable: false
50
+ });
51
+ }
52
+ codexCommandOverride = codexProbe.command;
53
+ }
54
+ else {
55
+ const engineAvailability = getEngineAvailability(engine);
56
+ if (!engineAvailability.available) {
57
+ throw new MartinToolError("engine_unavailable", `Engine '${engine}' is not available on PATH.`, {
58
+ category: "environment",
59
+ suggestion: "Install the requested CLI or set MARTIN_LIVE=false for a no-spend proof run.",
60
+ retryable: false
61
+ });
62
+ }
63
+ }
41
64
  }
42
- const adapter = process.env.MARTIN_LIVE === "false"
43
- ? createStubDirectProviderAdapter({ label: "Stub adapter (MARTIN_LIVE=false)", providerId: "stub", model: "stub" })
65
+ const adapter = !executionMode.liveMode
66
+ ? createVerifierOnlyAdapter({
67
+ workingDirectory,
68
+ label: "Proof mode adapter (MARTIN_LIVE=false)",
69
+ ...(proofModeVerifierSpawnImpl ? { spawnImpl: proofModeVerifierSpawnImpl } : {})
70
+ })
44
71
  : engine === "codex"
45
- ? createCodexCliAdapter({ workingDirectory, ...(model ? { model } : {}) })
46
- : createClaudeCliAdapter({ workingDirectory, ...(model ? { model } : {}) });
72
+ ? createCodexCliAdapter({
73
+ workingDirectory,
74
+ ...(model ? { model } : {}),
75
+ ...(codexCommandOverride ? { command: codexCommandOverride } : {})
76
+ })
77
+ : engine === "gemini"
78
+ ? createGeminiCliAdapter({ workingDirectory, ...(model ? { model } : {}) })
79
+ : createClaudeCliAdapter({ workingDirectory, ...(model ? { model } : {}) });
47
80
  const partialBudget = {};
48
81
  if (input.maxUsd !== undefined) {
49
82
  partialBudget.maxUsd = input.maxUsd;
@@ -54,14 +87,12 @@ export async function runLoopTool(input) {
54
87
  if (input.maxTokens !== undefined) {
55
88
  partialBudget.maxTokens = input.maxTokens;
56
89
  }
57
- const budget = {
58
- ...DEFAULT_BUDGET,
59
- ...partialBudget
60
- };
90
+ const budget = normalizeLoopBudget(partialBudget);
61
91
  const result = await runMartin({
62
92
  workspaceId: input.workspaceId ?? "ws_mcp",
63
93
  projectId: input.projectId ?? "proj_mcp",
64
- store: createFileRunStore({ runsRoot }),
94
+ store: runStoreOverrideForTests ?? createFileRunStore({ runsRoot }),
95
+ receiptScope,
65
96
  task: {
66
97
  title: input.objective.slice(0, 100),
67
98
  objective: input.objective,
@@ -106,6 +137,7 @@ export async function runLoopTool(input) {
106
137
  budget,
107
138
  inspection: {
108
139
  ...recordPaths,
140
+ receiptScope: result.loop.receiptScope ?? receiptScope,
109
141
  loop: buildLoopPreview(result.loop),
110
142
  verification,
111
143
  artifacts
@@ -1,8 +1,60 @@
1
1
  import { readFile, readdir, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "../vendor/core/index.js";
3
+ import { readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, verifyReceiptIntegrityFromFiles } from "../vendor/core/index.js";
4
4
  import { resolveSafeLoopRecordPath, resolveSafeRunsJsonPath, resolveSafeRunsPath, resolveSafeRunsRootPath } from "../server-validation.js";
5
5
  import { attemptNotFoundError, invalidSelectorError, noLoopRecordsError, storeUnreadableError } from "./tool-errors.js";
6
+ async function attachReceiptIntegrity(detail) {
7
+ const ledgerPath = detail.canonicalRunDirectory
8
+ ? await resolveReceiptEvidencePath(detail.canonicalRunDirectory)
9
+ : detail.ledgerPath;
10
+ const integrity = detail.canonicalLoopRecordPath && detail.canonicalRunDirectory && ledgerPath
11
+ ? await verifyReceiptIntegrityFromFiles({
12
+ runId: detail.loop.loopId,
13
+ runsRoot: detail.runsRoot,
14
+ loopRecordPath: detail.canonicalLoopRecordPath,
15
+ ledgerPath
16
+ }).catch(() => ({
17
+ state: "unsigned",
18
+ reason: "Receipt integrity verification could not be completed."
19
+ }))
20
+ : ({
21
+ state: "unsigned",
22
+ reason: "Receipt integrity is only available for canonical run directories."
23
+ });
24
+ const receiptScope = resolveReceiptScope(detail.loop, detail.runsRoot);
25
+ return {
26
+ ...detail,
27
+ ...(ledgerPath ? { ledgerPath } : {}),
28
+ loop: {
29
+ ...detail.loop,
30
+ receiptIntegrity: integrity,
31
+ ...(receiptScope ? { receiptScope } : {})
32
+ }
33
+ };
34
+ }
35
+ function resolveReceiptScope(loop, runsRoot) {
36
+ if (loop.receiptScope) {
37
+ return loop.receiptScope;
38
+ }
39
+ if (!loop.task?.repoRoot && !runsRoot) {
40
+ return undefined;
41
+ }
42
+ return {
43
+ ...(loop.task?.repoRoot ? { repoRoot: loop.task.repoRoot } : {}),
44
+ ...(loop.task?.repoRoot ? { workingDirectory: loop.task.repoRoot } : {}),
45
+ ...(runsRoot ? { runsRoot } : {})
46
+ };
47
+ }
48
+ async function resolveReceiptEvidencePath(runDirectory) {
49
+ for (const candidate of ["ledger.jsonl", "events.jsonl"]) {
50
+ const candidatePath = path.join(runDirectory, candidate);
51
+ const candidateStats = await safeStat(candidatePath);
52
+ if (candidateStats?.isFile()) {
53
+ return candidatePath;
54
+ }
55
+ }
56
+ return undefined;
57
+ }
6
58
  export async function loadLoopRecordsForInspect(input) {
7
59
  const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
8
60
  if (!input.file) {
@@ -118,14 +170,14 @@ export async function loadDetailedLoopRecord(input) {
118
170
  const canonicalStats = await safeStat(canonicalLoopRecordPath);
119
171
  if (canonicalStats?.isFile()) {
120
172
  const loop = await readCanonicalLoopRecord(canonicalLoopRecordPath);
121
- return buildDetailedLoopSource({
173
+ return await attachReceiptIntegrity(buildDetailedLoopSource({
122
174
  source: canonicalLoopRecordPath,
123
175
  sourceKind: "file",
124
176
  runsRoot,
125
177
  loop,
126
178
  canonicalLoopRecordPath,
127
179
  canonicalRunDirectory: path.dirname(canonicalLoopRecordPath)
128
- });
180
+ }));
129
181
  }
130
182
  }
131
183
  const inspected = await readAllLoopRecordsSafely(targetPath);
@@ -139,10 +191,10 @@ export async function loadDetailedLoopRecord(input) {
139
191
  runsRoot,
140
192
  loop
141
193
  });
142
- return {
194
+ return await attachReceiptIntegrity({
143
195
  ...detail,
144
196
  warnings: [...detail.warnings, ...inspected.warnings]
145
- };
197
+ });
146
198
  }
147
199
  const latest = await readLatestLoopRecordFromFile(targetPath);
148
200
  if (!latest) {
@@ -150,35 +202,35 @@ export async function loadDetailedLoopRecord(input) {
150
202
  }
151
203
  if (path.basename(targetPath) === "loop-record.json") {
152
204
  const loop = await readCanonicalLoopRecord(targetPath);
153
- return buildDetailedLoopSource({
205
+ return await attachReceiptIntegrity(buildDetailedLoopSource({
154
206
  source: targetPath,
155
207
  sourceKind: "file",
156
208
  runsRoot,
157
209
  loop,
158
210
  canonicalLoopRecordPath: targetPath,
159
211
  canonicalRunDirectory: path.dirname(targetPath)
160
- });
212
+ }));
161
213
  }
162
- return await buildDetailedLoopSourceFromDiscoveredLoop({
214
+ return await attachReceiptIntegrity(await buildDetailedLoopSourceFromDiscoveredLoop({
163
215
  source: targetPath,
164
216
  sourceKind: "file",
165
217
  runsRoot,
166
218
  loop: latest
167
- });
219
+ }));
168
220
  }
169
221
  if (input.loopId) {
170
222
  const canonicalLoopRecordPath = resolvePotentialLoopRecordPath(input.loopId, runsRoot);
171
223
  const canonicalStats = await safeStat(canonicalLoopRecordPath);
172
224
  if (canonicalStats?.isFile()) {
173
225
  const loop = await readCanonicalLoopRecord(canonicalLoopRecordPath);
174
- return buildDetailedLoopSource({
226
+ return await attachReceiptIntegrity(buildDetailedLoopSource({
175
227
  source: canonicalLoopRecordPath,
176
228
  sourceKind: "loop_id",
177
229
  runsRoot,
178
230
  loop,
179
231
  canonicalLoopRecordPath,
180
232
  canonicalRunDirectory: path.dirname(canonicalLoopRecordPath)
181
- });
233
+ }));
182
234
  }
183
235
  const inspected = await readAllLoopRecordsSafely(runsRoot);
184
236
  const loop = inspected.loops.find((candidate) => candidate.loopId === input.loopId);
@@ -191,10 +243,10 @@ export async function loadDetailedLoopRecord(input) {
191
243
  runsRoot,
192
244
  loop
193
245
  });
194
- return {
246
+ return await attachReceiptIntegrity({
195
247
  ...detail,
196
248
  warnings: [...detail.warnings, ...inspected.warnings]
197
- };
249
+ });
198
250
  }
199
251
  const inspected = await readAllLoopRecordsSafely(runsRoot);
200
252
  const loop = inspected.loops[0];
@@ -207,10 +259,10 @@ export async function loadDetailedLoopRecord(input) {
207
259
  runsRoot,
208
260
  loop
209
261
  });
210
- return {
262
+ return await attachReceiptIntegrity({
211
263
  ...detail,
212
264
  warnings: [...detail.warnings, ...inspected.warnings]
213
- };
265
+ });
214
266
  }
215
267
  export async function loadAttemptFromLoop(input) {
216
268
  const detail = await loadDetailedLoopRecord(input);