@martinloop/mcp 0.2.5 → 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 (70) hide show
  1. package/README.md +40 -132
  2. package/dist/discovery-metadata.d.ts +10 -5
  3. package/dist/discovery-metadata.js +95 -5
  4. package/dist/package-version.d.ts +1 -1
  5. package/dist/package-version.js +1 -1
  6. package/dist/prompts.d.ts +1 -1
  7. package/dist/prompts.js +93 -1
  8. package/dist/resources.d.ts +9 -1
  9. package/dist/resources.js +247 -16
  10. package/dist/server-validation.d.ts +2 -1
  11. package/dist/server-validation.js +124 -0
  12. package/dist/server.js +379 -5
  13. package/dist/tools/doctor.d.ts +14 -1
  14. package/dist/tools/doctor.js +43 -8
  15. package/dist/tools/eval.d.ts +24 -0
  16. package/dist/tools/eval.js +66 -0
  17. package/dist/tools/get-run.d.ts +2 -0
  18. package/dist/tools/get-run.js +2 -1
  19. package/dist/tools/get-status.d.ts +8 -0
  20. package/dist/tools/get-status.js +18 -0
  21. package/dist/tools/get-verification-results.d.ts +2 -0
  22. package/dist/tools/get-verification-results.js +2 -1
  23. package/dist/tools/logs.d.ts +25 -0
  24. package/dist/tools/logs.js +49 -0
  25. package/dist/tools/plan.d.ts +20 -0
  26. package/dist/tools/plan.js +10 -0
  27. package/dist/tools/pr-tools.d.ts +31 -0
  28. package/dist/tools/pr-tools.js +112 -0
  29. package/dist/tools/preflight.d.ts +24 -1
  30. package/dist/tools/preflight.js +47 -7
  31. package/dist/tools/run-controls.d.ts +36 -0
  32. package/dist/tools/run-controls.js +88 -0
  33. package/dist/tools/run-dossier.d.ts +16 -0
  34. package/dist/tools/run-dossier.js +64 -2
  35. package/dist/tools/run-loop.d.ts +3 -2
  36. package/dist/tools/run-loop.js +52 -13
  37. package/dist/tools/tool-errors.d.ts +1 -1
  38. package/dist/tools/tool-errors.js +1 -1
  39. package/dist/tools/tool-support.d.ts +6 -3
  40. package/dist/tools/tool-support.js +37 -3
  41. package/dist/tools/workflow-governance.d.ts +133 -0
  42. package/dist/tools/workflow-governance.js +581 -0
  43. package/dist/vendor/adapters/claude-cli.d.ts +25 -0
  44. package/dist/vendor/adapters/claude-cli.js +279 -19
  45. package/dist/vendor/adapters/cli-bridge.d.ts +6 -0
  46. package/dist/vendor/adapters/cli-bridge.js +58 -9
  47. package/dist/vendor/adapters/codex-launcher.d.ts +44 -0
  48. package/dist/vendor/adapters/codex-launcher.js +247 -0
  49. package/dist/vendor/adapters/index.d.ts +4 -2
  50. package/dist/vendor/adapters/index.js +4 -1
  51. package/dist/vendor/adapters/openai-compatible.d.ts +62 -0
  52. package/dist/vendor/adapters/openai-compatible.js +267 -0
  53. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  54. package/dist/vendor/adapters/runtime-support.js +8 -1
  55. package/dist/vendor/adapters/verifier-only.js +4 -3
  56. package/dist/vendor/contracts/index.d.ts +39 -0
  57. package/dist/vendor/contracts/index.js +2 -0
  58. package/dist/vendor/core/index.d.ts +23 -3
  59. package/dist/vendor/core/index.js +88 -15
  60. package/dist/vendor/core/persistence/index.d.ts +2 -0
  61. package/dist/vendor/core/persistence/index.js +1 -0
  62. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  63. package/dist/vendor/core/persistence/integrity.js +239 -0
  64. package/dist/vendor/core/persistence/store.d.ts +7 -0
  65. package/dist/vendor/core/persistence/store.js +25 -1
  66. package/dist/vendor/core/policy.d.ts +9 -0
  67. package/dist/workflow-state.d.ts +25 -0
  68. package/dist/workflow-state.js +102 -0
  69. package/package.json +3 -3
  70. package/server.json +2 -2
