@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/resources.js
CHANGED
|
@@ -3,6 +3,8 @@ import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
|
|
|
3
3
|
import { inspectLoopTool } from "./tools/inspect-loop.js";
|
|
4
4
|
import { martinDoctorTool } from "./tools/doctor.js";
|
|
5
5
|
import { martinTriageRunsTool } from "./tools/triage-runs.js";
|
|
6
|
+
import { martinRunDossierTool } from "./tools/run-dossier.js";
|
|
7
|
+
import { inspectRepoSignals, buildPolicyPackDefinition, buildRepoRiskMap } from "./tools/workflow-governance.js";
|
|
6
8
|
import { buildAttemptSnapshot, buildPersistedLoopPreview, buildVerificationHistorySnapshot, parseAttemptIndex, resolveMartinDiscoveryContext, toPrettyJson } from "./discovery-support.js";
|
|
7
9
|
import { loadDetailedLoopRecord, readLedgerEvents } from "./tools/run-store.js";
|
|
8
10
|
import { invalidArgumentsError, MartinToolError } from "./tools/tool-errors.js";
|
|
@@ -10,13 +12,21 @@ export const MARTIN_STATIC_RESOURCE_URIS = {
|
|
|
10
12
|
serverHealth: "martin://server/health",
|
|
11
13
|
recentRuns: "martin://runs/recent",
|
|
12
14
|
triage: "martin://runs/triage",
|
|
15
|
+
latestRun: "martin://runs/latest",
|
|
13
16
|
latestSummary: "martin://runs/latest/summary",
|
|
14
17
|
latestProofCard: "martin://runs/latest/proof-card",
|
|
15
18
|
latestBudgetStatus: "martin://runs/latest/budget-status",
|
|
16
19
|
latestVerifierEvidence: "martin://runs/latest/verifier-evidence",
|
|
17
20
|
latestRollbackEvidence: "martin://runs/latest/rollback-evidence",
|
|
21
|
+
currentPolicies: "martin://policies/current",
|
|
22
|
+
repoRiskMap: "martin://repo/risk-map",
|
|
23
|
+
verifierResults: "martin://verifiers/results",
|
|
18
24
|
agentNextStep: "martin://agent/next-step",
|
|
19
25
|
mcpUsageGuide: "martin://guides/mcp-usage",
|
|
26
|
+
agentStartGuide: "martin://guides/agent-start",
|
|
27
|
+
commandMapGuide: "martin://guides/command-map",
|
|
28
|
+
ideOnboardingGuide: "martin://guides/ide-onboarding",
|
|
29
|
+
operatingRulesGuide: "martin://guides/operating-rules",
|
|
20
30
|
publishReadinessGuide: "martin://guides/publish-readiness"
|
|
21
31
|
};
|
|
22
32
|
export const MARTIN_RESOURCE_TEMPLATES = [
|
|
@@ -26,6 +36,12 @@ export const MARTIN_RESOURCE_TEMPLATES = [
|
|
|
26
36
|
uriTemplate: "martin://runs/{loopId}",
|
|
27
37
|
description: "Read a canonical Martin loop record using the same selector path as martin_status."
|
|
28
38
|
},
|
|
39
|
+
{
|
|
40
|
+
name: "martin_run_dossier_resource",
|
|
41
|
+
title: "Martin Run Dossier",
|
|
42
|
+
uriTemplate: "martin://runs/{loopId}/dossier",
|
|
43
|
+
description: "Read the dossier-shaped summary for a persisted Martin loop."
|
|
44
|
+
},
|
|
29
45
|
{
|
|
30
46
|
name: "martin_run_attempt",
|
|
31
47
|
title: "Martin Run Attempt",
|
|
@@ -61,6 +77,13 @@ export const MARTIN_STATIC_RESOURCES = [
|
|
|
61
77
|
description: "Priority-ranked Martin runs that likely need attention.",
|
|
62
78
|
mimeType: "application/json"
|
|
63
79
|
},
|
|
80
|
+
{
|
|
81
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.latestRun,
|
|
82
|
+
name: "martin_latest_run",
|
|
83
|
+
title: "Martin Latest Run",
|
|
84
|
+
description: "Full latest-run dossier surface for agents that need more than the compact summary.",
|
|
85
|
+
mimeType: "application/json"
|
|
86
|
+
},
|
|
64
87
|
{
|
|
65
88
|
uri: MARTIN_STATIC_RESOURCE_URIS.latestSummary,
|
|
66
89
|
name: "martin_latest_summary",
|
|
@@ -96,6 +119,27 @@ export const MARTIN_STATIC_RESOURCES = [
|
|
|
96
119
|
description: "Compact rollback and artifact evidence for the latest run.",
|
|
97
120
|
mimeType: "application/json"
|
|
98
121
|
},
|
|
122
|
+
{
|
|
123
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.currentPolicies,
|
|
124
|
+
name: "martin_current_policies",
|
|
125
|
+
title: "Martin Current Policies",
|
|
126
|
+
description: "Local policy-pack presets and the recommended default pack for the current repo.",
|
|
127
|
+
mimeType: "application/json"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.repoRiskMap,
|
|
131
|
+
name: "martin_repo_risk_map",
|
|
132
|
+
title: "Martin Repo Risk Map",
|
|
133
|
+
description: "Sensitive repo surfaces and the recommended policy pack for this workspace.",
|
|
134
|
+
mimeType: "application/json"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.verifierResults,
|
|
138
|
+
name: "martin_latest_verifier_results",
|
|
139
|
+
title: "Martin Latest Verifier Results",
|
|
140
|
+
description: "Latest verifier results alias for compact run evidence.",
|
|
141
|
+
mimeType: "application/json"
|
|
142
|
+
},
|
|
99
143
|
{
|
|
100
144
|
uri: MARTIN_STATIC_RESOURCE_URIS.agentNextStep,
|
|
101
145
|
name: "martin_agent_next_step",
|
|
@@ -110,6 +154,34 @@ export const MARTIN_STATIC_RESOURCES = [
|
|
|
110
154
|
description: "Recommended workflow for using Martin Loop over MCP.",
|
|
111
155
|
mimeType: "text/markdown"
|
|
112
156
|
},
|
|
157
|
+
{
|
|
158
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.agentStartGuide,
|
|
159
|
+
name: "martin_agent_start_guide",
|
|
160
|
+
title: "Martin Agent Start Guide",
|
|
161
|
+
description: "Short agent-facing guide for using Martin with minimal context and low tool bloat.",
|
|
162
|
+
mimeType: "text/markdown"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.commandMapGuide,
|
|
166
|
+
name: "martin_command_map_guide",
|
|
167
|
+
title: "Martin Command Map Guide",
|
|
168
|
+
description: "Command-by-command guide for choosing the right Martin tool or surface.",
|
|
169
|
+
mimeType: "text/markdown"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.ideOnboardingGuide,
|
|
173
|
+
name: "martin_ide_onboarding_guide",
|
|
174
|
+
title: "Martin IDE Onboarding Guide",
|
|
175
|
+
description: "IDE-facing setup guide for making MartinLoop part of the default MCP workflow.",
|
|
176
|
+
mimeType: "text/markdown"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
uri: MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide,
|
|
180
|
+
name: "martin_operating_rules_guide",
|
|
181
|
+
title: "Martin Operating Rules",
|
|
182
|
+
description: "Built-in operating rules that tell agents when Martin commands must be used before work proceeds.",
|
|
183
|
+
mimeType: "text/markdown"
|
|
184
|
+
},
|
|
113
185
|
{
|
|
114
186
|
uri: MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide,
|
|
115
187
|
name: "martin_publish_readiness_guide",
|
|
@@ -147,6 +219,8 @@ export async function readMartinResource(input) {
|
|
|
147
219
|
const triage = await martinTriageRunsTool({ runsDir: context.runsRoot });
|
|
148
220
|
return jsonResource(input.uri, withDiscoveryMetadata(triage, context.runsRoot));
|
|
149
221
|
}
|
|
222
|
+
case MARTIN_STATIC_RESOURCE_URIS.latestRun:
|
|
223
|
+
return jsonResource(input.uri, withDiscoveryMetadata(await martinRunDossierTool({ latest: true, runsDir: context.runsRoot }), context.runsRoot));
|
|
150
224
|
case MARTIN_STATIC_RESOURCE_URIS.latestSummary:
|
|
151
225
|
return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestSummaryResource(context.runsRoot), context.runsRoot));
|
|
152
226
|
case MARTIN_STATIC_RESOURCE_URIS.latestProofCard:
|
|
@@ -157,10 +231,24 @@ export async function readMartinResource(input) {
|
|
|
157
231
|
return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestVerifierEvidenceResource(context.runsRoot), context.runsRoot));
|
|
158
232
|
case MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence:
|
|
159
233
|
return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestRollbackEvidenceResource(context.runsRoot), context.runsRoot));
|
|
234
|
+
case MARTIN_STATIC_RESOURCE_URIS.currentPolicies:
|
|
235
|
+
return jsonResource(input.uri, withDiscoveryMetadata(buildCurrentPoliciesResource(context.workingDirectory), context.runsRoot));
|
|
236
|
+
case MARTIN_STATIC_RESOURCE_URIS.repoRiskMap:
|
|
237
|
+
return jsonResource(input.uri, withDiscoveryMetadata(buildRepoRiskMap(inspectRepoSignals(context.workingDirectory)), context.runsRoot));
|
|
238
|
+
case MARTIN_STATIC_RESOURCE_URIS.verifierResults:
|
|
239
|
+
return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestVerifierEvidenceResource(context.runsRoot), context.runsRoot));
|
|
160
240
|
case MARTIN_STATIC_RESOURCE_URIS.agentNextStep:
|
|
161
241
|
return jsonResource(input.uri, withDiscoveryMetadata(await buildAgentNextStepResource(context.runsRoot), context.runsRoot));
|
|
162
242
|
case MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide:
|
|
163
243
|
return textResource(input.uri, "text/markdown", buildMcpUsageGuide(context.runsRoot));
|
|
244
|
+
case MARTIN_STATIC_RESOURCE_URIS.agentStartGuide:
|
|
245
|
+
return textResource(input.uri, "text/markdown", buildAgentStartGuide(context.runsRoot));
|
|
246
|
+
case MARTIN_STATIC_RESOURCE_URIS.commandMapGuide:
|
|
247
|
+
return textResource(input.uri, "text/markdown", buildCommandMapGuide(context.runsRoot));
|
|
248
|
+
case MARTIN_STATIC_RESOURCE_URIS.ideOnboardingGuide:
|
|
249
|
+
return textResource(input.uri, "text/markdown", buildIdeOnboardingGuide(context.runsRoot));
|
|
250
|
+
case MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide:
|
|
251
|
+
return textResource(input.uri, "text/markdown", buildOperatingRulesGuide(context.runsRoot));
|
|
164
252
|
case MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide:
|
|
165
253
|
return textResource(input.uri, "text/markdown", buildPublishReadinessGuide(context.runsRoot));
|
|
166
254
|
default:
|
|
@@ -203,6 +291,12 @@ async function readDynamicMartinResource(input) {
|
|
|
203
291
|
warnings: [...detail.warnings, ...verification.warnings]
|
|
204
292
|
}, input.runsDir));
|
|
205
293
|
}
|
|
294
|
+
const dossierMatch = /^martin:\/\/runs\/([^/]+)\/dossier$/u.exec(input.uri);
|
|
295
|
+
if (dossierMatch?.[1]) {
|
|
296
|
+
const loopId = decodeURIComponent(dossierMatch[1]);
|
|
297
|
+
const dossier = await martinRunDossierTool({ loopId, runsDir: input.runsDir });
|
|
298
|
+
return jsonResource(input.uri, withDiscoveryMetadata(dossier, input.runsDir));
|
|
299
|
+
}
|
|
206
300
|
const attemptMatch = /^martin:\/\/runs\/([^/]+)\/attempts\/([^/]+)$/u.exec(input.uri);
|
|
207
301
|
if (attemptMatch?.[1] && attemptMatch[2]) {
|
|
208
302
|
const loopId = decodeURIComponent(attemptMatch[1]);
|
|
@@ -254,7 +348,7 @@ async function loadLatestRunForCompactResource(runsRoot) {
|
|
|
254
348
|
async function buildLatestSummaryResource(runsRoot) {
|
|
255
349
|
const latest = await loadLatestRunForCompactResource(runsRoot);
|
|
256
350
|
if (latest.empty || !latest.detail) {
|
|
257
|
-
return compactEmptyState("latest-summary", latest.warnings);
|
|
351
|
+
return compactEmptyState("latest-summary", runsRoot, latest.warnings);
|
|
258
352
|
}
|
|
259
353
|
const ledgerEvents = await readLedgerEvents(latest.detail);
|
|
260
354
|
const loop = latest.detail.loop;
|
|
@@ -294,7 +388,7 @@ async function buildLatestSummaryResource(runsRoot) {
|
|
|
294
388
|
async function buildLatestBudgetStatusResource(runsRoot) {
|
|
295
389
|
const latest = await loadLatestRunForCompactResource(runsRoot);
|
|
296
390
|
if (latest.empty || !latest.detail) {
|
|
297
|
-
return compactEmptyState("budget-status", latest.warnings);
|
|
391
|
+
return compactEmptyState("budget-status", runsRoot, latest.warnings);
|
|
298
392
|
}
|
|
299
393
|
const loop = latest.detail.loop;
|
|
300
394
|
const preview = buildPersistedLoopPreview(loop);
|
|
@@ -325,7 +419,7 @@ async function buildLatestBudgetStatusResource(runsRoot) {
|
|
|
325
419
|
async function buildLatestVerifierEvidenceResource(runsRoot) {
|
|
326
420
|
const latest = await loadLatestRunForCompactResource(runsRoot);
|
|
327
421
|
if (latest.empty || !latest.detail) {
|
|
328
|
-
return compactEmptyState("verifier-evidence", latest.warnings);
|
|
422
|
+
return compactEmptyState("verifier-evidence", runsRoot, latest.warnings);
|
|
329
423
|
}
|
|
330
424
|
const ledgerEvents = await readLedgerEvents(latest.detail);
|
|
331
425
|
const loop = latest.detail.loop;
|
|
@@ -348,7 +442,7 @@ async function buildLatestVerifierEvidenceResource(runsRoot) {
|
|
|
348
442
|
async function buildLatestRollbackEvidenceResource(runsRoot) {
|
|
349
443
|
const latest = await loadLatestRunForCompactResource(runsRoot);
|
|
350
444
|
if (latest.empty || !latest.detail) {
|
|
351
|
-
return compactEmptyState("rollback-evidence", latest.warnings);
|
|
445
|
+
return compactEmptyState("rollback-evidence", runsRoot, latest.warnings);
|
|
352
446
|
}
|
|
353
447
|
const loop = latest.detail.loop;
|
|
354
448
|
const artifacts = loop.artifacts ?? [];
|
|
@@ -374,7 +468,7 @@ async function buildAgentNextStepResource(runsRoot) {
|
|
|
374
468
|
const latest = await loadLatestRunForCompactResource(runsRoot);
|
|
375
469
|
if (latest.empty || !latest.detail) {
|
|
376
470
|
return {
|
|
377
|
-
...compactEmptyState("agent-next-step", latest.warnings),
|
|
471
|
+
...compactEmptyState("agent-next-step", runsRoot, latest.warnings),
|
|
378
472
|
nextTool: "martin_doctor",
|
|
379
473
|
reason: "No run store evidence exists yet; confirm environment and run-store visibility first."
|
|
380
474
|
};
|
|
@@ -398,8 +492,17 @@ async function buildAgentNextStepResource(runsRoot) {
|
|
|
398
492
|
? "failed"
|
|
399
493
|
: "unavailable"
|
|
400
494
|
},
|
|
495
|
+
requiredWorkflow: [
|
|
496
|
+
"martin_doctor",
|
|
497
|
+
"martin_plan",
|
|
498
|
+
"martin_preflight",
|
|
499
|
+
"martin_run",
|
|
500
|
+
"martin_dossier",
|
|
501
|
+
"martin_eval"
|
|
502
|
+
],
|
|
401
503
|
preferredResource: MARTIN_STATIC_RESOURCE_URIS.latestSummary,
|
|
402
|
-
proofCard: MARTIN_STATIC_RESOURCE_URIS.latestProofCard
|
|
504
|
+
proofCard: MARTIN_STATIC_RESOURCE_URIS.latestProofCard,
|
|
505
|
+
operatingRules: MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide
|
|
403
506
|
};
|
|
404
507
|
}
|
|
405
508
|
async function buildLatestProofCardResource(runsRoot) {
|
|
@@ -452,15 +555,30 @@ async function buildLatestProofCardResource(runsRoot) {
|
|
|
452
555
|
""
|
|
453
556
|
].join("\n");
|
|
454
557
|
}
|
|
455
|
-
function compactEmptyState(kind, warnings) {
|
|
558
|
+
function compactEmptyState(kind, runsRoot, warnings) {
|
|
456
559
|
return {
|
|
457
560
|
kind,
|
|
458
561
|
status: "empty",
|
|
562
|
+
runsRoot,
|
|
459
563
|
summary: "No Martin run records are available yet.",
|
|
460
564
|
nextStep: "Run `martin doctor`, create the demo workspace with `npx martin-loop demo`, then run a no-spend stub task with MARTIN_LIVE=false.",
|
|
461
565
|
warnings
|
|
462
566
|
};
|
|
463
567
|
}
|
|
568
|
+
function buildCurrentPoliciesResource(workingDirectory) {
|
|
569
|
+
const signals = inspectRepoSignals(workingDirectory);
|
|
570
|
+
const recommended = buildPolicyPackDefinition(undefined, signals);
|
|
571
|
+
return {
|
|
572
|
+
recommended: recommended.name,
|
|
573
|
+
packs: [
|
|
574
|
+
buildPolicyPackDefinition("solo-founder", signals),
|
|
575
|
+
buildPolicyPackDefinition("startup-team", signals),
|
|
576
|
+
buildPolicyPackDefinition("enterprise-strict", signals),
|
|
577
|
+
buildPolicyPackDefinition("oss-maintainer", signals),
|
|
578
|
+
buildPolicyPackDefinition("security-sensitive", signals)
|
|
579
|
+
]
|
|
580
|
+
};
|
|
581
|
+
}
|
|
464
582
|
function describePrevention(loop) {
|
|
465
583
|
const prevented = [];
|
|
466
584
|
const latestAttempt = loop.attempts.at(-1);
|
|
@@ -506,13 +624,13 @@ function inferAgentNextStep(loop, verification) {
|
|
|
506
624
|
instruction: "Call `martin_preflight` with explicit verifier, budget, and path scope before the next run."
|
|
507
625
|
};
|
|
508
626
|
}
|
|
509
|
-
function buildMcpUsageGuide(
|
|
627
|
+
function buildMcpUsageGuide(runsRoot) {
|
|
510
628
|
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
511
629
|
return `# Martin Loop MCP Usage
|
|
512
630
|
|
|
513
631
|
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
514
632
|
Server version: \`${metadata.serverVersion}\`
|
|
515
|
-
Runs root:
|
|
633
|
+
Runs root: \`${runsRoot}\`
|
|
516
634
|
|
|
517
635
|
Martin Loop exposes governed coding workflows over MCP. Use the server health and run-store views to decide when to preflight, execute, inspect, or escalate.
|
|
518
636
|
|
|
@@ -529,7 +647,7 @@ Martin Loop exposes governed coding workflows over MCP. Use the server health an
|
|
|
529
647
|
## Current Martin MCP Surface
|
|
530
648
|
|
|
531
649
|
- Tools: \`martin_run\`, \`martin_inspect\`, \`martin_status\`, \`martin_doctor\`, \`martin_preflight\`, \`martin_list_runs\`, \`martin_triage_runs\`, \`martin_get_run\`, \`martin_get_attempt\`, \`martin_get_verification_results\`, \`martin_run_dossier\`
|
|
532
|
-
- Static resources: \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\`, \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`${MARTIN_STATIC_RESOURCE_URIS.triage}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide}\`
|
|
650
|
+
- Static resources: \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\`, \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`${MARTIN_STATIC_RESOURCE_URIS.triage}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`, \`${MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide}\`
|
|
533
651
|
- Resource templates: \`martin://runs/{loopId}\`, \`martin://runs/{loopId}/attempts/{attemptIndex}\`, \`martin://runs/{loopId}/verification\`
|
|
534
652
|
- Prompts: \`martin_start\`, \`martin_preflight\`, \`martin_triage\`, \`martin_resume\`, \`martin_prove\`, \`martin_release_check\`, \`martin_governed_coding_kickoff\`, \`martin_debug_failed_run\`, \`martin_publish_readiness_review\`, \`martin_triage_run_store\`
|
|
535
653
|
|
|
@@ -540,13 +658,125 @@ Martin Loop exposes governed coding workflows over MCP. Use the server health an
|
|
|
540
658
|
- Attempt inspection stays aligned with the same loop selectors used by \`martin_status\` and \`martin_inspect\`.
|
|
541
659
|
`;
|
|
542
660
|
}
|
|
543
|
-
function
|
|
661
|
+
function buildAgentStartGuide(runsRoot) {
|
|
662
|
+
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
663
|
+
return `# Agent Start Here
|
|
664
|
+
|
|
665
|
+
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
666
|
+
Server version: \`${metadata.serverVersion}\`
|
|
667
|
+
Runs root: \`${runsRoot}\`
|
|
668
|
+
|
|
669
|
+
Use Martin Loop as the local governor before you spend agent tokens or retry a failed coding loop.
|
|
670
|
+
|
|
671
|
+
## Cheap Default Flow
|
|
672
|
+
|
|
673
|
+
1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\` first. It is the smallest "what should I do now?" payload.
|
|
674
|
+
2. If no runs exist, call \`martin_doctor\`, then \`martin_preflight\`.
|
|
675
|
+
3. If a run exists, read \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\` before reading full run JSON.
|
|
676
|
+
4. If the verifier failed, use prompt \`martin_debug_failed_run\` or \`martin_triage\`.
|
|
677
|
+
5. If you need a shareable receipt, read \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`.
|
|
678
|
+
|
|
679
|
+
## Install Profiles
|
|
680
|
+
|
|
681
|
+
- \`minimal\` (default): doctor, preflight, list, triage, dossier. Good for low tool bloat.
|
|
682
|
+
- \`diagnostic\`: read-only inspection tools for debugging without execution.
|
|
683
|
+
- \`full-local\`: all local tools, including \`martin_run\`.
|
|
684
|
+
- \`github-review\`: review-oriented local profile for PR drafting and reviewer evidence.
|
|
685
|
+
- \`starter\` and \`full\`: compatibility aliases.
|
|
686
|
+
|
|
687
|
+
## Copy-Paste Agent Rule
|
|
688
|
+
|
|
689
|
+
Before running or retrying an autonomous coding task, call Martin. Prefer compact resources first, then full dossiers only when the compact receipt says more evidence is needed. Never claim success unless Martin shows verifier-backed completion or a clear blocked reason.
|
|
690
|
+
`;
|
|
691
|
+
}
|
|
692
|
+
function buildCommandMapGuide(runsRoot) {
|
|
693
|
+
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
694
|
+
return `# Martin Command Map
|
|
695
|
+
|
|
696
|
+
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
697
|
+
Server version: \`${metadata.serverVersion}\`
|
|
698
|
+
Runs root: \`${runsRoot}\`
|
|
699
|
+
|
|
700
|
+
Use this guide when an IDE or agent needs to know which Martin surface to call next.
|
|
701
|
+
|
|
702
|
+
## Default Governed Sequence
|
|
703
|
+
|
|
704
|
+
1. \`martin_doctor\` — confirm environment and visibility.
|
|
705
|
+
2. \`martin_plan\` — propose the bounded approach without spending a run.
|
|
706
|
+
3. \`martin_preflight\` — turn the plan into an explicit contract.
|
|
707
|
+
4. \`martin_run\` — execute only after the contract is safe.
|
|
708
|
+
5. \`martin_dossier\` — inspect what happened and what Martin prevented.
|
|
709
|
+
6. \`martin_eval\` — convert the result into review posture when needed.
|
|
710
|
+
|
|
711
|
+
## When To Use Which Surface
|
|
712
|
+
|
|
713
|
+
- Need the smallest next action: \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`
|
|
714
|
+
- Need a quick receipt: \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`
|
|
715
|
+
- Need a shareable proof object: \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\`
|
|
716
|
+
- Need a full run review: \`martin_dossier\`
|
|
717
|
+
- Need to decide whether to retry: \`martin_triage_runs\`
|
|
718
|
+
- Need IDE setup guidance: \`${MARTIN_STATIC_RESOURCE_URIS.ideOnboardingGuide}\`
|
|
719
|
+
`;
|
|
720
|
+
}
|
|
721
|
+
function buildIdeOnboardingGuide(runsRoot) {
|
|
722
|
+
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
723
|
+
return `# Martin IDE Onboarding
|
|
724
|
+
|
|
725
|
+
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
726
|
+
Server version: \`${metadata.serverVersion}\`
|
|
727
|
+
Runs root: \`${runsRoot}\`
|
|
728
|
+
|
|
729
|
+
Use this guide after the MCP server is installed so the host naturally uses MartinLoop during real work.
|
|
730
|
+
|
|
731
|
+
## IDE Default Flow
|
|
732
|
+
|
|
733
|
+
1. Read \`${MARTIN_STATIC_RESOURCE_URIS.agentStartGuide}\`
|
|
734
|
+
2. Read \`${MARTIN_STATIC_RESOURCE_URIS.operatingRulesGuide}\`
|
|
735
|
+
3. Use prompt \`martin_start\` or \`martin_governed_coding_kickoff\`
|
|
736
|
+
4. Call \`martin_doctor\`
|
|
737
|
+
5. Call \`martin_plan\`
|
|
738
|
+
6. Call \`martin_preflight\`
|
|
739
|
+
7. Only then call \`martin_run\`
|
|
740
|
+
|
|
741
|
+
## Goal
|
|
742
|
+
|
|
743
|
+
MartinLoop should become the normal path for coding work in the host, not a manual extra step the human has to remember.
|
|
744
|
+
`;
|
|
745
|
+
}
|
|
746
|
+
function buildOperatingRulesGuide(runsRoot) {
|
|
747
|
+
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
748
|
+
return `# Martin Operating Rules
|
|
749
|
+
|
|
750
|
+
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
751
|
+
Server version: \`${metadata.serverVersion}\`
|
|
752
|
+
Runs root: \`${runsRoot}\`
|
|
753
|
+
|
|
754
|
+
Use these rules inside the IDE or MCP host as the default MartinLoop operating posture.
|
|
755
|
+
|
|
756
|
+
## Required Sequence Before Real Work
|
|
757
|
+
|
|
758
|
+
1. \`martin_doctor\`
|
|
759
|
+
2. \`martin_plan\`
|
|
760
|
+
3. \`martin_preflight\`
|
|
761
|
+
4. \`martin_run\`
|
|
762
|
+
5. \`martin_dossier\`
|
|
763
|
+
|
|
764
|
+
## Enforcement Rule
|
|
765
|
+
|
|
766
|
+
Do not execute real coding work through MartinLoop until doctor and preflight have both been used for the current task.
|
|
767
|
+
|
|
768
|
+
## Completion Rule
|
|
769
|
+
|
|
770
|
+
Do not claim success until MartinLoop shows verifier-backed completion or an explicit blocked reason backed by evidence.
|
|
771
|
+
`;
|
|
772
|
+
}
|
|
773
|
+
function buildPublishReadinessGuide(runsRoot) {
|
|
544
774
|
const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
|
|
545
775
|
return `# Martin MCP Publish Readiness
|
|
546
776
|
|
|
547
777
|
Discovery revision: \`${metadata.discoveryRevision}\`
|
|
548
778
|
Server version: \`${metadata.serverVersion}\`
|
|
549
|
-
Runs root:
|
|
779
|
+
Runs root: \`${runsRoot}\`
|
|
550
780
|
|
|
551
781
|
Use this guide when reviewing whether the public MCP package is ready to publish, promote, or hand off for registry submission.
|
|
552
782
|
|
|
@@ -565,10 +795,11 @@ Use this guide when reviewing whether the public MCP package is ready to publish
|
|
|
565
795
|
- If a discovery helper exists but is not yet wired into the server, report that as an integration gap instead of treating the capability as fully shipped.
|
|
566
796
|
`;
|
|
567
797
|
}
|
|
568
|
-
function withDiscoveryMetadata(value,
|
|
798
|
+
function withDiscoveryMetadata(value, runsRoot) {
|
|
569
799
|
return {
|
|
570
800
|
metadata: {
|
|
571
|
-
...buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION)
|
|
801
|
+
...buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION),
|
|
802
|
+
...(runsRoot ? { runsRoot } : {})
|
|
572
803
|
},
|
|
573
804
|
value
|
|
574
805
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type ToolName = "martin_run" | "martin_inspect" | "martin_status" | "martin_doctor" | "martin_preflight" | "martin_list_runs" | "martin_triage_runs" | "martin_get_run" | "martin_get_attempt" | "martin_get_verification_results" | "martin_run_dossier";
|
|
1
|
+
type ToolName = "martin_run" | "martin_inspect" | "martin_status" | "martin_doctor" | "martin_plan" | "martin_preflight" | "martin_logs" | "martin_cancel" | "martin_pause" | "martin_continue" | "martin_list_runs" | "martin_triage_runs" | "martin_get_run" | "martin_get_attempt" | "martin_get_verification_results" | "martin_run_dossier" | "martin_dossier" | "martin_eval" | "martin_pr_summary" | "martin_create_pr" | "martin_review_pr";
|
|
2
2
|
export { sanitizeToolErrorMessage } from "./tools/tool-errors.js";
|
|
3
3
|
export declare function validateToolInput(name: ToolName, args: unknown): unknown;
|
|
4
4
|
export declare function resolveSafeRepoRoot(repoRoot?: string, workspaceRoot?: string): string;
|
|
@@ -13,8 +13,16 @@ export function validateToolInput(name, args) {
|
|
|
13
13
|
return validateStatusInput(args);
|
|
14
14
|
case "martin_doctor":
|
|
15
15
|
return validateDoctorInput(args);
|
|
16
|
+
case "martin_plan":
|
|
17
|
+
return validatePlanInput(args);
|
|
16
18
|
case "martin_preflight":
|
|
17
19
|
return validatePreflightInput(args);
|
|
20
|
+
case "martin_logs":
|
|
21
|
+
return validateLogsInput(args);
|
|
22
|
+
case "martin_cancel":
|
|
23
|
+
case "martin_pause":
|
|
24
|
+
case "martin_continue":
|
|
25
|
+
return validateRunControlInput(args);
|
|
18
26
|
case "martin_list_runs":
|
|
19
27
|
return validateListRunsInput(args);
|
|
20
28
|
case "martin_triage_runs":
|
|
@@ -26,7 +34,16 @@ export function validateToolInput(name, args) {
|
|
|
26
34
|
case "martin_get_verification_results":
|
|
27
35
|
return validateGetVerificationResultsInput(args);
|
|
28
36
|
case "martin_run_dossier":
|
|
37
|
+
case "martin_dossier":
|
|
29
38
|
return validateRunDossierInput(args);
|
|
39
|
+
case "martin_eval":
|
|
40
|
+
return validateEvalInput(args);
|
|
41
|
+
case "martin_pr_summary":
|
|
42
|
+
return validateRunDossierInput(args);
|
|
43
|
+
case "martin_create_pr":
|
|
44
|
+
return validateCreatePrInput(args);
|
|
45
|
+
case "martin_review_pr":
|
|
46
|
+
return validateReviewPrInput(args);
|
|
30
47
|
default:
|
|
31
48
|
throw invalidArgumentsError(`Unknown tool: ${name}`, "Refresh the Martin tool manifest and retry.");
|
|
32
49
|
}
|
|
@@ -69,12 +86,27 @@ export function resolveSafeRunsPath(file, runsRoot = resolveRunsRoot(process.env
|
|
|
69
86
|
export function resolveSafeRunsRootPath(runsRoot, fallbackRunsRoot = resolveRunsRoot(process.env)) {
|
|
70
87
|
const baseRoot = resolve(fallbackRunsRoot);
|
|
71
88
|
const candidate = runsRoot ? resolve(baseRoot, runsRoot) : baseRoot;
|
|
89
|
+
if (runsRoot && !existsSync(candidate)) {
|
|
90
|
+
if (!isAbsolute(runsRoot)) {
|
|
91
|
+
assertRawPathWithinRoot(candidate, baseRoot, "runsDir");
|
|
92
|
+
}
|
|
93
|
+
return candidate;
|
|
94
|
+
}
|
|
72
95
|
assertPathWithinRoot(candidate, baseRoot, "runsDir", {
|
|
73
96
|
requireExistingCandidate: false,
|
|
74
97
|
requireExistingRoot: false
|
|
75
98
|
});
|
|
76
99
|
return candidate;
|
|
77
100
|
}
|
|
101
|
+
function assertRawPathWithinRoot(candidatePath, rootPath, name) {
|
|
102
|
+
const relativePath = relative(resolve(rootPath), resolve(candidatePath));
|
|
103
|
+
if (relativePath === "" || relativePath === ".") {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
107
|
+
throw invalidPathError(`Invalid ${name}.`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
78
110
|
export function resolveSafeLoopRecordPath(loopId, runsRoot = resolveRunsRoot(process.env)) {
|
|
79
111
|
const normalizedLoopId = requireLoopId(loopId, "loopId");
|
|
80
112
|
return resolveSafeRunsJsonPath(`${normalizedLoopId}/loop-record.json`, runsRoot);
|
|
@@ -102,9 +134,14 @@ function validateRunInput(args) {
|
|
|
102
134
|
"workingDirectory",
|
|
103
135
|
"engine",
|
|
104
136
|
"model",
|
|
137
|
+
"context",
|
|
138
|
+
"policyPack",
|
|
105
139
|
"maxUsd",
|
|
106
140
|
"maxIterations",
|
|
107
141
|
"maxTokens",
|
|
142
|
+
"maxMinutes",
|
|
143
|
+
"maxFilesChanged",
|
|
144
|
+
"maxCommands",
|
|
108
145
|
"verificationPlan",
|
|
109
146
|
"allowedPaths",
|
|
110
147
|
"deniedPaths",
|
|
@@ -119,9 +156,20 @@ function validateRunInput(args) {
|
|
|
119
156
|
: {}),
|
|
120
157
|
...(engine ? { engine } : {}),
|
|
121
158
|
...optionalString(record.model, "model"),
|
|
159
|
+
...optionalString(record.context, "context"),
|
|
160
|
+
...optionalEnumAsObject(record.policyPack, "policyPack", [
|
|
161
|
+
"solo-founder",
|
|
162
|
+
"startup-team",
|
|
163
|
+
"enterprise-strict",
|
|
164
|
+
"oss-maintainer",
|
|
165
|
+
"security-sensitive"
|
|
166
|
+
]),
|
|
122
167
|
...optionalPositiveNumber(record.maxUsd, "maxUsd"),
|
|
123
168
|
...optionalPositiveInteger(record.maxIterations, "maxIterations"),
|
|
124
169
|
...optionalPositiveInteger(record.maxTokens, "maxTokens"),
|
|
170
|
+
...optionalPositiveInteger(record.maxMinutes, "maxMinutes"),
|
|
171
|
+
...optionalPositiveInteger(record.maxFilesChanged, "maxFilesChanged"),
|
|
172
|
+
...optionalPositiveInteger(record.maxCommands, "maxCommands"),
|
|
125
173
|
...optionalStringArrayAsObject(record.verificationPlan, "verificationPlan"),
|
|
126
174
|
...optionalPathPatternArrayAsObject(record.allowedPaths, "allowedPaths"),
|
|
127
175
|
...optionalPathPatternArrayAsObject(record.deniedPaths, "deniedPaths"),
|
|
@@ -196,6 +244,35 @@ function validateDoctorInput(args) {
|
|
|
196
244
|
function validatePreflightInput(args) {
|
|
197
245
|
return validateRunInput(args);
|
|
198
246
|
}
|
|
247
|
+
function validatePlanInput(args) {
|
|
248
|
+
return validateRunInput(args);
|
|
249
|
+
}
|
|
250
|
+
function validateLogsInput(args) {
|
|
251
|
+
const record = requireObject(args);
|
|
252
|
+
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "limit"]);
|
|
253
|
+
const resolvedRunsDir = record.runsDir !== undefined
|
|
254
|
+
? resolveSafeRunsRootPath(requireString(record.runsDir, "runsDir"))
|
|
255
|
+
: undefined;
|
|
256
|
+
const selectors = [
|
|
257
|
+
record.file !== undefined ? "file" : null,
|
|
258
|
+
record.loopId !== undefined ? "loopId" : null,
|
|
259
|
+
record.latest !== undefined ? "latest" : null
|
|
260
|
+
].filter((value) => value !== null);
|
|
261
|
+
if (selectors.length !== 1) {
|
|
262
|
+
throw invalidSelectorError("Provide exactly one of file, loopId, or latest.", "Choose exactly one run selector per call.");
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
...(record.file !== undefined
|
|
266
|
+
? {
|
|
267
|
+
file: resolveSafeRunsPath(requireString(record.file, "file"), resolvedRunsDir ?? resolveRunsRoot(process.env))
|
|
268
|
+
}
|
|
269
|
+
: {}),
|
|
270
|
+
...(record.loopId !== undefined ? { loopId: requireLoopId(record.loopId, "loopId") } : {}),
|
|
271
|
+
...(resolvedRunsDir ? { runsDir: resolvedRunsDir } : {}),
|
|
272
|
+
...(record.latest === true ? { latest: true } : {}),
|
|
273
|
+
...optionalPositiveInteger(record.limit, "limit")
|
|
274
|
+
};
|
|
275
|
+
}
|
|
199
276
|
function validateListRunsInput(args) {
|
|
200
277
|
const record = requireObject(args);
|
|
201
278
|
assertAllowedKeys(record, [
|
|
@@ -326,8 +403,47 @@ function validateGetVerificationResultsInput(args) {
|
|
|
326
403
|
};
|
|
327
404
|
}
|
|
328
405
|
function validateRunDossierInput(args) {
|
|
406
|
+
const record = requireObject(args);
|
|
407
|
+
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format"]);
|
|
408
|
+
const base = validateGetRunInput(args);
|
|
409
|
+
return {
|
|
410
|
+
...base,
|
|
411
|
+
...optionalEnumAsObject(record.format, "format", ["json", "md", "github-pr"])
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function validateEvalInput(args) {
|
|
329
415
|
return validateGetRunInput(args);
|
|
330
416
|
}
|
|
417
|
+
function validateRunControlInput(args) {
|
|
418
|
+
const record = requireObject(args);
|
|
419
|
+
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "reason", "requestedBy"]);
|
|
420
|
+
const base = validateGetRunInput(args);
|
|
421
|
+
return {
|
|
422
|
+
...base,
|
|
423
|
+
...optionalString(record.reason, "reason"),
|
|
424
|
+
...optionalString(record.requestedBy, "requestedBy")
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
function validateCreatePrInput(args) {
|
|
428
|
+
const record = requireObject(args);
|
|
429
|
+
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format", "title", "base", "execute"]);
|
|
430
|
+
const base = validateRunDossierInput(args);
|
|
431
|
+
return {
|
|
432
|
+
...base,
|
|
433
|
+
...optionalString(record.title, "title"),
|
|
434
|
+
...optionalString(record.base, "base"),
|
|
435
|
+
...optionalBoolean(record.execute, "execute")
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function validateReviewPrInput(args) {
|
|
439
|
+
const record = requireObject(args);
|
|
440
|
+
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "format", "prBody"]);
|
|
441
|
+
const base = validateRunDossierInput(args);
|
|
442
|
+
return {
|
|
443
|
+
...base,
|
|
444
|
+
...optionalString(record.prBody, "prBody")
|
|
445
|
+
};
|
|
446
|
+
}
|
|
331
447
|
function requireObject(value) {
|
|
332
448
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
333
449
|
throw invalidArgumentsError("Tool arguments must be an object.");
|