@martinloop/mcp 0.2.5 → 0.2.7

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 (44) hide show
  1. package/README.md +101 -138
  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.js +93 -1
  7. package/dist/resources.d.ts +8 -0
  8. package/dist/resources.js +245 -14
  9. package/dist/server-validation.d.ts +1 -1
  10. package/dist/server-validation.js +116 -0
  11. package/dist/server.js +361 -3
  12. package/dist/tools/doctor.d.ts +2 -0
  13. package/dist/tools/doctor.js +6 -2
  14. package/dist/tools/eval.d.ts +24 -0
  15. package/dist/tools/eval.js +65 -0
  16. package/dist/tools/get-status.d.ts +8 -0
  17. package/dist/tools/get-status.js +18 -0
  18. package/dist/tools/logs.d.ts +25 -0
  19. package/dist/tools/logs.js +49 -0
  20. package/dist/tools/plan.d.ts +20 -0
  21. package/dist/tools/plan.js +10 -0
  22. package/dist/tools/pr-tools.d.ts +31 -0
  23. package/dist/tools/pr-tools.js +111 -0
  24. package/dist/tools/preflight.d.ts +10 -0
  25. package/dist/tools/preflight.js +11 -2
  26. package/dist/tools/run-controls.d.ts +36 -0
  27. package/dist/tools/run-controls.js +88 -0
  28. package/dist/tools/run-dossier.d.ts +14 -0
  29. package/dist/tools/run-dossier.js +61 -1
  30. package/dist/tools/run-loop.js +21 -2
  31. package/dist/tools/tool-errors.d.ts +1 -1
  32. package/dist/tools/tool-support.js +28 -1
  33. package/dist/tools/workflow-governance.d.ts +133 -0
  34. package/dist/tools/workflow-governance.js +581 -0
  35. package/dist/vendor/adapters/cli-bridge.d.ts +5 -0
  36. package/dist/vendor/adapters/cli-bridge.js +16 -8
  37. package/dist/vendor/adapters/index.d.ts +2 -1
  38. package/dist/vendor/adapters/index.js +2 -0
  39. package/dist/vendor/adapters/openai-compatible.d.ts +47 -0
  40. package/dist/vendor/adapters/openai-compatible.js +242 -0
  41. package/dist/workflow-state.d.ts +25 -0
  42. package/dist/workflow-state.js +102 -0
  43. package/package.json +3 -3
  44. 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" }
@@ -649,6 +656,30 @@ const dossierOutputSchema = {
649
656
  "warnings"
650
657
  ]
651
658
  };