package/dist/server.js CHANGED
@@ -19,6 +19,7 @@
19
19
  import { fileURLToPath } from "node:url";
20
20
  import { realpathSync } from "node:fs";
21
21
  import path from "node:path";
22
+ import { resolveRunsRoot } from "./vendor/core/index.js";
22
23
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
23
24
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
24
25
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
@@ -26,19 +27,25 @@ import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
26
27
  import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
27
28
  import { listMartinResources, listMartinResourceTemplates, readMartinResource } from "./resources.js";
28
29
  import { martinDoctorTool } from "./tools/doctor.js";
30
+ import { martinEvalTool } from "./tools/eval.js";
29
31
  import { martinGetAttemptTool } from "./tools/get-attempt.js";
30
32
  import { martinGetRunTool } from "./tools/get-run.js";
31
33
  import { martinGetVerificationResultsTool } from "./tools/get-verification-results.js";
32
34
  import { getStatusTool } from "./tools/get-status.js";
33
35
  import { inspectLoopTool } from "./tools/inspect-loop.js";
34
36
  import { martinListRunsTool } from "./tools/list-runs.js";
37
+ import { martinLogsTool } from "./tools/logs.js";
38
+ import { martinPlanTool } from "./tools/plan.js";
35
39
  import { martinPreflightTool } from "./tools/preflight.js";
40
+ import { martinCreatePrTool, martinPrSummaryTool, martinReviewPrTool } from "./tools/pr-tools.js";
36
41
  import { martinRunDossierTool } from "./tools/run-dossier.js";
42
+ import { createRunControlReceipt } from "./tools/run-controls.js";
37
43
  import { martinTriageRunsTool } from "./tools/triage-runs.js";
38
44
  import { runLoopTool } from "./tools/run-loop.js";
39
45
  import { createToolErrorResult, createToolSuccessResult } from "./tools/tool-response.js";
40
46
  import { MartinToolError, toToolFailure } from "./tools/tool-errors.js";
41
47
  import { sanitizeToolErrorMessage, validateToolInput } from "./server-validation.js";
48
+ import { recordMcpWorkflowStep } from "./workflow-state.js";
42
49
  const stringArraySchema = {
43
50
  type: "array",
44
51
  items: { type: "string" }
@@ -115,6 +122,22 @@ const verificationSchema = {
115
122
  latestAttemptIndex: { type: "integer" },
116
123
  completedAt: { type: "string" },
117
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
+ },
118
141
  warnings: stringArraySchema
119
142
  },
120
143
  required: ["status", "eventCount", "ledgerEventCount", "warnings"]
@@ -363,7 +386,7 @@ const doctorOutputSchema = {
363
386
  workspaceRoot: { type: "string" },
364
387
  workingDirectory: { type: "string" },
365
388
  runsRoot: { type: "string" },
366
- mode: { type: "string", enum: ["live", "stub"] },
389
+ mode: { type: "string", enum: ["live", "proof"] },
367
390
  liveMode: { type: "boolean" }
368
391
  },
369
392
  required: ["workspaceRoot", "workingDirectory", "runsRoot", "mode", "liveMode"]
@@ -398,7 +421,7 @@ const preflightOutputSchema = {
398
421
  type: "object",
399
422
  additionalProperties: false,
400
423
  properties: {
401
- mode: { type: "string", enum: ["live", "stub"] },
424
+ mode: { type: "string", enum: ["live", "proof"] },
402
425
  liveMode: { type: "boolean" },
403
426
  engineReady: { type: "boolean" }
404
427
  },
@@ -649,6 +672,30 @@ const dossierOutputSchema = {
649
672
  "warnings"
650
673
  ]
651
674
  };
