@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.
- package/README.md +101 -138
- package/dist/discovery-metadata.d.ts +10 -5
- package/dist/discovery-metadata.js +95 -5
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/prompts.js +93 -1
- package/dist/resources.d.ts +8 -0
- package/dist/resources.js +245 -14
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +116 -0
- package/dist/server.js +361 -3
- package/dist/tools/doctor.d.ts +2 -0
- package/dist/tools/doctor.js +6 -2
- package/dist/tools/eval.d.ts +24 -0
- package/dist/tools/eval.js +65 -0
- package/dist/tools/get-status.d.ts +8 -0
- package/dist/tools/get-status.js +18 -0
- package/dist/tools/logs.d.ts +25 -0
- package/dist/tools/logs.js +49 -0
- package/dist/tools/plan.d.ts +20 -0
- package/dist/tools/plan.js +10 -0
- package/dist/tools/pr-tools.d.ts +31 -0
- package/dist/tools/pr-tools.js +111 -0
- package/dist/tools/preflight.d.ts +10 -0
- package/dist/tools/preflight.js +11 -2
- package/dist/tools/run-controls.d.ts +36 -0
- package/dist/tools/run-controls.js +88 -0
- package/dist/tools/run-dossier.d.ts +14 -0
- package/dist/tools/run-dossier.js +61 -1
- package/dist/tools/run-loop.js +21 -2
- package/dist/tools/tool-errors.d.ts +1 -1
- package/dist/tools/tool-support.js +28 -1
- package/dist/tools/workflow-governance.d.ts +133 -0
- package/dist/tools/workflow-governance.js +581 -0
- package/dist/vendor/adapters/cli-bridge.d.ts +5 -0
- package/dist/vendor/adapters/cli-bridge.js +16 -8
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -0
- package/dist/vendor/adapters/openai-compatible.d.ts +47 -0
- package/dist/vendor/adapters/openai-compatible.js +242 -0
- package/dist/workflow-state.d.ts +25 -0
- package/dist/workflow-state.js +102 -0
- package/package.json +3 -3
- 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) {
|
package/dist/tools/doctor.d.ts
CHANGED
|
@@ -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>;
|
package/dist/tools/doctor.js
CHANGED
|
@@ -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)
|
|
28
|
-
: `Doctor found ${warnings.length} issue(s);
|
|
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>;
|
package/dist/tools/get-status.js
CHANGED
|
@@ -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
|
}
|