659
+ const planOutputSchema = {
660
+ type: "object",
661
+ additionalProperties: true
662
+ };
663
+ const logsOutputSchema = {
664
+ type: "object",
665
+ additionalProperties: true
666
+ };
667
+ const controlOutputSchema = {
668
+ type: "object",
669
+ additionalProperties: true
670
+ };
671
+ const evalOutputSchema = {
672
+ type: "object",
673
+ additionalProperties: true
674
+ };
675
+ const prSummaryOutputSchema = {
676
+ type: "object",
677
+ additionalProperties: true
678
+ };
679
+ const prReviewOutputSchema = {
680
+ type: "object",
681
+ additionalProperties: true
682
+ };
652
683
  export function createMartinMcpServer(serverInfo) {
653
684
  const server = new Server({
654
685
  name: serverInfo?.name ?? "martin-loop",
@@ -658,7 +689,7 @@ export function createMartinMcpServer(serverInfo) {
658
689
  tools: [
659
690
  {
660
691
  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.",
692
+ 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
693
  annotations: {
663
694
  destructiveHint: true,
664
695
  idempotentHint: false,
@@ -791,7 +822,7 @@ export function createMartinMcpServer(serverInfo) {
791
822
  },
792
823
  {
793
824
  name: "martin_doctor",
794
- description: "Read-only environment and run-store diagnostics for the Martin MCP server.",
825
+ description: "Read-only environment and run-store diagnostics for the Martin MCP server. This is the expected first call before governed work begins.",
795
826
  annotations: {
796
827
  readOnlyHint: true,
797
828
  idempotentHint: true
@@ -817,9 +848,44 @@ export function createMartinMcpServer(serverInfo) {
817
848
  },
818
849
  outputSchema: doctorOutputSchema
819
850
  },
851
+ {
852
+ name: "martin_plan",
853
+ 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.",
854
+ annotations: {
855
+ readOnlyHint: true,
856
+ idempotentHint: true
857
+ },
858
+ inputSchema: {
859
+ type: "object",
860
+ additionalProperties: false,
861
+ properties: {
862
+ objective: { type: "string", description: "The coding objective to plan." },
863
+ workingDirectory: {
864
+ type: "string",
865
+ description: "Optional repo-root override resolved under the MCP workspace root."
866
+ },
867
+ context: { type: "string", description: "Optional extra issue or bug context." },
868
+ policyPack: {
869
+ type: "string",
870
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
871
+ },
872
+ verificationPlan: { type: "array", items: { type: "string" } },
873
+ allowedPaths: { type: "array", items: { type: "string" } },
874
+ deniedPaths: { type: "array", items: { type: "string" } },
875
+ maxUsd: { type: "number", exclusiveMinimum: 0 },
876
+ maxIterations: { type: "integer", exclusiveMinimum: 0 },
877
+ maxTokens: { type: "integer", exclusiveMinimum: 0 },
878
+ maxMinutes: { type: "integer", exclusiveMinimum: 0 },
879
+ maxFilesChanged: { type: "integer", exclusiveMinimum: 0 },
880
+ maxCommands: { type: "integer", exclusiveMinimum: 0 }
881
+ },
882
+ required: ["objective"]
883
+ },
884
+ outputSchema: planOutputSchema
885
+ },
820
886
  {
821
887
  name: "martin_preflight",
822
- description: "Read-only validation of a planned Martin run before any execution or spend.",
888
+ description: "Read-only validation of a planned Martin run before any execution or spend. This is the last required step before martin_run.",
823
889
  annotations: {
824
890
  readOnlyHint: true,
825
891
  idempotentHint: true
@@ -845,6 +911,14 @@ export function createMartinMcpServer(serverInfo) {
845
911
  type: "string",
846
912
  description: "Model override passed to the CLI."
847
913
  },
914
+ context: {
915
+ type: "string",
916
+ description: "Optional issue context carried into the run contract."
917
+ },
918
+ policyPack: {
919
+ type: "string",
920
+ enum: ["solo-founder", "startup-team", "enterprise-strict", "oss-maintainer", "security-sensitive"]
921
+ },
848
922
  maxUsd: {
849
923
  type: "number",
850
924
  exclusiveMinimum: 0,
@@ -860,6 +934,21 @@ export function createMartinMcpServer(serverInfo) {
860
934
  exclusiveMinimum: 0,
861
935
  description: "Maximum total tokens across all attempts."
862
936
  },
937
+ maxMinutes: {
938
+ type: "integer",
939
+ exclusiveMinimum: 0,
940
+ description: "Estimated wall-clock minutes allowed for the run contract."
941
+ },
942
+ maxFilesChanged: {
943
+ type: "integer",
944
+ exclusiveMinimum: 0,
945
+ description: "Estimated maximum files changed for the run contract."
946
+ },
947
+ maxCommands: {
948
+ type: "integer",
949
+ exclusiveMinimum: 0,
950
+ description: "Estimated maximum commands allowed for the run contract."
951
+ },
863
952
  verificationPlan: {
864
953
  type: "array",
865
954
  items: { type: "string" },
@@ -882,6 +971,93 @@ export function createMartinMcpServer(serverInfo) {
882
971
  },
883
972
  outputSchema: preflightOutputSchema
884
973
  },
974
+ {
975
+ name: "martin_logs",
976
+ description: "Read recent Martin loop events, ledger entries, and operator control receipts for live observability.",
977
+ annotations: {
978
+ readOnlyHint: true,
979
+ idempotentHint: true
980
+ },
981
+ inputSchema: {
982
+ type: "object",
983
+ additionalProperties: false,
984
+ properties: {
985
+ file: { type: "string" },
986
+ loopId: { type: "string" },
987
+ runsDir: { type: "string" },
988
+ latest: { const: true },
989
+ limit: { type: "integer", minimum: 1 }
990
+ },
991
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
992
+ },
993
+ outputSchema: logsOutputSchema
994
+ },
995
+ {
996
+ name: "martin_pause",
997
+ 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.",
998
+ annotations: {
999
+ destructiveHint: true,
1000
+ idempotentHint: false
1001
+ },
1002
+ inputSchema: {
1003
+ type: "object",
1004
+ additionalProperties: false,
1005
+ properties: {
1006
+ file: { type: "string" },
1007
+ loopId: { type: "string" },
1008
+ runsDir: { type: "string" },
1009
+ latest: { const: true },
1010
+ reason: { type: "string" },
1011
+ requestedBy: { type: "string" }
1012
+ },
1013
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1014
+ },
1015
+ outputSchema: controlOutputSchema
1016
+ },
1017
+ {
1018
+ name: "martin_cancel",
1019
+ description: "Record a durable cancellation request for a Martin run. This writes a control receipt; it does not silently kill a process without evidence.",
1020
+ annotations: {
1021
+ destructiveHint: true,
1022
+ idempotentHint: false
1023
+ },
1024
+ inputSchema: {
1025
+ type: "object",
1026
+ additionalProperties: false,
1027
+ properties: {
1028
+ file: { type: "string" },
1029
+ loopId: { type: "string" },
1030
+ runsDir: { type: "string" },
1031
+ latest: { const: true },
1032
+ reason: { type: "string" },
1033
+ requestedBy: { type: "string" }
1034
+ },
1035
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1036
+ },
1037
+ outputSchema: controlOutputSchema
1038
+ },
1039
+ {
1040
+ name: "martin_continue",
1041
+ description: "Record a durable continue or resume request for a Martin run after a human pause or approval checkpoint.",
1042
+ annotations: {
1043
+ destructiveHint: true,
1044
+ idempotentHint: false
1045
+ },
1046
+ inputSchema: {
1047
+ type: "object",
1048
+ additionalProperties: false,
1049
+ properties: {
1050
+ file: { type: "string" },
1051
+ loopId: { type: "string" },
1052
+ runsDir: { type: "string" },
1053
+ latest: { const: true },
1054
+ reason: { type: "string" },
1055
+ requestedBy: { type: "string" }
1056
+ },
1057
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1058
+ },
1059
+ outputSchema: controlOutputSchema
1060
+ },
885
1061
  {
886
1062
  name: "martin_list_runs",
887
1063
  description: "List recent Martin runs from the run store with lightweight filters for status, lifecycle, engine metadata, and recency.",
@@ -1055,6 +1231,114 @@ export function createMartinMcpServer(serverInfo) {
1055
1231
  ]
1056
1232
  },
1057
1233
  outputSchema: dossierOutputSchema
1234
+ },
1235
+ {
1236
+ name: "martin_dossier",
1237
+ 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.",
1238
+ annotations: {
1239
+ readOnlyHint: true,
1240
+ idempotentHint: true
1241
+ },
1242
+ inputSchema: {
1243
+ type: "object",
1244
+ additionalProperties: false,
1245
+ properties: {
1246
+ file: { type: "string" },
1247
+ loopId: { type: "string" },
1248
+ runsDir: { type: "string" },
1249
+ latest: { const: true },
1250
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
1251
+ },
1252
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1253
+ },
1254
+ outputSchema: dossierOutputSchema
1255
+ },
1256
+ {
1257
+ name: "martin_eval",
1258
+ description: "Grade a Martin run for task completion, verifier health, diff discipline, risk, and reviewability.",
1259
+ annotations: {
1260
+ readOnlyHint: true,
1261
+ idempotentHint: true
1262
+ },
1263
+ inputSchema: {
1264
+ type: "object",
1265
+ additionalProperties: false,
1266
+ properties: {
1267
+ file: { type: "string" },
1268
+ loopId: { type: "string" },
1269
+ runsDir: { type: "string" },
1270
+ latest: { const: true }
1271
+ },
1272
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1273
+ },
1274
+ outputSchema: evalOutputSchema
1275
+ },
1276
+ {
1277
+ name: "martin_pr_summary",
1278
+ description: "Generate a PR title and body with a MartinLoop dossier block for a completed run.",
1279
+ annotations: {
1280
+ readOnlyHint: true,
1281
+ idempotentHint: true
1282
+ },
1283
+ inputSchema: {
1284
+ type: "object",
1285
+ additionalProperties: false,
1286
+ properties: {
1287
+ file: { type: "string" },
1288
+ loopId: { type: "string" },
1289
+ runsDir: { type: "string" },
1290
+ latest: { const: true },
1291
+ format: { type: "string", enum: ["json", "md", "github-pr"] }
1292
+ },
1293
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1294
+ },
1295
+ outputSchema: prSummaryOutputSchema
1296
+ },
1297
+ {
1298
+ name: "martin_create_pr",
1299
+ description: "Create or preview a GitHub PR with a MartinLoop dossier body. Use execute=true to actually call gh.",
1300
+ annotations: {
1301
+ destructiveHint: true,
1302
+ idempotentHint: false
1303
+ },
1304
+ inputSchema: {
1305
+ type: "object",
1306
+ additionalProperties: false,
1307
+ properties: {
1308
+ file: { type: "string" },
1309
+ loopId: { type: "string" },
1310
+ runsDir: { type: "string" },
1311
+ latest: { const: true },
1312
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1313
+ title: { type: "string" },
1314
+ base: { type: "string" },
1315
+ execute: { type: "boolean" }
1316
+ },
1317
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1318
+ },
1319
+ outputSchema: prSummaryOutputSchema
1320
+ },
1321
+ {
1322
+ name: "martin_review_pr",
1323
+ description: "Review a PR or PR draft against the Martin dossier and evaluation evidence.",
1324
+ annotations: {
1325
+ readOnlyHint: true,
1326
+ idempotentHint: true
1327
+ },
1328
+ inputSchema: {
1329
+ type: "object",
1330
+ additionalProperties: false,
1331
+ properties: {
1332
+ file: { type: "string" },
1333
+ loopId: { type: "string" },
1334
+ runsDir: { type: "string" },
1335
+ latest: { const: true },
1336
+ format: { type: "string", enum: ["json", "md", "github-pr"] },
1337
+ prBody: { type: "string" }
1338
+ },
1339
+ oneOf: [{ required: ["file"] }, { required: ["loopId"] }, { required: ["latest"] }]
1340
+ },
1341
+ outputSchema: prReviewOutputSchema
1058
1342
  }
1059
1343
  ]
1060
1344
  }));
@@ -1113,11 +1397,58 @@ export function createMartinMcpServer(serverInfo) {
1113
1397
  if (name === "martin_doctor") {
1114
1398
  const input = validateToolInput("martin_doctor", args);
1115
1399
  const output = await martinDoctorTool(input);
1400
+ await recordMcpWorkflowStep({
1401
+ runsRoot: output.environment.runsRoot,
1402
+ step: "doctor",
1403
+ workingDirectory: output.environment.workingDirectory,
1404
+ engine: input.engine
1405
+ }).catch(() => { });
1116
1406
  return createToolSuccessResult(output, output.summary);
1117
1407
  }
1408
+ if (name === "martin_plan") {
1409
+ const input = validateToolInput("martin_plan", args);
1410
+ const output = await martinPlanTool(input);
1411
+ await recordMcpWorkflowStep({
1412
+ runsRoot: resolveRunsRoot(process.env),
1413
+ step: "plan",
1414
+ workingDirectory: output.workingDirectory,
1415
+ objective: output.objective
1416
+ }).catch(() => { });
1417
+ return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1418
+ }
1118
1419
  if (name === "martin_preflight") {
1119
1420
  const input = validateToolInput("martin_preflight", args);
1120
1421
  const output = await martinPreflightTool(input);
1422
+ if (output.ok) {
1423
+ await recordMcpWorkflowStep({
1424
+ runsRoot: output.execution.runsRoot,
1425
+ step: "preflight",
1426
+ workingDirectory: output.normalized.workingDirectory,
1427
+ objective: output.normalized.objective,
1428
+ engine: output.normalized.engine,
1429
+ verificationPlan: output.normalized.verificationPlan
1430
+ }).catch(() => { });
1431
+ }
1432
+ return createToolSuccessResult(output, output.summary);
1433
+ }
1434
+ if (name === "martin_logs") {
1435
+ const input = validateToolInput("martin_logs", args);
1436
+ const output = await martinLogsTool(input);
1437
+ return createToolSuccessResult(output, `Loaded ${output.logCount} log entries for Martin run ${output.loopId}.`);
1438
+ }
1439
+ if (name === "martin_pause") {
1440
+ const input = validateToolInput("martin_pause", args);
1441
+ const output = await createRunControlReceipt("pause", input);
1442
+ return createToolSuccessResult(output, output.summary);
1443
+ }
1444
+ if (name === "martin_cancel") {
1445
+ const input = validateToolInput("martin_cancel", args);
1446
+ const output = await createRunControlReceipt("cancel", input);
1447
+ return createToolSuccessResult(output, output.summary);
1448
+ }
1449
+ if (name === "martin_continue") {
1450
+ const input = validateToolInput("martin_continue", args);
1451
+ const output = await createRunControlReceipt("continue", input);
1121
1452
  return createToolSuccessResult(output, output.summary);
1122
1453
  }
1123
1454
  if (name === "martin_list_runs") {
@@ -1150,6 +1481,33 @@ export function createMartinMcpServer(serverInfo) {
1150
1481
  const output = await martinRunDossierTool(input);
1151
1482
  return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} with ${output.attempts.length} attempt(s).`);
1152
1483
  }
1484
+ if (name === "martin_dossier") {
1485
+ const input = validateToolInput("martin_dossier", args);
1486
+ const output = await martinRunDossierTool(input);
1487
+ return createToolSuccessResult(output, `Dossier ready for Martin run ${output.loop.loopId} in ${output.format} format.`);
1488
+ }
1489
+ if (name === "martin_eval") {
1490
+ const input = validateToolInput("martin_eval", args);
1491
+ const output = await martinEvalTool(input);
1492
+ return createToolSuccessResult(output, `Evaluation for ${output.loopId}: ${output.grade} (${output.score}).`);
1493
+ }
1494
+ if (name === "martin_pr_summary") {
1495
+ const input = validateToolInput("martin_pr_summary", args);
1496
+ const output = await martinPrSummaryTool(input);
1497
+ return createToolSuccessResult(output, `PR summary ready for Martin run ${output.loopId}.`);
1498
+ }
1499
+ if (name === "martin_create_pr") {
1500
+ const input = validateToolInput("martin_create_pr", args);
1501
+ const output = await martinCreatePrTool(input);
1502
+ return createToolSuccessResult(output, output.created
1503
+ ? `Created PR for Martin run ${output.loopId}.`
1504
+ : `PR preview ready for Martin run ${output.loopId}.`);
1505
+ }
1506
+ if (name === "martin_review_pr") {
1507
+ const input = validateToolInput("martin_review_pr", args);
1508
+ const output = await martinReviewPrTool(input);
1509
+ return createToolSuccessResult(output, `PR review verdict for ${output.loopId}: ${output.verdict}.`);
1510
+ }
1153
1511
  return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
1154
1512
  }
1155
1513
  catch (error) {
@@ -1,4 +1,5 @@
1
1
  import { type LoopPreview, type MartinEngine } from "./tool-support.js";
2
+ import { type MartinReadinessReport } from "./workflow-governance.js";
2
3
  export interface MartinDoctorInput {
3
4
  workingDirectory?: string;
4
5
  runsDir?: string;
@@ -30,6 +31,7 @@ export interface MartinDoctorOutput {
30
31
  loopCount: number;
31
32
  latestRun?: LoopPreview;
32
33
  };
34
+ readiness: MartinReadinessReport;
33
35
  warnings: string[];
34
36
  }
35
37
  export declare function martinDoctorTool(input: MartinDoctorInput): Promise<MartinDoctorOutput>;
@@ -1,6 +1,7 @@
1
1
  import { resolveRunsRoot } from "../vendor/core/index.js";
2
2
  import { resolveSafeRepoRoot, resolveSafeRunsRootPath } from "../server-validation.js";
3
3
  import { getEngineAvailability, inspectRunsRoot, resolveExecutionMode } from "./tool-support.js";
4
+ import { buildReadinessReport, inspectRepoSignals } from "./workflow-governance.js";
4
5
  export async function martinDoctorTool(input) {
5
6
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
6
7
  const runsRoot = resolveSafeRunsRootPath(input.runsDir, resolveRunsRoot(process.env));
@@ -8,6 +9,8 @@ export async function martinDoctorTool(input) {
8
9
  const claude = getEngineAvailability("claude");
9
10
  const codex = getEngineAvailability("codex");
10
11
  const runStore = await inspectRunsRoot(runsRoot);
12
+ const signals = inspectRepoSignals(workingDirectory);
13
+ const readiness = buildReadinessReport(signals, runStore);
11
14
  const warnings = [];
12
15
  if (!runStore.exists) {
13
16
  warnings.push("Configured Martin runs root does not exist yet.");
@@ -24,8 +27,8 @@ export async function martinDoctorTool(input) {
24
27
  warnings.push(...runStore.warnings);
25
28
  const status = warnings.length === 0 ? "ok" : "degraded";
26
29
  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.`;
30
+ ? `Doctor passed: repo readiness ${readiness.score}/100 with ${runStore.loopCount} visible run(s).`
31
+ : `Doctor found ${warnings.length} issue(s); readiness ${readiness.score}/100 before live execution.`;
29
32
  return {
30
33
  status,
31
34
  summary,
@@ -59,6 +62,7 @@ export async function martinDoctorTool(input) {
59
62
  loopCount: runStore.loopCount,
60
63
  ...(runStore.latestRun ? { latestRun: runStore.latestRun } : {})
61
64
  },
65
+ readiness,
62
66
  warnings
63
67
  };
64
68
  }
@@ -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>;
@@ -0,0 +1,65 @@
1
+ import { loadDetailedLoopRecord, readLedgerEvents } from "./run-store.js";
2
+ import { buildVerificationSummary } from "./tool-support.js";
3
+ import { assessRunRisk, inspectRepoSignals } from "./workflow-governance.js";
4
+ export async function martinEvalTool(input) {
5
+ const detail = await loadDetailedLoopRecord(input);
6
+ const ledgerEvents = await readLedgerEvents(detail);
7
+ const verification = buildVerificationSummary(detail.loop, ledgerEvents);
8
+ const repoRoot = detail.loop.task?.repoRoot;
9
+ const signals = inspectRepoSignals(repoRoot ?? process.cwd());
10
+ const risk = assessRunRisk({
11
+ objective: detail.loop.task?.objective ?? detail.loop.loopId,
12
+ allowedPaths: detail.loop.task?.allowedPaths ?? [],
13
+ blockedPaths: detail.loop.task?.deniedPaths ?? [],
14
+ verifiers: detail.loop.task?.verificationPlan ?? [],
15
+ signals
16
+ });
17
+ const checks = {
18
+ taskCompletion: detail.loop.status === "completed" ? "passed" : detail.loop.status === "exited" ? "warning" : "failed",
19
+ verifier: verification.status === "passed"
20
+ ? "passed"
21
+ : verification.status === "failed"
22
+ ? "failed"
23
+ : "warning",
24
+ diffDiscipline: (detail.loop.task?.allowedPaths?.length ?? 0) > 0 ? "passed" : "warning",
25
+ regressionRisk: verification.status === "passed" ? "passed" : "warning",
26
+ securityRisk: risk.level === "high" ? "failed" : risk.level === "medium" ? "warning" : "passed",
27
+ reviewability: detail.loop.attempts.length > 0 && (detail.loop.events?.length ?? 0) > 0 ? "passed" : "warning"
28
+ };
29
+ let score = 100;
30
+ score -= checks.taskCompletion === "failed" ? 25 : checks.taskCompletion === "warning" ? 10 : 0;
31
+ score -= checks.verifier === "failed" ? 25 : checks.verifier === "warning" ? 10 : 0;
32
+ score -= checks.diffDiscipline === "warning" ? 8 : 0;
33
+ score -= checks.regressionRisk === "warning" ? 10 : 0;
34
+ score -= checks.securityRisk === "failed" ? 20 : checks.securityRisk === "warning" ? 10 : 0;
35
+ score -= checks.reviewability === "warning" ? 8 : 0;
36
+ score = Math.max(0, score);
37
+ const grade = verification.status === "unavailable"
38
+ ? "insufficient_evidence"
39
+ : score >= 90
40
+ ? "mergeable"
41
+ : score >= 75
42
+ ? "mergeable_with_review"
43
+ : score >= 55
44
+ ? "needs_review"
45
+ : "blocked";
46
+ const warnings = [...detail.warnings, ...verification.warnings, ...risk.reasons];
47
+ return {
48
+ source: detail.source,
49
+ sourceKind: detail.sourceKind,
50
+ loopId: detail.loop.loopId,
51
+ score,
52
+ grade,
53
+ checks: { ...checks },
54
+ warnings,
55
+ summary: grade === "mergeable"
56
+ ? `Run ${detail.loop.loopId} looks mergeable with verifier-backed completion.`
57
+ : grade === "mergeable_with_review"
58
+ ? `Run ${detail.loop.loopId} looks mergeable with review; inspect dossier and risk notes first.`
59
+ : grade === "needs_review"
60
+ ? `Run ${detail.loop.loopId} needs review before promotion.`
61
+ : grade === "insufficient_evidence"
62
+ ? `Run ${detail.loop.loopId} does not have enough evidence for a safe promotion decision.`
63
+ : `Run ${detail.loop.loopId} is blocked from promotion by verification or risk gaps.`
64
+ };
65
+ }
@@ -33,5 +33,13 @@ export interface GetStatusOutput {
33
33
  inspection: {
34
34
  loop: LoopPreview;
35
35
  };
36
+ live: {
37
+ phase: string;
38
+ pauseState: "active" | "paused" | "cancellation_requested";
39
+ approvalState: "not_required" | "resume_requested";
40
+ commandsRun: number;
41
+ filesTouched: number;
42
+ verifierStep?: string;
43
+ };
36
44
  }
37
45
  export declare function getStatusTool(input: GetStatusInput): Promise<GetStatusOutput>;
@@ -1,9 +1,19 @@
1
1
  import { evaluateCostGovernor } from "../vendor/core/index.js";
2
2
  import { loadLoopRecordForStatus } from "./run-store.js";
3
3
  import { buildLoopPreview } from "./tool-support.js";
4
+ import { readRunControlState } from "./run-controls.js";
4
5
  export async function getStatusTool(input) {
5
6
  const resolved = await loadLoopRecordForStatus(input);
6
7
  const loop = resolved.loop;
8
+ const control = input.loopJson !== undefined
9
+ ? {
10
+ requestedState: "active",
11
+ approvalState: "not_required",
12
+ receipts: []
13
+ }
14
+ : await readRunControlState(input);
15
+ const latestEvent = loop.events?.at(-1);
16
+ const changedFiles = loop.artifacts?.filter((artifact) => artifact.kind === "diff").length ?? 0;
7
17
  const costState = evaluateCostGovernor({
8
18
  budget: loop.budget,
9
19
  cost: {
@@ -35,6 +45,14 @@ export async function getStatusTool(input) {
35
45
  },
36
46
  inspection: {
37
47
  loop: buildLoopPreview(loop)
48
+ },
49
+ live: {
50
+ phase: latestEvent?.lifecycleState ?? loop.lifecycleState,
51
+ pauseState: control.requestedState,
52
+ approvalState: control.approvalState,
53
+ commandsRun: loop.attempts.length,
54
+ filesTouched: changedFiles,
55
+ ...(loop.task?.verificationPlan?.[0] ? { verifierStep: loop.task.verificationPlan[0] } : {})
38
56
  }
39
57
  };
40
58
  }