675
+ const planOutputSchema = {
676
+ type: "object",
677
+ additionalProperties: true
678
+ };
679
+ const logsOutputSchema = {
680
+ type: "object",
681
+ additionalProperties: true
682
+ };
683
+ const controlOutputSchema = {
684
+ type: "object",
685
+ additionalProperties: true
686
+ };
687
+ const evalOutputSchema = {
688
+ type: "object",
689
+ additionalProperties: true
690
+ };
691
+ const prSummaryOutputSchema = {
692
+ type: "object",
693
+ additionalProperties: true
694
+ };
695
+ const prReviewOutputSchema = {
696
+ type: "object",
697
+ additionalProperties: true
698
+ };
652
699
  export function createMartinMcpServer(serverInfo) {
653
700
  const server = new Server({
654
701
  name: serverInfo?.name ?? "martin-loop",
@@ -658,7 +705,7 @@ export function createMartinMcpServer(serverInfo) {
658
705
  tools: [
659
706
  {
660
707
  name: "martin_run",
661
- description: "Execute a governed Martin Loop run on a coding task and return the run summary, spend, artifact rollup, and verification state.",
708
+ description: "Execute a governed Martin Loop run on a coding task and return the run summary, spend, artifact rollup, and verification state. This hard-blocks until martin_doctor, martin_plan, and martin_preflight receipts exist for the same task.",
662
709
  annotations: {
663
710
  destructiveHint: true,
664
711
  idempotentHint: false,
@@ -791,7 +838,7 @@ export function createMartinMcpServer(serverInfo) {
791
838
  },
792
839
  {
793
840
  name: "martin_doctor",
794
- description: "Read-only environment and run-store diagnostics for the Martin MCP server.",
841
+ description: "Read-only environment and run-store diagnostics for the Martin MCP server. This is the expected first call before governed work begins.",
795
842
  annotations: {
796
843
  readOnlyHint: true,
797
844
  idempotentHint: true
@@ -817,9 +864,44 @@ export function createMartinMcpServer(serverInfo) {
817
864
  },
818
865
  outputSchema: doctorOutputSchema
819
866
  },
867
+ {
868
+ name: "martin_plan",
869
+ description: "Read-only planning step that turns an objective into a scoped implementation plan, verifier proposal, policy pack, and risk recommendation. Use before preflight and before any real coding run.",
870
+ annotations: {
871
+ readOnlyHint: true,
872
+ idempotentHint: true
873
+ },
874
+ inputSchema: {
875
+ type: "object",
876
+ additionalProperties: false,
877
+ properties: {
878
+ objective: { type: "string", description: "The coding objective to plan." },
879
+ workingDirectory: {
880
+ type: "string",
881
+ description: "Optional repo-root override resolved under the MCP workspace root."
882
+ },
883
+ context: { type: "string", description: "Optional extra issue or bug context." },
884
+ policyPack: {
885
+ type: "string",
886
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
887
+ },
888
+ verificationPlan: { type: "array", items: { type: "string" } },
889
+ allowedPaths: { type: "array", items: { type: "string" } },
890
+ deniedPaths: { type: "array", items: { type: "string" } },
891
+ maxUsd: { type: "number", exclusiveMinimum: 0 },
892
+ maxIterations: { type: "integer", exclusiveMinimum: 0 },
893
+ maxTokens: { type: "integer", exclusiveMinimum: 0 },
894
+ maxMinutes: { type: "integer", exclusiveMinimum: 0 },
895
+ maxFilesChanged: { type: "integer", exclusiveMinimum: 0 },
896
+ maxCommands: { type: "integer", exclusiveMinimum: 0 }
897
+ },
898
+ required: ["objective"]
899
+ },
900
+ outputSchema: planOutputSchema
901
+ },
820
902
  {
821
903
  name: "martin_preflight",
822
- description: "Read-only validation of a planned Martin run before any execution or spend.",
904
+ description: "Read-only validation of a planned Martin run before any execution or spend. This is the last required step before martin_run.",
823
905
  annotations: {
824
906
  readOnlyHint: true,
825
907
  idempotentHint: true
@@ -845,6 +927,14 @@ export function createMartinMcpServer(serverInfo) {
845
927
  type: "string",
846
928
  description: "Model override passed to the CLI."
847
929
  },
930
+ context: {
931
+ type: "string",
932
+ description: "Optional issue context carried into the run contract."
933
+ },
934
+ policyPack: {
935
+ type: "string",
936
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
937
+ },
848
938
  maxUsd: {
849
939
  type: "number",
850
940
  exclusiveMinimum: 0,
@@ -860,6 +950,21 @@ export function createMartinMcpServer(serverInfo) {
860
950
  exclusiveMinimum: 0,
861
951
  description: "Maximum total tokens across all attempts."
862
952
  },
953
+ maxMinutes: {
954
+ type: "integer",
955
+ exclusiveMinimum: 0,
956
+ description: "Estimated wall-clock minutes allowed for the run contract."
957
+ },
958
+ maxFilesChanged: {
959
+ type: "integer",
960
+ exclusiveMinimum: 0,
961
+ description: "Estimated maximum files changed for the run contract."
962
+ },
963
+ maxCommands: {
964
+ type: "integer",
965
+ exclusiveMinimum: 0,
966
+ description: "Estimated maximum commands allowed for the run contract."
967
+ },
863
968
  verificationPlan: {
864
969
  type: "array",
865
970
  items: { type: "string" },
@@ -882,6 +987,93 @@ export function createMartinMcpServer(serverInfo) {
882
987
  },
883
988
  outputSchema: preflightOutputSchema
884
989
  },
990
+ {
991
+ name: "martin_logs",
992
+ description: "Read recent Martin loop events, ledger entries, and operator control receipts for live observability.",
993
+ annotations: {
994
+ readOnlyHint: true,
995
+ idempotentHint: true
996
+ },
997
+ inputSchema: {
998
+ type: "object",
999
+ additionalProperties: false,
1000
+ properties: {
1001
+ file: { type: "string" },
1002
+ loopId: { type: "string" },
1003
+ runsDir: { type: "string" },
1004
+ latest: { const: true },
1005
+ limit: { type: "integer", minimum: 1 }
1006
+ },
1007
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1008
+ },
1009
+ outputSchema: logsOutputSchema
1010
+ },
1011
+ {
1012
+ name: "martin_pause",
1013
+ description: "Record a durable pause request for a Martin run so humans and runtimes can see that execution should pause before risky follow-up work.",
1014
+ annotations: {
1015
+ destructiveHint: true,
1016
+ idempotentHint: false
1017
+ },
1018
+ inputSchema: {
1019
+ type: "object",
1020
+ additionalProperties: false,
1021
+ properties: {
1022
+ file: { type: "string" },
1023
+ loopId: { type: "string" },
1024
+ runsDir: { type: "string" },
1025
+ latest: { const: true },
1026
+ reason: { type: "string" },
1027
+ requestedBy: { type: "string" }
1028
+ },
1029
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1030
+ },
1031
+ outputSchema: controlOutputSchema
1032
+ },
1033
+ {
1034
+ name: "martin_cancel",
1035
+ description: "Record a durable cancellation request for a Martin run. This writes a control receipt; it does not silently kill a process without evidence.",
1036
+ annotations: {
1037
+ destructiveHint: true,
1038
+ idempotentHint: false
1039
+ },
1040
+ inputSchema: {
1041
+ type: "object",
1042
+ additionalProperties: false,
1043
+ properties: {
1044
+ file: { type: "string" },
1045
+ loopId: { type: "string" },
1046
+ runsDir: { type: "string" },
1047
+ latest: { const: true },
1048
+ reason: { type: "string" },
1049
+ requestedBy: { type: "string" }
1050
+ },
1051
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1052
+ },
1053
+ outputSchema: controlOutputSchema
1054
+ },
1055
+ {
1056
+ name: "martin_continue",
1057
+ description: "Record a durable continue or resume request for a Martin run after a human pause or approval checkpoint.",
1058
+ annotations: {
1059
+ destructiveHint: true,
1060
+ idempotentHint: false
1061
+ },
1062
+ inputSchema: {
1063
+ type: "object",
1064
+ additionalProperties: false,
1065
+ properties: {
1066
+ file: { type: "string" },
1067
+ loopId: { type: "string" },
1068
+ runsDir: { type: "string" },
1069
+ latest: { const: true },
1070
+ reason: { type: "string" },
1071
+ requestedBy: { type: "string" }
1072
+ },
1073
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1074
+ },
1075
+ outputSchema: controlOutputSchema
1076
+ },
885
1077
  {
886
1078
  name: "martin_list_runs",
887
1079
  description: "List recent Martin runs from the run store with lightweight filters for status, lifecycle, engine metadata, and recency.",
@@ -1055,6 +1247,114 @@ export function createMartinMcpServer(serverInfo) {
1055
1247
  ]
1056
1248
  },
1057
1249
  outputSchema: dossierOutputSchema
1250
+ },
1251
+ {
1252
+ name: "martin_dossier",
1253
+ description: "Alias for martin_run_dossier with support for JSON, Markdown, or GitHub PR formatting. Use after martin_run to understand what happened and whether the result is actually safe to trust.",
1254
+ annotations: {
1255
+ readOnlyHint: true,
1256
+ idempotentHint: true
1257
+ },
1258
+ inputSchema: {
1259
+ type: "object",
1260
+ additionalProperties: false,
1261
+ properties: {
1262
+ file: { type: "string" },
1263
+ loopId: { type: "string" },
1264
+ runsDir: { type: "string" },
1265
+ latest: { const: true },
1266
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
1267
+ },
1268
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1269
+ },
1270
+ outputSchema: dossierOutputSchema
1271
+ },
1272
+ {
1273
+ name: "martin_eval",
1274
+ description: "Grade a Martin run for task completion, verifier health, diff discipline, risk, and reviewability.",
1275
+ annotations: {
1276
+ readOnlyHint: true,
1277
+ idempotentHint: true
1278
+ },
1279
+ inputSchema: {
1280
+ type: "object",
1281
+ additionalProperties: false,
1282
+ properties: {
1283
+ file: { type: "string" },
1284
+ loopId: { type: "string" },
1285
+ runsDir: { type: "string" },
1286
+ latest: { const: true }
1287
+ },
1288
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1289
+ },
1290
+ outputSchema: evalOutputSchema
1291
+ },
1292
+ {
1293
+ name: "martin_pr_summary",
1294
+ description: "Generate a PR title and body with a MartinLoop dossier block for a completed run.",
1295
+ annotations: {
1296
+ readOnlyHint: true,
1297
+ idempotentHint: true
1298
+ },
1299
+ inputSchema: {
1300
+ type: "object",
1301
+ additionalProperties: false,
1302
+ properties: {
1303
+ file: { type: "string" },
1304
+ loopId: { type: "string" },
1305
+ runsDir: { type: "string" },
1306
+ latest: { const: true },
1307
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
1308
+ },
1309
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1310
+ },
1311
+ outputSchema: prSummaryOutputSchema
1312
+ },
1313
+ {
1314
+ name: "martin_create_pr",
1315
+ description: "Create or preview a GitHub PR with a MartinLoop dossier body. Use execute=true to actually call gh.",
1316
+ annotations: {
1317
+ destructiveHint: true,
1318
+ idempotentHint: false
1319
+ },
1320
+ inputSchema: {
1321
+ type: "object",
1322
+ additionalProperties: false,
1323
+ properties: {
1324
+ file: { type: "string" },
1325
+ loopId: { type: "string" },
1326
+ runsDir: { type: "string" },
1327
+ latest: { const: true },
1328
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1329
+ title: { type: "string" },
1330
+ base: { type: "string" },
1331
+ execute: { type: "boolean" }
1332
+ },
1333
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1334
+ },
1335
+ outputSchema: prSummaryOutputSchema
1336
+ },
1337
+ {
1338
+ name: "martin_review_pr",
1339
+ description: "Review a PR or PR draft against the Martin dossier and evaluation evidence.",
1340
+ annotations: {
1341
+ readOnlyHint: true,
1342
+ idempotentHint: true
1343
+ },
1344
+ inputSchema: {
1345
+ type: "object",
1346
+ additionalProperties: false,
1347
+ properties: {
1348
+ file: { type: "string" },
1349
+ loopId: { type: "string" },
1350
+ runsDir: { type: "string" },
1351
+ latest: { const: true },
1352
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1353
+ prBody: { type: "string" }
1354
+ },
1355
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1356
+ },
1357
+ outputSchema: prReviewOutputSchema
1058
1358
  }
1059
1359
  ]
1060
1360
  }));
@@ -1113,11 +1413,58 @@ export function createMartinMcpServer(serverInfo) {
1113
1413
  if (name === "martin_doctor") {
1114
1414
  const input = validateToolInput("martin_doctor", args);
1115
1415
  const output = await martinDoctorTool(input);
1416
+ await recordMcpWorkflowStep({
1417
+ runsRoot: output.environment.runsRoot,
1418
+ step: "doctor",
1419
+ workingDirectory: output.environment.workingDirectory,
1420
+ engine: input.engine
1421
+ }).catch(() => { });
1116
1422
  return createToolSuccessResult(output, output.summary);
1117
1423
  }
1424
+ if (name === "martin_plan") {
1425
+ const input = validateToolInput("martin_plan", args);
1426
+ const output = await martinPlanTool(input);
1427
+ await recordMcpWorkflowStep({
1428
+ runsRoot: resolveRunsRoot(process.env),
1429
+ step: "plan",
1430
+ workingDirectory: output.workingDirectory,
1431
+ objective: output.objective
1432
+ }).catch(() => { });
1433
+ return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1434
+ }
1118
1435
  if (name === "martin_preflight") {
1119
1436
  const input = validateToolInput("martin_preflight", args);
1120
1437
  const output = await martinPreflightTool(input);
1438
+ if (output.ok) {
1439
+ await recordMcpWorkflowStep({
1440
+ runsRoot: output.execution.runsRoot,
1441
+ step: "preflight",
1442
+ workingDirectory: output.normalized.workingDirectory,
1443
+ objective: output.normalized.objective,
1444
+ engine: output.normalized.engine,
1445
+ verificationPlan: output.normalized.verificationPlan
1446
+ }).catch(() => { });
1447
+ }
1448
+ return createToolSuccessResult(output, output.summary);
1449
+ }
1450
+ if (name === "martin_logs") {
1451
+ const input = validateToolInput("martin_logs", args);
1452
+ const output = await martinLogsTool(input);
1453
+ return createToolSuccessResult(output, `Loaded ${output.logCount} log entries for Martin run ${output.loopId}.`);
1454
+ }
1455
+ if (name === "martin_pause") {
1456
+ const input = validateToolInput("martin_pause", args);
1457
+ const output = await createRunControlReceipt("pause", input);
1458
+ return createToolSuccessResult(output, output.summary);
1459
+ }
1460
+ if (name === "martin_cancel") {
1461
+ const input = validateToolInput("martin_cancel", args);
1462
+ const output = await createRunControlReceipt("cancel", input);
1463
+ return createToolSuccessResult(output, output.summary);
1464
+ }
1465
+ if (name === "martin_continue") {
1466
+ const input = validateToolInput("martin_continue", args);
1467
+ const output = await createRunControlReceipt("continue", input);
1121
1468
  return createToolSuccessResult(output, output.summary);
1122
1469
  }
1123
1470
  if (name === "martin_list_runs") {
@@ -1150,6 +1497,33 @@ export function createMartinMcpServer(serverInfo) {
1150
1497
  const output = await martinRunDossierTool(input);
1151
1498
  return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`);
1152
1499
  }
1500
+ if (name === "martin_dossier") {
1501
+ const input = validateToolInput("martin_dossier", args);
1502
+ const output = await martinRunDossierTool(input);
1503
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} in ${output.format} format.`);
1504
+ }
1505
+ if (name === "martin_eval") {
1506
+ const input = validateToolInput("martin_eval", args);
1507
+ const output = await martinEvalTool(input);
1508
+ return createToolSuccessResult(output, `Evaluation for ${output.loopId}: ${output.grade} (${output.score}).`);
1509
+ }
1510
+ if (name === "martin_pr_summary") {
1511
+ const input = validateToolInput("martin_pr_summary", args);
1512
+ const output = await martinPrSummaryTool(input);
1513
+ return createToolSuccessResult(output, `PR summary ready for Martin run ${output.loopId}.`);
1514
+ }
1515
+ if (name === "martin_create_pr") {
1516
+ const input = validateToolInput("martin_create_pr", args);
1517
+ const output = await martinCreatePrTool(input);
1518
+ return createToolSuccessResult(output, output.created
1519
+ ? `Created PR for Martin run ${output.loopId}.`
1520
+ : `PR preview ready for Martin run ${output.loopId}.`);
1521
+ }
1522
+ if (name === "martin_review_pr") {
1523
+ const input = validateToolInput("martin_review_pr", args);
1524
+ const output = await martinReviewPrTool(input);
1525
+ return createToolSuccessResult(output, `PR review verdict for ${output.loopId}: ${output.verdict}.`);
1526
+ }
1153
1527
  return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
1154
1528
  }
1155
1529
  catch (error) {
@@ -1,4 +1,6 @@
1
+ import { type CodexHostPlatform } from "../vendor/adapters/index.js";
1
2
  import { type LoopPreview, type MartinEngine } from "./tool-support.js";
3
+ import { type MartinReadinessReport } from "./workflow-governance.js";
2
4
  export interface MartinDoctorInput {
3
5
  workingDirectory?: string;
4
6
  runsDir?: string;
@@ -16,13 +18,23 @@ export interface MartinDoctorOutput {
16
18
  workspaceRoot: string;
17
19
  workingDirectory: string;
18
20
  runsRoot: string;
19
- mode: "live" | "stub";
21
+ mode: "live" | "proof";
20
22
  liveMode: boolean;
21
23
  };
24
+ scope: {
25
+ invocationRoot: string;
26
+ workingDirectory: string;
27
+ repoRoot: string;
28
+ runsRoot: string;
29
+ };
22
30
  engines: Record<MartinEngine, {
23
31
  available: boolean;
24
32
  detail: string;
25
33
  resolvedPath?: string;
34
+ hostPlatform?: CodexHostPlatform;
35
+ nativeInstallValid?: boolean;
36
+ launchReady?: boolean;
37
+ probeSummary?: string;
26
38
  }>;
27
39
  requestedEngine?: MartinEngine;
28
40
  runStore: {
@@ -30,6 +42,7 @@ export interface MartinDoctorOutput {
30
42
  loopCount: number;
31
43
  latestRun?: LoopPreview;
32
44
  };
45
+ readiness: MartinReadinessReport;
33
46
  warnings: string[];
34
47
  }
35
48
  export declare function martinDoctorTool(input: MartinDoctorInput): Promise<MartinDoctorOutput>;
@@ -1,31 +1,46 @@
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
+ import { buildReadinessReport, inspectRepoSignals } from "./workflow-governance.js";
4
6
  export async function martinDoctorTool(input) {
5
7
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
6
8
  const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
9
+ const workspaceRoot = resolveSafeRepoRoot();
7
10
  const executionMode = resolveExecutionMode();
8
11
  const claude = getEngineAvailability("claude");
9
- 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;
10
20
  const runStore = await inspectRunsRoot(runsRoot);
21
+ const signals = inspectRepoSignals(workingDirectory);
22
+ const readiness = buildReadinessReport(signals, runStore);
11
23
  const warnings = [];
12
24
  if (!runStore.exists) {
13
25
  warnings.push("Configured Martin runs root does not exist yet.");
14
26
  }
15
- if (executionMode.liveMode && !claude.available && !codex.available) {
16
- 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.");
17
29
  }
18
30
  if (input.engine && executionMode.liveMode) {
19
- const selected = input.engine === "claude" ? claude : codex;
31
+ const selected = input.engine === "claude" ? claude : input.engine === "gemini" ? gemini : codex;
20
32
  if (!selected.available) {
21
33
  warnings.push(`Requested engine '${input.engine}' is not available on PATH.`);
22
34
  }
35
+ if (input.engine === "codex" && codexProbe && !codexProbe.ok) {
36
+ warnings.push(codexProbe.summary);
37
+ }
23
38
  }
24
39
  warnings.push(...runStore.warnings);
25
40
  const status = warnings.length === 0 ? "ok" : "degraded";
26
41
  const summary = status === "ok"
27
- ? `Doctor passed: ${runStore.loopCount} run(s) visible in ${runsRoot}.`
28
- : `Doctor found ${warnings.length} issue(s); review warnings before live execution.`;
42
+ ? `Doctor passed: repo readiness ${readiness.score}/100 with ${runStore.loopCount} visible run(s).`
43
+ : `Doctor found ${warnings.length} issue(s); readiness ${readiness.score}/100 before live execution.`;
29
44
  return {
30
45
  status,
31
46
  summary,
@@ -35,12 +50,18 @@ export async function martinDoctorTool(input) {
35
50
  platform: process.platform
36
51
  },
37
52
  environment: {
38
- workspaceRoot: resolveSafeRepoRoot(),
53
+ workspaceRoot,
39
54
  workingDirectory,
40
55
  runsRoot,
41
56
  mode: executionMode.mode,
42
57
  liveMode: executionMode.liveMode
43
58
  },
59
+ scope: {
60
+ invocationRoot: workspaceRoot,
61
+ workingDirectory,
62
+ repoRoot: workingDirectory,
63
+ runsRoot
64
+ },
44
65
  engines: {
45
66
  claude: {
46
67
  available: claude.available,
@@ -50,7 +71,20 @@ export async function martinDoctorTool(input) {
50
71
  codex: {
51
72
  available: codex.available,
52
73
  detail: codex.detail,
53
- ...(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 } : {})
54
88
  }
55
89
  },
56
90
  ...(input.engine ? { requestedEngine: input.engine } : {}),
@@ -59,6 +93,7 @@ export async function martinDoctorTool(input) {
59
93
  loopCount: runStore.loopCount,
60
94
  ...(runStore.latestRun ? { latestRun: runStore.latestRun } : {})
61
95
  },
96
+ readiness,
62
97
  warnings
63
98
  };
64
99
  }
@@ -0,0 +1,24 @@
1
+ export interface MartinEvalInput {
2
+ file?: string;
3
+ loopId?: string;
4
+ runsDir?: string;
5
+ latest?: boolean;
6
+ }
7
+ export interface MartinEvalOutput {
8
+ source: string;
9
+ sourceKind: "file" | "loop_id" | "latest" | "runs_root";
10
+ loopId: string;
11
+ score: number;
12
+ grade: "mergeable" | "mergeable_with_review" | "needs_review" | "blocked" | "insufficient_evidence";
13
+ checks: {
14
+ taskCompletion: "passed" | "warning" | "failed";
15
+ verifier: "passed" | "warning" | "failed";
16
+ diffDiscipline: "passed" | "warning" | "failed";
17
+ regressionRisk: "passed" | "warning" | "failed";
18
+ securityRisk: "passed" | "warning" | "failed";
19
+ reviewability: "passed" | "warning" | "failed";
20
+ };
21
+ warnings: string[];
22
+ summary: string;
23
+ }
24
+ export declare function martinEvalTool(input: MartinEvalInput): Promise<MartinEvalOutput>;