@martinloop/mcp 0.1.4 → 0.2.5

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 (74) hide show
  1. package/README.md +138 -135
  2. package/dist/discovery-metadata.d.ts +16 -0
  3. package/dist/discovery-metadata.js +62 -0
  4. package/dist/discovery-support.d.ts +62 -0
  5. package/dist/discovery-support.js +224 -0
  6. package/dist/package-version.d.ts +1 -0
  7. package/dist/package-version.js +3 -0
  8. package/dist/prompts.d.ts +13 -0
  9. package/dist/prompts.js +455 -0
  10. package/dist/resources.d.ts +29 -0
  11. package/dist/resources.js +575 -0
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +295 -87
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1135 -247
  16. package/dist/tools/doctor.js +14 -6
  17. package/dist/tools/get-attempt.d.ts +15 -0
  18. package/dist/tools/get-attempt.js +15 -0
  19. package/dist/tools/get-run.d.ts +24 -0
  20. package/dist/tools/get-run.js +23 -0
  21. package/dist/tools/get-status.d.ts +11 -0
  22. package/dist/tools/get-status.js +12 -2
  23. package/dist/tools/get-verification-results.d.ts +14 -0
  24. package/dist/tools/get-verification-results.js +14 -0
  25. package/dist/tools/inspect-loop.d.ts +9 -0
  26. package/dist/tools/inspect-loop.js +11 -2
  27. package/dist/tools/list-runs.d.ts +29 -0
  28. package/dist/tools/list-runs.js +24 -0
  29. package/dist/tools/preflight.js +7 -2
  30. package/dist/tools/run-dossier.d.ts +41 -0
  31. package/dist/tools/run-dossier.js +41 -0
  32. package/dist/tools/run-loop.d.ts +19 -0
  33. package/dist/tools/run-loop.js +41 -3
  34. package/dist/tools/run-store.d.ts +57 -3
  35. package/dist/tools/run-store.js +404 -53
  36. package/dist/tools/tool-errors.d.ts +37 -0
  37. package/dist/tools/tool-errors.js +170 -0
  38. package/dist/tools/tool-response.d.ts +16 -0
  39. package/dist/tools/tool-response.js +34 -0
  40. package/dist/tools/tool-support.d.ts +92 -2
  41. package/dist/tools/tool-support.js +358 -63
  42. package/dist/tools/triage-runs.d.ts +33 -0
  43. package/dist/tools/triage-runs.js +138 -0
  44. package/dist/vendor/adapters/claude-cli.js +0 -1
  45. package/dist/vendor/adapters/cli-bridge.js +0 -1
  46. package/dist/vendor/adapters/direct-provider.js +0 -1
  47. package/dist/vendor/adapters/index.js +0 -1
  48. package/dist/vendor/adapters/runtime-support.js +0 -1
  49. package/dist/vendor/adapters/stub-agent-cli.js +0 -1
  50. package/dist/vendor/adapters/stub-direct-provider.js +0 -1
  51. package/dist/vendor/adapters/verifier-only.js +0 -1
  52. package/dist/vendor/contracts/governance.js +0 -1
  53. package/dist/vendor/contracts/index.d.ts +2 -0
  54. package/dist/vendor/contracts/index.js +1 -1
  55. package/dist/vendor/contracts/operator.d.ts +19 -0
  56. package/dist/vendor/contracts/operator.js +11 -0
  57. package/dist/vendor/core/compiler.js +0 -1
  58. package/dist/vendor/core/context-integrity.js +0 -1
  59. package/dist/vendor/core/grounding.js +0 -1
  60. package/dist/vendor/core/index.js +1 -2
  61. package/dist/vendor/core/leash.js +19 -12
  62. package/dist/vendor/core/persistence/compiler.js +0 -1
  63. package/dist/vendor/core/persistence/index.js +0 -1
  64. package/dist/vendor/core/persistence/ledger.js +0 -1
  65. package/dist/vendor/core/persistence/runs-reader.js +0 -1
  66. package/dist/vendor/core/persistence/store.js +0 -1
  67. package/dist/vendor/core/policy.js +0 -1
  68. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  69. package/dist/vendor/core/red-blue/red-phase.js +135 -0
  70. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  71. package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
  72. package/dist/vendor/core/rollback.js +2 -3
  73. package/package.json +10 -3
  74. package/server.json +2 -2
@@ -0,0 +1,575 @@
1
+ import { buildMartinDiscoveryMetadata } from "./discovery-metadata.js";
2
+ import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
3
+ import { inspectLoopTool } from "./tools/inspect-loop.js";
4
+ import { martinDoctorTool } from "./tools/doctor.js";
5
+ import { martinTriageRunsTool } from "./tools/triage-runs.js";
6
+ import { buildAttemptSnapshot, buildPersistedLoopPreview, buildVerificationHistorySnapshot, parseAttemptIndex, resolveMartinDiscoveryContext, toPrettyJson } from "./discovery-support.js";
7
+ import { loadDetailedLoopRecord, readLedgerEvents } from "./tools/run-store.js";
8
+ import { invalidArgumentsError, MartinToolError } from "./tools/tool-errors.js";
9
+ export const MARTIN_STATIC_RESOURCE_URIS = {
10
+ serverHealth: "martin://server/health",
11
+ recentRuns: "martin://runs/recent",
12
+ triage: "martin://runs/triage",
13
+ latestSummary: "martin://runs/latest/summary",
14
+ latestProofCard: "martin://runs/latest/proof-card",
15
+ latestBudgetStatus: "martin://runs/latest/budget-status",
16
+ latestVerifierEvidence: "martin://runs/latest/verifier-evidence",
17
+ latestRollbackEvidence: "martin://runs/latest/rollback-evidence",
18
+ agentNextStep: "martin://agent/next-step",
19
+ mcpUsageGuide: "martin://guides/mcp-usage",
20
+ publishReadinessGuide: "martin://guides/publish-readiness"
21
+ };
22
+ export const MARTIN_RESOURCE_TEMPLATES = [
23
+ {
24
+ name: "martin_run_record",
25
+ title: "Martin Run Record",
26
+ uriTemplate: "martin://runs/{loopId}",
27
+ description: "Read a canonical Martin loop record using the same selector path as martin_status."
28
+ },
29
+ {
30
+ name: "martin_run_attempt",
31
+ title: "Martin Run Attempt",
32
+ uriTemplate: "martin://runs/{loopId}/attempts/{attemptIndex}",
33
+ description: "Inspect a specific persisted Martin attempt and its linked verification event."
34
+ },
35
+ {
36
+ name: "martin_run_verification",
37
+ title: "Martin Run Verification History",
38
+ uriTemplate: "martin://runs/{loopId}/verification",
39
+ description: "Inspect verification.completed events for a persisted Martin loop."
40
+ }
41
+ ];
42
+ export const MARTIN_STATIC_RESOURCES = [
43
+ {
44
+ uri: MARTIN_STATIC_RESOURCE_URIS.serverHealth,
45
+ name: "martin_server_health",
46
+ title: "Martin Server Health",
47
+ description: "Health and environment readiness for the Martin MCP server.",
48
+ mimeType: "application/json"
49
+ },
50
+ {
51
+ uri: MARTIN_STATIC_RESOURCE_URIS.recentRuns,
52
+ name: "martin_recent_runs",
53
+ title: "Martin Recent Runs",
54
+ description: "Recent Martin loop previews derived from the current runs root.",
55
+ mimeType: "application/json"
56
+ },
57
+ {
58
+ uri: MARTIN_STATIC_RESOURCE_URIS.triage,
59
+ name: "martin_run_triage",
60
+ title: "Martin Run Triage",
61
+ description: "Priority-ranked Martin runs that likely need attention.",
62
+ mimeType: "application/json"
63
+ },
64
+ {
65
+ uri: MARTIN_STATIC_RESOURCE_URIS.latestSummary,
66
+ name: "martin_latest_summary",
67
+ title: "Martin Latest Summary",
68
+ description: "Compact latest-run summary for context-constrained agents.",
69
+ mimeType: "application/json"
70
+ },
71
+ {
72
+ uri: MARTIN_STATIC_RESOURCE_URIS.latestProofCard,
73
+ name: "martin_latest_proof_card",
74
+ title: "Martin Latest Proof Card",
75
+ description: "Small Markdown receipt showing what happened, what Martin prevented, and the next safe action.",
76
+ mimeType: "text/markdown"
77
+ },
78
+ {
79
+ uri: MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus,
80
+ name: "martin_latest_budget_status",
81
+ title: "Martin Latest Budget Status",
82
+ description: "Compact budget, token, and stop-condition snapshot for the latest run.",
83
+ mimeType: "application/json"
84
+ },
85
+ {
86
+ uri: MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence,
87
+ name: "martin_latest_verifier_evidence",
88
+ title: "Martin Latest Verifier Evidence",
89
+ description: "Compact verifier evidence and warnings for the latest run.",
90
+ mimeType: "application/json"
91
+ },
92
+ {
93
+ uri: MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence,
94
+ name: "martin_latest_rollback_evidence",
95
+ title: "Martin Latest Rollback Evidence",
96
+ description: "Compact rollback and artifact evidence for the latest run.",
97
+ mimeType: "application/json"
98
+ },
99
+ {
100
+ uri: MARTIN_STATIC_RESOURCE_URIS.agentNextStep,
101
+ name: "martin_agent_next_step",
102
+ title: "Martin Agent Next Step",
103
+ description: "Single next recommended Martin tool, prompt, or resource for a context-constrained agent.",
104
+ mimeType: "application/json"
105
+ },
106
+ {
107
+ uri: MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide,
108
+ name: "martin_mcp_usage_guide",
109
+ title: "Martin MCP Usage Guide",
110
+ description: "Recommended workflow for using Martin Loop over MCP.",
111
+ mimeType: "text/markdown"
112
+ },
113
+ {
114
+ uri: MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide,
115
+ name: "martin_publish_readiness_guide",
116
+ title: "Martin Publish Readiness Guide",
117
+ description: "Checklist-oriented guide for MCP publish readiness reviews.",
118
+ mimeType: "text/markdown"
119
+ }
120
+ ];
121
+ export function listMartinResources() {
122
+ return {
123
+ resources: MARTIN_STATIC_RESOURCES.map((resource) => ({ ...resource }))
124
+ };
125
+ }
126
+ export function listMartinResourceTemplates() {
127
+ return {
128
+ resourceTemplates: MARTIN_RESOURCE_TEMPLATES.map((template) => ({ ...template }))
129
+ };
130
+ }
131
+ export async function readMartinResource(input) {
132
+ const context = resolveMartinDiscoveryContext(input);
133
+ switch (input.uri) {
134
+ case MARTIN_STATIC_RESOURCE_URIS.serverHealth: {
135
+ const health = await martinDoctorTool({
136
+ runsDir: context.runsRoot,
137
+ workingDirectory: context.workingDirectory,
138
+ ...(context.engine ? { engine: context.engine } : {})
139
+ });
140
+ return jsonResource(input.uri, withDiscoveryMetadata(health, context.runsRoot));
141
+ }
142
+ case MARTIN_STATIC_RESOURCE_URIS.recentRuns: {
143
+ const recentRuns = await inspectLoopTool({ runsDir: context.runsRoot });
144
+ return jsonResource(input.uri, withDiscoveryMetadata(recentRuns, context.runsRoot));
145
+ }
146
+ case MARTIN_STATIC_RESOURCE_URIS.triage: {
147
+ const triage = await martinTriageRunsTool({ runsDir: context.runsRoot });
148
+ return jsonResource(input.uri, withDiscoveryMetadata(triage, context.runsRoot));
149
+ }
150
+ case MARTIN_STATIC_RESOURCE_URIS.latestSummary:
151
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestSummaryResource(context.runsRoot), context.runsRoot));
152
+ case MARTIN_STATIC_RESOURCE_URIS.latestProofCard:
153
+ return textResource(input.uri, "text/markdown", await buildLatestProofCardResource(context.runsRoot));
154
+ case MARTIN_STATIC_RESOURCE_URIS.latestBudgetStatus:
155
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestBudgetStatusResource(context.runsRoot), context.runsRoot));
156
+ case MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence:
157
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestVerifierEvidenceResource(context.runsRoot), context.runsRoot));
158
+ case MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence:
159
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildLatestRollbackEvidenceResource(context.runsRoot), context.runsRoot));
160
+ case MARTIN_STATIC_RESOURCE_URIS.agentNextStep:
161
+ return jsonResource(input.uri, withDiscoveryMetadata(await buildAgentNextStepResource(context.runsRoot), context.runsRoot));
162
+ case MARTIN_STATIC_RESOURCE_URIS.mcpUsageGuide:
163
+ return textResource(input.uri, "text/markdown", buildMcpUsageGuide(context.runsRoot));
164
+ case MARTIN_STATIC_RESOURCE_URIS.publishReadinessGuide:
165
+ return textResource(input.uri, "text/markdown", buildPublishReadinessGuide(context.runsRoot));
166
+ default:
167
+ return readDynamicMartinResource({
168
+ uri: input.uri,
169
+ runsDir: context.runsRoot
170
+ });
171
+ }
172
+ }
173
+ function textResource(uri, mimeType, text) {
174
+ return {
175
+ contents: [
176
+ {
177
+ uri,
178
+ mimeType,
179
+ text
180
+ }
181
+ ]
182
+ };
183
+ }
184
+ function jsonResource(uri, value) {
185
+ return textResource(uri, "application/json", toPrettyJson(value));
186
+ }
187
+ async function readDynamicMartinResource(input) {
188
+ const runMatch = /^martin:\/\/runs\/([^/]+)$/u.exec(input.uri);
189
+ if (runMatch?.[1]) {
190
+ const loopId = decodeURIComponent(runMatch[1]);
191
+ const detail = await loadDetailedLoopRecord({ loopId, runsDir: input.runsDir });
192
+ const ledgerEvents = await readLedgerEvents(detail);
193
+ const loop = detail.loop;
194
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
195
+ return jsonResource(input.uri, withDiscoveryMetadata({
196
+ source: detail.source,
197
+ loop: buildPersistedLoopPreview(loop),
198
+ task: loop.task,
199
+ budget: loop.budget,
200
+ cost: loop.cost,
201
+ attempts: loop.attempts,
202
+ verification,
203
+ warnings: [...detail.warnings, ...verification.warnings]
204
+ }, input.runsDir));
205
+ }
206
+ const attemptMatch = /^martin:\/\/runs\/([^/]+)\/attempts\/([^/]+)$/u.exec(input.uri);
207
+ if (attemptMatch?.[1] && attemptMatch[2]) {
208
+ const loopId = decodeURIComponent(attemptMatch[1]);
209
+ const attemptIndex = parseAttemptIndex(decodeURIComponent(attemptMatch[2]));
210
+ const detail = await loadDetailedLoopRecord({ loopId, runsDir: input.runsDir });
211
+ const ledgerEvents = await readLedgerEvents(detail);
212
+ const loop = detail.loop;
213
+ const attempt = buildAttemptSnapshot(loop, attemptIndex, ledgerEvents);
214
+ return jsonResource(input.uri, withDiscoveryMetadata({
215
+ ...attempt,
216
+ warnings: [...detail.warnings, ...attempt.warnings]
217
+ }, input.runsDir));
218
+ }
219
+ const verificationMatch = /^martin:\/\/runs\/([^/]+)\/verification$/u.exec(input.uri);
220
+ if (verificationMatch?.[1]) {
221
+ const loopId = decodeURIComponent(verificationMatch[1]);
222
+ const detail = await loadDetailedLoopRecord({ loopId, runsDir: input.runsDir });
223
+ const ledgerEvents = await readLedgerEvents(detail);
224
+ const loop = detail.loop;
225
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
226
+ return jsonResource(input.uri, withDiscoveryMetadata({
227
+ ...verification,
228
+ warnings: [...detail.warnings, ...verification.warnings]
229
+ }, input.runsDir));
230
+ }
231
+ throw invalidArgumentsError(`Unknown resource URI '${input.uri}'.`, "Use resources/list or resources/templates/list to discover Martin resource URIs.");
232
+ }
233
+ async function loadLatestRunForCompactResource(runsRoot) {
234
+ try {
235
+ return {
236
+ empty: false,
237
+ detail: await loadDetailedLoopRecord({ latest: true, runsDir: runsRoot }),
238
+ warnings: []
239
+ };
240
+ }
241
+ catch (error) {
242
+ if (error instanceof MartinToolError && error.code === "no_loop_records") {
243
+ return {
244
+ empty: true,
245
+ warnings: [
246
+ "No Martin run records were found yet.",
247
+ "Run `npx martin-loop demo`, then run a governed task or set MARTIN_LIVE=false for a no-spend local proof run."
248
+ ]
249
+ };
250
+ }
251
+ throw error;
252
+ }
253
+ }
254
+ async function buildLatestSummaryResource(runsRoot) {
255
+ const latest = await loadLatestRunForCompactResource(runsRoot);
256
+ if (latest.empty || !latest.detail) {
257
+ return compactEmptyState("latest-summary", latest.warnings);
258
+ }
259
+ const ledgerEvents = await readLedgerEvents(latest.detail);
260
+ const loop = latest.detail.loop;
261
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
262
+ const preview = buildPersistedLoopPreview(loop);
263
+ const nextStep = inferAgentNextStep(loop, verification);
264
+ return {
265
+ kind: "latest-summary",
266
+ loop: preview,
267
+ task: {
268
+ title: loop.task?.title,
269
+ objective: loop.task?.objective,
270
+ verificationPlan: loop.task?.verificationPlan ?? []
271
+ },
272
+ budget: loop.budget,
273
+ cost: {
274
+ actualUsd: loop.cost.actualUsd,
275
+ avoidedUsdEstimate: loop.cost.avoidedUsd ?? 0,
276
+ tokensIn: loop.cost.tokensIn,
277
+ tokensOut: loop.cost.tokensOut,
278
+ estimateLabel: "Cost and avoided-spend fields are local run estimates unless your adapter reported authoritative usage."
279
+ },
280
+ verification: {
281
+ status: verification.latestVerification?.passed === true
282
+ ? "passed"
283
+ : verification.latestVerification?.passed === false
284
+ ? "failed"
285
+ : "unavailable",
286
+ latest: verification.latestVerification,
287
+ count: verification.verificationCount
288
+ },
289
+ whatMartinPrevented: describePrevention(loop),
290
+ nextStep,
291
+ warnings: [...latest.detail.warnings, ...verification.warnings]
292
+ };
293
+ }
294
+ async function buildLatestBudgetStatusResource(runsRoot) {
295
+ const latest = await loadLatestRunForCompactResource(runsRoot);
296
+ if (latest.empty || !latest.detail) {
297
+ return compactEmptyState("budget-status", latest.warnings);
298
+ }
299
+ const loop = latest.detail.loop;
300
+ const preview = buildPersistedLoopPreview(loop);
301
+ return {
302
+ kind: "budget-status",
303
+ loopId: loop.loopId,
304
+ lifecycleState: loop.lifecycleState,
305
+ shouldStop: preview.shouldStop,
306
+ pressure: preview.pressure,
307
+ budget: loop.budget,
308
+ cost: {
309
+ actualUsd: loop.cost.actualUsd,
310
+ avoidedUsdEstimate: loop.cost.avoidedUsd ?? 0,
311
+ tokensIn: loop.cost.tokensIn,
312
+ tokensOut: loop.cost.tokensOut,
313
+ estimateLabel: "Avoided USD and token fields are estimates unless backed by adapter usage receipts."
314
+ },
315
+ remaining: {
316
+ budgetUsd: preview.remainingBudgetUsd,
317
+ iterations: preview.remainingIterations,
318
+ tokens: preview.remainingTokens
319
+ },
320
+ whatStoppedOrWillStopNext: preview.shouldStop
321
+ ? `Martin is at a stop condition: ${loop.lifecycleState}.`
322
+ : "Martin still has budget/iteration/token room, but preflight should run before another attempt."
323
+ };
324
+ }
325
+ async function buildLatestVerifierEvidenceResource(runsRoot) {
326
+ const latest = await loadLatestRunForCompactResource(runsRoot);
327
+ if (latest.empty || !latest.detail) {
328
+ return compactEmptyState("verifier-evidence", latest.warnings);
329
+ }
330
+ const ledgerEvents = await readLedgerEvents(latest.detail);
331
+ const loop = latest.detail.loop;
332
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
333
+ return {
334
+ kind: "verifier-evidence",
335
+ loopId: loop.loopId,
336
+ status: verification.latestVerification?.passed === true
337
+ ? "passed"
338
+ : verification.latestVerification?.passed === false
339
+ ? "failed"
340
+ : "unavailable",
341
+ latestVerification: verification.latestVerification,
342
+ verificationCount: verification.verificationCount,
343
+ verificationHistory: verification.verificationHistory.slice(-3),
344
+ warnings: [...latest.detail.warnings, ...verification.warnings],
345
+ nextSafeAction: inferAgentNextStep(loop, verification)
346
+ };
347
+ }
348
+ async function buildLatestRollbackEvidenceResource(runsRoot) {
349
+ const latest = await loadLatestRunForCompactResource(runsRoot);
350
+ if (latest.empty || !latest.detail) {
351
+ return compactEmptyState("rollback-evidence", latest.warnings);
352
+ }
353
+ const loop = latest.detail.loop;
354
+ const artifacts = loop.artifacts ?? [];
355
+ const rollbackArtifacts = artifacts.filter((artifact) => /rollback|restore|diff|patch/iu.test(`${artifact.kind} ${artifact.label} ${artifact.uri}`));
356
+ return {
357
+ kind: "rollback-evidence",
358
+ loopId: loop.loopId,
359
+ artifactCount: artifacts.length,
360
+ rollbackEvidenceCount: rollbackArtifacts.length,
361
+ rollbackEvidence: rollbackArtifacts.slice(0, 5).map((artifact) => ({
362
+ artifactId: artifact.artifactId,
363
+ kind: artifact.kind,
364
+ label: artifact.label,
365
+ uri: artifact.uri
366
+ })),
367
+ boundary: rollbackArtifacts.length > 0
368
+ ? "Rollback or diff evidence exists in the local run artifacts."
369
+ : "No rollback artifact was found in this compact view; inspect the full dossier before claiming rollback evidence.",
370
+ nextSafeAction: "If rollback evidence is required, call `martin_run_dossier` or read `martin://runs/{loopId}` before proceeding."
371
+ };
372
+ }
373
+ async function buildAgentNextStepResource(runsRoot) {
374
+ const latest = await loadLatestRunForCompactResource(runsRoot);
375
+ if (latest.empty || !latest.detail) {
376
+ return {
377
+ ...compactEmptyState("agent-next-step", latest.warnings),
378
+ nextTool: "martin_doctor",
379
+ reason: "No run store evidence exists yet; confirm environment and run-store visibility first."
380
+ };
381
+ }
382
+ const ledgerEvents = await readLedgerEvents(latest.detail);
383
+ const loop = latest.detail.loop;
384
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
385
+ const nextStep = inferAgentNextStep(loop, verification);
386
+ return {
387
+ kind: "agent-next-step",
388
+ loopId: loop.loopId,
389
+ nextStep,
390
+ compactContext: {
391
+ status: loop.status,
392
+ lifecycleState: loop.lifecycleState,
393
+ attempts: loop.attempts.length,
394
+ latestFailureClass: loop.attempts.at(-1)?.failureClass,
395
+ verifier: verification.latestVerification?.passed === true
396
+ ? "passed"
397
+ : verification.latestVerification?.passed === false
398
+ ? "failed"
399
+ : "unavailable"
400
+ },
401
+ preferredResource: MARTIN_STATIC_RESOURCE_URIS.latestSummary,
402
+ proofCard: MARTIN_STATIC_RESOURCE_URIS.latestProofCard
403
+ };
404
+ }
405
+ async function buildLatestProofCardResource(runsRoot) {
406
+ const latest = await loadLatestRunForCompactResource(runsRoot);
407
+ if (latest.empty || !latest.detail) {
408
+ return [
409
+ "# Martin Proof Card",
410
+ "",
411
+ "Status: no run evidence yet",
412
+ "",
413
+ "What happened: Martin has not found a local run record in this runs root.",
414
+ "What Martin prevented: unknown until a governed run executes.",
415
+ "Next safe action: call `martin_doctor`, run `npx martin-loop demo`, then inspect `martin://runs/latest/summary`.",
416
+ "",
417
+ "Estimate note: no cost or token savings are claimed without run evidence.",
418
+ ""
419
+ ].join("\n");
420
+ }
421
+ const ledgerEvents = await readLedgerEvents(latest.detail);
422
+ const loop = latest.detail.loop;
423
+ const verification = buildVerificationHistorySnapshot(loop, ledgerEvents);
424
+ const preview = buildPersistedLoopPreview(loop);
425
+ const latestAttempt = loop.attempts.at(-1);
426
+ const verifierStatus = verification.latestVerification?.passed === true
427
+ ? "passed"
428
+ : verification.latestVerification?.passed === false
429
+ ? "failed honestly"
430
+ : "unavailable";
431
+ return [
432
+ "# Martin Proof Card",
433
+ "",
434
+ `Run: \`${loop.loopId}\``,
435
+ `Status: ${loop.status} / ${loop.lifecycleState}`,
436
+ `Attempts: ${loop.attempts.length}`,
437
+ `Spend: $${loop.cost.actualUsd.toFixed(2)} actual, $${(loop.cost.avoidedUsd ?? 0).toFixed(2)} avoided estimate`,
438
+ `Tokens: ${loop.cost.tokensIn} in / ${loop.cost.tokensOut} out`,
439
+ `Verifier: ${verifierStatus}`,
440
+ "",
441
+ `What happened: ${latestAttempt?.summary ?? loop.task?.objective ?? "Martin produced a persisted governed run record."}`,
442
+ `What Martin prevented: ${describePrevention(loop).join("; ")}`,
443
+ `Budget state: ${preview.shouldStop ? "stop condition reached" : "budget still available"}; ${preview.remainingIterations} iteration(s) and ${preview.remainingTokens} token(s) remain.`,
444
+ `Next safe action: ${inferAgentNextStep(loop, verification).instruction}`,
445
+ "",
446
+ "Evidence links:",
447
+ `- Summary: \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\``,
448
+ `- Verifier: \`${MARTIN_STATIC_RESOURCE_URIS.latestVerifierEvidence}\``,
449
+ `- Rollback/artifacts: \`${MARTIN_STATIC_RESOURCE_URIS.latestRollbackEvidence}\``,
450
+ "",
451
+ "Estimate note: avoided spend and token savings are directional local estimates unless backed by adapter/provider usage receipts.",
452
+ ""
453
+ ].join("\n");
454
+ }
455
+ function compactEmptyState(kind, warnings) {
456
+ return {
457
+ kind,
458
+ status: "empty",
459
+ summary: "No Martin run records are available yet.",
460
+ 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
+ warnings
462
+ };
463
+ }
464
+ function describePrevention(loop) {
465
+ const prevented = [];
466
+ const latestAttempt = loop.attempts.at(-1);
467
+ if (loop.lifecycleState === "budget_exit" || loop.lifecycleState === "diminishing_returns") {
468
+ prevented.push("unsafe or uneconomical retry continuation");
469
+ }
470
+ if (latestAttempt?.failureClass) {
471
+ prevented.push(`unlabeled failure drift (${latestAttempt.failureClass})`);
472
+ }
473
+ if ((loop.cost.avoidedUsd ?? 0) > 0) {
474
+ prevented.push("estimated additional spend");
475
+ }
476
+ if (loop.status !== "completed") {
477
+ prevented.push("false success claims before verifier-backed completion");
478
+ }
479
+ return prevented.length > 0 ? prevented : ["no extra prevention claim available from this compact record"];
480
+ }
481
+ function inferAgentNextStep(loop, verification) {
482
+ if (verification.latestVerification?.passed === false) {
483
+ return {
484
+ action: "debug_failed_run",
485
+ toolOrResource: "martin_debug_failed_run",
486
+ instruction: `Use prompt \`martin_debug_failed_run\` for loop \`${loop.loopId}\`, then rerun only after the verifier failure is understood.`
487
+ };
488
+ }
489
+ if (loop.lifecycleState === "budget_exit" || loop.lifecycleState === "diminishing_returns") {
490
+ return {
491
+ action: "triage_before_retry",
492
+ toolOrResource: "martin_triage_runs",
493
+ instruction: "Call `martin_triage_runs` and inspect the proof card before spending another attempt."
494
+ };
495
+ }
496
+ if (loop.status === "completed" && verification.latestVerification?.passed === true) {
497
+ return {
498
+ action: "prove_and_share",
499
+ toolOrResource: MARTIN_STATIC_RESOURCE_URIS.latestProofCard,
500
+ instruction: "Read the proof card and full dossier before sharing or promoting the result."
501
+ };
502
+ }
503
+ return {
504
+ action: "preflight_next_attempt",
505
+ toolOrResource: "martin_preflight",
506
+ instruction: "Call `martin_preflight` with explicit verifier, budget, and path scope before the next run."
507
+ };
508
+ }
509
+ function buildMcpUsageGuide(_runsRoot) {
510
+ const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
511
+ return `# Martin Loop MCP Usage
512
+
513
+ Discovery revision: \`${metadata.discoveryRevision}\`
514
+ Server version: \`${metadata.serverVersion}\`
515
+ Runs root: inspect \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` when you need the active local path.
516
+
517
+ 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
+
519
+ ## Recommended Flow
520
+
521
+ 1. Read \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` or call \`martin_doctor\` to confirm the environment and run-store visibility.
522
+ 2. Use the \`martin_governed_coding_kickoff\` prompt to frame a governed coding request.
523
+ 3. Call \`martin_preflight\` before \`martin_run\` when the task or safety envelope is non-trivial.
524
+ 4. After execution, inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\`, \`martin://runs/{loopId}\`, and \`martin://runs/{loopId}/verification\`.
525
+ 5. For low-context agents, read \`${MARTIN_STATIC_RESOURCE_URIS.agentNextStep}\`, \`${MARTIN_STATIC_RESOURCE_URIS.latestSummary}\`, or \`${MARTIN_STATIC_RESOURCE_URIS.latestProofCard}\` before asking for full JSON.
526
+ 6. Read \`${MARTIN_STATIC_RESOURCE_URIS.triage}\` or call \`martin_triage_runs\` to prioritize which run needs attention first.
527
+ 7. Use \`martin_debug_failed_run\` when a loop exits failed, budget-bound, or escalated.
528
+
529
+ ## Current Martin MCP Surface
530
+
531
+ - 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}\`
533
+ - Resource templates: \`martin://runs/{loopId}\`, \`martin://runs/{loopId}/attempts/{attemptIndex}\`, \`martin://runs/{loopId}/verification\`
534
+ - 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
+
536
+ ## Notes
537
+
538
+ - Discovery helpers read the existing Martin run-store; they do not create new schema.
539
+ - Verification history is derived from persisted \`verification.completed\` evidence in loop records and ledgers.
540
+ - Attempt inspection stays aligned with the same loop selectors used by \`martin_status\` and \`martin_inspect\`.
541
+ `;
542
+ }
543
+ function buildPublishReadinessGuide(_runsRoot) {
544
+ const metadata = buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION);
545
+ return `# Martin MCP Publish Readiness
546
+
547
+ Discovery revision: \`${metadata.discoveryRevision}\`
548
+ Server version: \`${metadata.serverVersion}\`
549
+ Runs root: inspect \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` when you need the active local path.
550
+
551
+ Use this guide when reviewing whether the public MCP package is ready to publish, promote, or hand off for registry submission.
552
+
553
+ ## Review Areas
554
+
555
+ 1. Environment health: confirm \`${MARTIN_STATIC_RESOURCE_URIS.serverHealth}\` shows a sane working directory, runs root, and engine availability for the target mode.
556
+ 2. Discovery surface: verify tools, prompts, static resources, and resource templates are all discoverable and named consistently.
557
+ 3. Run evidence: inspect \`${MARTIN_STATIC_RESOURCE_URIS.recentRuns}\` and, when relevant, \`martin://runs/{loopId}/verification\` to confirm verification outcomes are explainable from persisted data.
558
+ 4. Packaging lane: run the MCP package test, build, and smoke-pack commands before calling the package publish-ready.
559
+ 5. Findings-first reporting: call out missing schema, integration gaps, triage-critical runs, and verification blockers before summarizing strengths.
560
+
561
+ ## Evidence Expectations
562
+
563
+ - Prefer concrete run IDs, attempt indices, and verification summaries over vague statements.
564
+ - Distinguish local package validation from live registry readiness.
565
+ - 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
+ `;
567
+ }
568
+ function withDiscoveryMetadata(value, _runsRoot) {
569
+ return {
570
+ metadata: {
571
+ ...buildMartinDiscoveryMetadata(MARTIN_MCP_PACKAGE_VERSION)
572
+ },
573
+ value
574
+ };
575
+ }
@@ -1,10 +1,9 @@
1
- type ToolName = "martin_doctor" | "martin_preflight" | "martin_run" | "martin_inspect" | "martin_status";
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";
2
+ export { sanitizeToolErrorMessage } from "./tools/tool-errors.js";
2
3
  export declare function validateToolInput(name: ToolName, args: unknown): unknown;
3
- export declare function sanitizeToolErrorMessage(error: unknown): string;
4
4
  export declare function resolveSafeRepoRoot(repoRoot?: string, workspaceRoot?: string): string;
5
5
  export declare function resolveSafeRunsJsonPath(file: string, runsRoot?: string): string;
6
6
  export declare function resolveSafeRunsPath(file: string, runsRoot?: string): string;
7
7
  export declare function resolveSafeRunsRootPath(runsRoot?: string, fallbackRunsRoot?: string): string;
8
8
  export declare function resolveSafeLoopRecordPath(loopId: string, runsRoot?: string): string;
9
9
  export declare function normalizeSafePathPatterns(value: unknown, name: string): string[] | undefined;
10
- export {};