@martinloop/mcp 0.3.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/README.md +90 -106
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +1 -1
  4. package/dist/resources.d.ts +1 -2
  5. package/dist/resources.js +190 -238
  6. package/dist/server-validation.d.ts +1 -1
  7. package/dist/server-validation.js +16 -11
  8. package/dist/server.d.ts +23 -0
  9. package/dist/server.js +152 -7
  10. package/dist/tools/run-dossier.d.ts +3 -1
  11. package/dist/tools/run-dossier.js +40 -2
  12. package/dist/tools/run-loop.d.ts +1 -4
  13. package/dist/tools/run-loop.js +6 -15
  14. package/dist/tools/tool-support.d.ts +2 -1
  15. package/dist/tools/tool-support.js +1 -12
  16. package/dist/vendor/adapters/claude-cli.js +38 -35
  17. package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
  18. package/dist/vendor/adapters/cli-bridge.js +54 -13
  19. package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
  20. package/dist/vendor/adapters/codex-launcher.js +118 -19
  21. package/dist/vendor/adapters/index.d.ts +1 -3
  22. package/dist/vendor/adapters/index.js +1 -3
  23. package/dist/vendor/adapters/openai-compatible.js +57 -18
  24. package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
  25. package/dist/vendor/contracts/context-handoff.d.ts +100 -0
  26. package/dist/vendor/contracts/context-handoff.js +11 -0
  27. package/dist/vendor/contracts/context-manifest.d.ts +195 -0
  28. package/dist/vendor/contracts/context-manifest.js +17 -0
  29. package/dist/vendor/contracts/context-shadow.d.ts +75 -0
  30. package/dist/vendor/contracts/context-shadow.js +8 -0
  31. package/dist/vendor/contracts/exits.d.ts +126 -0
  32. package/dist/vendor/contracts/exits.js +32 -0
  33. package/dist/vendor/contracts/index.d.ts +39 -2
  34. package/dist/vendor/contracts/index.js +22 -0
  35. package/dist/vendor/contracts/mission.d.ts +102 -0
  36. package/dist/vendor/contracts/mission.js +64 -0
  37. package/dist/vendor/contracts/operator.d.ts +3 -1
  38. package/dist/vendor/contracts/operator.js +2 -1
  39. package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
  40. package/dist/vendor/contracts/verified-handoff.js +35 -0
  41. package/dist/vendor/core/context-chain-gate.d.ts +89 -0
  42. package/dist/vendor/core/context-chain-gate.js +173 -0
  43. package/dist/vendor/core/context-compiler.d.ts +53 -0
  44. package/dist/vendor/core/context-compiler.js +261 -0
  45. package/dist/vendor/core/context-handoff.d.ts +42 -0
  46. package/dist/vendor/core/context-handoff.js +167 -0
  47. package/dist/vendor/core/context-shadow.d.ts +39 -0
  48. package/dist/vendor/core/context-shadow.js +200 -0
  49. package/dist/vendor/core/exit-signal.d.ts +88 -0
  50. package/dist/vendor/core/exit-signal.js +339 -0
  51. package/dist/vendor/core/exits.d.ts +41 -0
  52. package/dist/vendor/core/exits.js +258 -0
  53. package/dist/vendor/core/index.d.ts +42 -1
  54. package/dist/vendor/core/index.js +1086 -662
  55. package/dist/vendor/core/mission/aggregate.d.ts +27 -0
  56. package/dist/vendor/core/mission/aggregate.js +42 -0
  57. package/dist/vendor/core/mission/index.d.ts +2 -0
  58. package/dist/vendor/core/mission/index.js +1 -0
  59. package/dist/vendor/core/persistence/compiler.d.ts +8 -0
  60. package/dist/vendor/core/persistence/compiler.js +156 -0
  61. package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
  62. package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
  63. package/dist/vendor/core/persistence/index.d.ts +4 -0
  64. package/dist/vendor/core/persistence/index.js +2 -0
  65. package/dist/vendor/core/persistence/ledger.d.ts +1 -1
  66. package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
  67. package/dist/vendor/core/persistence/mission-store.js +281 -0
  68. package/dist/vendor/core/policy.d.ts +2 -0
  69. package/dist/vendor/core/policy.js +5 -2
  70. package/dist/vendor/core/rollback.js +15 -1
  71. package/dist/vendor/core/savings.d.ts +13 -0
  72. package/dist/vendor/core/savings.js +22 -0
  73. package/dist/vendor/core/termination-store.d.ts +15 -0
  74. package/dist/vendor/core/termination-store.js +44 -0
  75. package/dist/vendor/core/verified-handoff.d.ts +67 -0
  76. package/dist/vendor/core/verified-handoff.js +210 -0
  77. package/package.json +76 -83
  78. package/server.json +21 -21
  79. package/LICENSE +0 -216
  80. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +0 -146
  81. package/dist/vendor/adapters/adapters/src/claude-cli.js +0 -1100
  82. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +0 -103
  83. package/dist/vendor/adapters/adapters/src/cli-bridge.js +0 -569
  84. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +0 -78
  85. package/dist/vendor/adapters/adapters/src/codex-launcher.js +0 -663
  86. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +0 -10
  87. package/dist/vendor/adapters/adapters/src/direct-provider.js +0 -40
  88. package/dist/vendor/adapters/adapters/src/index.d.ts +0 -8
  89. package/dist/vendor/adapters/adapters/src/index.js +0 -8
  90. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +0 -62
  91. package/dist/vendor/adapters/adapters/src/openai-compatible.js +0 -306
  92. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +0 -17
  93. package/dist/vendor/adapters/adapters/src/runtime-support.js +0 -59
  94. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +0 -8
  95. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +0 -40
  96. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +0 -8
  97. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +0 -12
  98. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +0 -9
  99. package/dist/vendor/adapters/adapters/src/verifier-only.js +0 -63
  100. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +0 -48
  101. package/dist/vendor/adapters/contracts/src/context-graph.js +0 -26
  102. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +0 -78
  103. package/dist/vendor/adapters/contracts/src/execution-policy.js +0 -43
  104. package/dist/vendor/adapters/contracts/src/governance.d.ts +0 -24
  105. package/dist/vendor/adapters/contracts/src/governance.js +0 -11
  106. package/dist/vendor/adapters/contracts/src/identity.d.ts +0 -29
  107. package/dist/vendor/adapters/contracts/src/identity.js +0 -30
  108. package/dist/vendor/adapters/contracts/src/index.d.ts +0 -412
  109. package/dist/vendor/adapters/contracts/src/index.js +0 -225
  110. package/dist/vendor/adapters/contracts/src/operator.d.ts +0 -19
  111. package/dist/vendor/adapters/contracts/src/operator.js +0 -11
  112. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +0 -26
  113. package/dist/vendor/adapters/contracts/src/trajectory.js +0 -27
  114. package/dist/vendor/adapters/core/src/compiler.d.ts +0 -52
  115. package/dist/vendor/adapters/core/src/compiler.js +0 -52
  116. package/dist/vendor/adapters/core/src/context-graph.d.ts +0 -3
  117. package/dist/vendor/adapters/core/src/context-graph.js +0 -371
  118. package/dist/vendor/adapters/core/src/context-integrity.d.ts +0 -26
  119. package/dist/vendor/adapters/core/src/context-integrity.js +0 -80
  120. package/dist/vendor/adapters/core/src/grounding.d.ts +0 -38
  121. package/dist/vendor/adapters/core/src/grounding.js +0 -273
  122. package/dist/vendor/adapters/core/src/identity.d.ts +0 -20
  123. package/dist/vendor/adapters/core/src/identity.js +0 -86
  124. package/dist/vendor/adapters/core/src/index.d.ts +0 -186
  125. package/dist/vendor/adapters/core/src/index.js +0 -1495
  126. package/dist/vendor/adapters/core/src/leash.d.ts +0 -48
  127. package/dist/vendor/adapters/core/src/leash.js +0 -497
  128. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +0 -18
  129. package/dist/vendor/adapters/core/src/persistence/compiler.js +0 -34
  130. package/dist/vendor/adapters/core/src/persistence/index.d.ts +0 -10
  131. package/dist/vendor/adapters/core/src/persistence/index.js +0 -5
  132. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +0 -39
  133. package/dist/vendor/adapters/core/src/persistence/integrity.js +0 -252
  134. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +0 -23
  135. package/dist/vendor/adapters/core/src/persistence/ledger.js +0 -9
  136. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +0 -63
  137. package/dist/vendor/adapters/core/src/persistence/memory-store.js +0 -115
  138. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +0 -66
  139. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +0 -140
  140. package/dist/vendor/adapters/core/src/persistence/store.d.ts +0 -89
  141. package/dist/vendor/adapters/core/src/persistence/store.js +0 -125
  142. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +0 -85
  143. package/dist/vendor/adapters/core/src/persistence/trace-store.js +0 -125
  144. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +0 -2
  145. package/dist/vendor/adapters/core/src/policy-compiler.js +0 -195
  146. package/dist/vendor/adapters/core/src/policy.d.ts +0 -156
  147. package/dist/vendor/adapters/core/src/policy.js +0 -704
  148. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +0 -53
  149. package/dist/vendor/adapters/core/src/repo-analyzer.js +0 -263
  150. package/dist/vendor/adapters/core/src/rollback.d.ts +0 -15
  151. package/dist/vendor/adapters/core/src/rollback.js +0 -253
  152. package/dist/vendor/adapters/core/src/routing.d.ts +0 -82
  153. package/dist/vendor/adapters/core/src/routing.js +0 -336
  154. package/dist/vendor/adapters/core/src/test-integrity.d.ts +0 -64
  155. package/dist/vendor/adapters/core/src/test-integrity.js +0 -197
  156. package/dist/vendor/adapters/core/src/trajectory.d.ts +0 -18
  157. package/dist/vendor/adapters/core/src/trajectory.js +0 -295
  158. package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
  159. package/dist/vendor/adapters/stub-agent-cli.js +0 -40
  160. package/dist/vendor/adapters/verifier-only.d.ts +0 -9
  161. package/dist/vendor/adapters/verifier-only.js +0 -63
package/dist/server.js CHANGED
@@ -16,12 +16,14 @@
16
16
  * Manual start:
17
17
  * node dist/server.js
18
18
  */
19
+ import { createServer as createHttpServer } from "node:http";
19
20
  import { fileURLToPath } from "node:url";
20
21
  import { realpathSync } from "node:fs";
21
22
  import path from "node:path";
22
23
  import { resolveRunsRoot } from "./vendor/core/index.js";
23
24
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
24
25
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
25
27
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
26
28
  import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
27
29
  import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
@@ -1401,6 +1403,10 @@ export function createMartinMcpServer(serverInfo) {
1401
1403
  type: "array",
1402
1404
  items: { type: "string" },
1403
1405
  description: "Optional file paths to scope the estimate."
1406
+ },
1407
+ workingDirectory: {
1408
+ type: "string",
1409
+ description: "Optional workspace path for recording the estimate receipt against the same task root you plan to run."
1404
1410
  }
1405
1411
  },
1406
1412
  required: ["objective"]
@@ -1518,7 +1524,7 @@ export function createMartinMcpServer(serverInfo) {
1518
1524
  workingDirectory: output.environment.workingDirectory,
1519
1525
  engine: input.engine,
1520
1526
  receiptScope: output.receiptScope
1521
- });
1527
+ }).catch(() => { });
1522
1528
  return createToolSuccessResult(output, output.summary);
1523
1529
  }
1524
1530
  if (name === "martin_plan") {
@@ -1535,7 +1541,7 @@ export function createMartinMcpServer(serverInfo) {
1535
1541
  repoRoot: output.workingDirectory,
1536
1542
  runsRoot: resolveRunsRoot(process.env)
1537
1543
  }
1538
- });
1544
+ }).catch(() => { });
1539
1545
  return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1540
1546
  }
1541
1547
  if (name === "martin_preflight") {
@@ -1553,7 +1559,7 @@ export function createMartinMcpServer(serverInfo) {
1553
1559
  allowedPaths: output.normalized.allowedPaths,
1554
1560
  deniedPaths: output.normalized.deniedPaths,
1555
1561
  budget: output.normalized.budget
1556
- });
1562
+ }).catch(() => { });
1557
1563
  }
1558
1564
  return createToolSuccessResult(output, output.summary);
1559
1565
  }
@@ -1640,8 +1646,8 @@ export function createMartinMcpServer(serverInfo) {
1640
1646
  const engine = input.engine ?? "claude";
1641
1647
  const budgetUsd = input.budgetUsd ?? 5;
1642
1648
  const fileScope = input.fileScope ?? [];
1643
- const workingDirectory = resolveSafeRepoRoot();
1644
1649
  const runsRoot = resolveRunsRoot(process.env);
1650
+ const workingDirectory = input.workingDirectory ?? resolveSafeRepoRoot();
1645
1651
  const route = classifyRoute({
1646
1652
  objective,
1647
1653
  verificationPlan: [],
@@ -1673,8 +1679,14 @@ export function createMartinMcpServer(serverInfo) {
1673
1679
  step: "estimate",
1674
1680
  workingDirectory,
1675
1681
  objective,
1676
- engine
1677
- });
1682
+ engine,
1683
+ receiptScope: {
1684
+ invocationRoot: resolveSafeRepoRoot(),
1685
+ workingDirectory,
1686
+ repoRoot: workingDirectory,
1687
+ runsRoot
1688
+ }
1689
+ }).catch(() => { });
1678
1690
  return createToolSuccessResult(output, `Estimate: ${route.selectedMode} route (${route.recommendedModelTier}), ~$${route.expectedCostUsd.toFixed(2)} expected cost, ${route.expectedPreworkBurnPct}% pre-work burn. Recommended budget: $${recommendedBudgetUsd.toFixed(2)}.`);
1679
1691
  }
1680
1692
  return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
@@ -1698,6 +1710,114 @@ export async function connectMartinMcpStdioServer() {
1698
1710
  await server.connect(transport);
1699
1711
  return server;
1700
1712
  }
1713
+ export function parseMartinMcpServerArgs(argv) {
1714
+ let transport = "stdio";
1715
+ let host = "127.0.0.1";
1716
+ let port = 3033;
1717
+ let path = "/mcp";
1718
+ for (let index = 0; index < argv.length; index += 1) {
1719
+ const token = argv[index];
1720
+ if (token === "--http") {
1721
+ transport = "http";
1722
+ continue;
1723
+ }
1724
+ if (token === "--port") {
1725
+ const next = argv[index + 1];
1726
+ const parsed = Number(next);
1727
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65535) {
1728
+ throw new Error(`Invalid --port value '${next ?? ""}'.`);
1729
+ }
1730
+ port = parsed;
1731
+ index += 1;
1732
+ continue;
1733
+ }
1734
+ if (token === "--host") {
1735
+ const next = argv[index + 1];
1736
+ if (!next) {
1737
+ throw new Error("Missing value for --host.");
1738
+ }
1739
+ host = next;
1740
+ index += 1;
1741
+ continue;
1742
+ }
1743
+ if (token === "--path") {
1744
+ const next = argv[index + 1];
1745
+ if (!next || !next.startsWith("/")) {
1746
+ throw new Error("MCP HTTP path must start with '/'.");
1747
+ }
1748
+ path = next;
1749
+ index += 1;
1750
+ }
1751
+ }
1752
+ return transport === "http"
1753
+ ? { transport, host, port, path }
1754
+ : { transport };
1755
+ }
1756
+ export async function connectMartinMcpHttpServer(options = {}) {
1757
+ const host = options.host ?? "127.0.0.1";
1758
+ const port = options.port ?? 3033;
1759
+ const path = options.path ?? "/mcp";
1760
+ const mcpServer = createMartinMcpServer();
1761
+ const transport = new StreamableHTTPServerTransport({
1762
+ sessionIdGenerator: undefined
1763
+ });
1764
+ await mcpServer.connect(transport);
1765
+ const httpServer = createHttpServer(async (req, res) => {
1766
+ try {
1767
+ const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? `${host}:${port}`}`);
1768
+ if (requestUrl.pathname !== path) {
1769
+ res.statusCode = 404;
1770
+ res.setHeader("content-type", "application/json");
1771
+ res.end(JSON.stringify({ error: "not_found", path: requestUrl.pathname }));
1772
+ return;
1773
+ }
1774
+ if (req.method === "POST") {
1775
+ const parsedBody = await readJsonBody(req, res);
1776
+ if (res.writableEnded) {
1777
+ return;
1778
+ }
1779
+ await transport.handleRequest(req, res, parsedBody);
1780
+ return;
1781
+ }
1782
+ if (req.method === "GET" || req.method === "DELETE") {
1783
+ await transport.handleRequest(req, res);
1784
+ return;
1785
+ }
1786
+ res.statusCode = 405;
1787
+ res.setHeader("allow", "GET, POST, DELETE");
1788
+ res.end();
1789
+ }
1790
+ catch (error) {
1791
+ if (!res.headersSent) {
1792
+ res.statusCode = 500;
1793
+ res.setHeader("content-type", "application/json");
1794
+ res.end(JSON.stringify({ error: "internal_error", message: error.message }));
1795
+ }
1796
+ }
1797
+ });
1798
+ await new Promise((resolve, reject) => {
1799
+ httpServer.once("error", reject);
1800
+ httpServer.listen(port, host, () => {
1801
+ httpServer.off("error", reject);
1802
+ resolve();
1803
+ });
1804
+ });
1805
+ const address = httpServer.address();
1806
+ const resolvedPort = typeof address === "object" && address ? address.port : port;
1807
+ return {
1808
+ server: httpServer,
1809
+ host,
1810
+ port: resolvedPort,
1811
+ path,
1812
+ endpoint: `http://${host}:${resolvedPort}${path}`,
1813
+ close: async () => {
1814
+ await transport.close();
1815
+ await new Promise((resolve) => {
1816
+ httpServer.close(() => resolve());
1817
+ });
1818
+ }
1819
+ };
1820
+ }
1701
1821
  export function isDirectExecutionEntry(entryPath, moduleUrl = import.meta.url) {
1702
1822
  if (typeof entryPath !== "string" || entryPath.length === 0) {
1703
1823
  return false;
@@ -1718,5 +1838,30 @@ function realPathOrResolved(filePath) {
1718
1838
  }
1719
1839
  }
1720
1840
  if (isDirectExecution()) {
1721
- await connectMartinMcpStdioServer();
1841
+ const startup = parseMartinMcpServerArgs(process.argv.slice(2));
1842
+ if (startup.transport === "http") {
1843
+ await connectMartinMcpHttpServer(startup);
1844
+ }
1845
+ else {
1846
+ await connectMartinMcpStdioServer();
1847
+ }
1848
+ }
1849
+ async function readJsonBody(req, res) {
1850
+ const chunks = [];
1851
+ for await (const chunk of req) {
1852
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1853
+ }
1854
+ const raw = Buffer.concat(chunks).toString("utf8").trim();
1855
+ if (!raw) {
1856
+ return undefined;
1857
+ }
1858
+ try {
1859
+ return JSON.parse(raw);
1860
+ }
1861
+ catch {
1862
+ res.statusCode = 400;
1863
+ res.setHeader("content-type", "application/json");
1864
+ res.end(JSON.stringify({ error: "invalid_json" }));
1865
+ return undefined;
1866
+ }
1722
1867
  }
@@ -2,7 +2,7 @@ import { buildArtifactSummary, buildBudgetSnapshot, buildCostSnapshot, buildEven
2
2
  import { readRunControlState } from "./run-controls.js";
3
3
  import { martinEvalTool } from "./eval.js";
4
4
  import { assessRunRisk } from "./workflow-governance.js";
5
- import type { ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
5
+ import type { ReceiptIntegritySummary, ReceiptScope, TerminationEnvelopeV1, VerifiedHandoffV1 } from "../vendor/contracts/index.js";
6
6
  export interface MartinRunDossierInput {
7
7
  file?: string;
8
8
  loopId?: string;
@@ -45,6 +45,8 @@ export interface MartinRunDossierOutput {
45
45
  };
46
46
  evaluation: Awaited<ReturnType<typeof martinEvalTool>>;
47
47
  control: Awaited<ReturnType<typeof readRunControlState>>;
48
+ terminationEnvelope?: TerminationEnvelopeV1;
49
+ verifiedHandoff: VerifiedHandoffV1;
48
50
  format: "json" | "md" | "github-pr";
49
51
  rendered?: string;
50
52
  inspection: {
@@ -4,6 +4,7 @@ import { loadDetailedLoopRecord, readAttemptArtifactFiles, readLedgerEvents } fr
4
4
  import { readRunControlState } from "./run-controls.js";
5
5
  import { martinEvalTool } from "./eval.js";
6
6
  import { assessRunRisk, inspectRepoSignals } from "./workflow-governance.js";
7
+ import { buildVerifiedHandoff } from "../vendor/core/index.js";
7
8
  export async function martinRunDossierTool(input) {
8
9
  const detail = await loadDetailedLoopRecord(input);
9
10
  const ledgerEvents = await readLedgerEvents(detail);
@@ -54,6 +55,7 @@ export async function martinRunDossierTool(input) {
54
55
  cost: buildCostSnapshot(detail.loop.cost),
55
56
  receiptIntegrity: resolveReceiptIntegrity(detail.loop),
56
57
  ...(detail.loop.receiptScope ? { receiptScope: detail.loop.receiptScope } : {}),
58
+ ...(detail.loop.terminationEnvelope ? { terminationEnvelope: detail.loop.terminationEnvelope } : {}),
57
59
  attempts,
58
60
  verification,
59
61
  artifacts: buildArtifactSummary(detail.loop),
@@ -72,7 +74,37 @@ export async function martinRunDossierTool(input) {
72
74
  ...(detail.canonicalLoopRecordPath ? { canonicalLoopRecordPath: detail.canonicalLoopRecordPath } : {}),
73
75
  ...(detail.ledgerPath ? { ledgerPath: detail.ledgerPath } : {})
74
76
  },
75
- warnings: [...detail.warnings, ...verification.warnings]
77
+ warnings: [...detail.warnings, ...verification.warnings],
78
+ verifiedHandoff: buildVerifiedHandoff({
79
+ loop: detail.loop,
80
+ receiptIntegrity: resolveReceiptIntegrity(detail.loop),
81
+ verification: {
82
+ status: verification.status,
83
+ summary: verification.summary ?? "No verification summary recorded.",
84
+ steps: [],
85
+ warnings: verification.warnings,
86
+ },
87
+ scope: {
88
+ status: detail.loop.task?.allowedPaths?.length ||
89
+ detail.loop.task?.deniedPaths?.length
90
+ ? "WITHIN_SCOPE"
91
+ : "NOT_EVALUATED",
92
+ allowedPaths: detail.loop.task?.allowedPaths ?? [],
93
+ deniedPaths: detail.loop.task?.deniedPaths ?? [],
94
+ changedFiles: [],
95
+ violations: [],
96
+ },
97
+ testIntegrity: {
98
+ status: "NOT_EVALUATED",
99
+ verdict: "NOT_EVALUATED",
100
+ protectedPaths: [],
101
+ changedProtectedPaths: [],
102
+ findings: [],
103
+ summary: "Automatic test-integrity evidence was not recorded for this run.",
104
+ },
105
+ unresolvedWork: verification.status === "passed" ? [] : [verification.summary ?? "Verification did not pass."],
106
+ nextAction: review.nextAction,
107
+ }),
76
108
  };
77
109
  if (output.format !== "json") {
78
110
  output.rendered = renderDossier(output);
@@ -81,7 +113,13 @@ export async function martinRunDossierTool(input) {
81
113
  }
82
114
  function renderDossier(output) {
83
115
  const lines = [
84
- output.format === "github-pr" ? "## MartinLoop Run Dossier" : "# MartinLoop Run Dossier",
116
+ output.format === "github-pr" ? "## MartinLoop Verified Handoff" : "# MartinLoop Verified Handoff",
117
+ "",
118
+ `Outcome: ${output.verifiedHandoff.outcome}`,
119
+ `Definition of Done: ${output.verifiedHandoff.definitionOfDone.acceptanceCriteria.length} acceptance criterion/criteria`,
120
+ `Verification: ${output.verifiedHandoff.verification.status}`,
121
+ `Scope: ${output.verifiedHandoff.scope.status}`,
122
+ `Test Integrity: ${output.verifiedHandoff.testIntegrity.verdict}`,
85
123
  "",
86
124
  `Objective: ${output.loop.objective}`,
87
125
  `Run: ${output.loop.loopId}`,
@@ -1,5 +1,4 @@
1
- import { type SpawnLike } from "../vendor/adapters/index.js";
2
- import { type RunStore, type RunMartinInput, type RunMartinResult } from "../vendor/core/index.js";
1
+ import { type RunStore } from "../vendor/core/index.js";
3
2
  import type { LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
4
3
  import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
5
4
  export interface RunLoopInput {
@@ -44,7 +43,5 @@ export interface RunLoopOutput {
44
43
  artifacts: ReturnType<typeof buildArtifactSummary>;
45
44
  };
46
45
  }
47
- export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
48
46
  export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
49
- export declare function __setRunMartinImplForTests(impl?: (input: RunMartinInput) => Promise<RunMartinResult>): void;
50
47
  export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;
@@ -1,21 +1,13 @@
1
- import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, probeCodexLaunch, resolveCliCommandAvailability, createVerifierOnlyAdapter } from "../vendor/adapters/index.js";
1
+ import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, createStubDirectProviderAdapter, probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
2
2
  import { createFileRunStore, evaluateCostGovernor, resolveRunsRoot, runMartin } from "../vendor/core/index.js";
3
3
  import { normalizeSafePathPatterns, resolveSafeRepoRoot } from "../server-validation.js";
4
4
  import { MartinToolError } from "./tool-errors.js";
5
5
  import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, buildRunRecordPaths, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
6
6
  import { normalizeLoopBudget } from "./workflow-governance.js";
7
- let proofModeVerifierSpawnImpl;
8
7
  let runStoreOverrideForTests;
9
- let runMartinImpl = runMartin;
10
- export function __setProofModeVerifierSpawnImplForTests(spawnImpl) {
11
- proofModeVerifierSpawnImpl = spawnImpl;
12
- }
13
8
  export function __setRunStoreOverrideForTests(store) {
14
9
  runStoreOverrideForTests = store;
15
10
  }
16
- export function __setRunMartinImplForTests(impl) {
17
- runMartinImpl = impl ?? runMartin;
18
- }
19
11
  export async function runLoopTool(input) {
20
12
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
21
13
  const engine = input.engine ?? "claude";
@@ -67,11 +59,10 @@ export async function runLoopTool(input) {
67
59
  }
68
60
  }
69
61
  const adapter = !executionMode.liveMode
70
- ? createVerifierOnlyAdapter({
71
- workingDirectory,
72
- label: "Proof mode adapter (MARTIN_LIVE=false)",
73
- ...(input.verifyTimeoutMs !== undefined ? { verifyTimeoutMs: input.verifyTimeoutMs } : {}),
74
- ...(proofModeVerifierSpawnImpl ? { spawnImpl: proofModeVerifierSpawnImpl } : {})
62
+ ? createStubDirectProviderAdapter({
63
+ label: "Proof mode stub adapter (MARTIN_LIVE=false)",
64
+ providerId: "stub",
65
+ model: "stub"
75
66
  })
76
67
  : engine === "codex"
77
68
  ? createCodexCliAdapter({
@@ -102,7 +93,7 @@ export async function runLoopTool(input) {
102
93
  partialBudget.maxTokens = input.maxTokens;
103
94
  }
104
95
  const budget = normalizeLoopBudget(partialBudget);
105
- const result = await runMartinImpl({
96
+ const result = await runMartin({
106
97
  workspaceId: input.workspaceId ?? "ws_mcp",
107
98
  projectId: input.projectId ?? "proj_mcp",
108
99
  store: runStoreOverrideForTests ?? createFileRunStore({ runsRoot }),
@@ -1,4 +1,4 @@
1
- import type { LoopArtifact, LoopBudget, LoopCost, LoopEvent, LoopTask, ReceiptIntegritySummary, ReceiptScope } from "../vendor/contracts/index.js";
1
+ import type { LoopArtifact, LoopBudget, LoopCost, LoopEvent, LoopTask, ReceiptIntegritySummary, ReceiptScope, TerminationEnvelopeV1 } from "../vendor/contracts/index.js";
2
2
  import { type LedgerEvent, type LoopAttemptRecord, type LoopRunRecord } from "../vendor/core/index.js";
3
3
  export declare const MARTIN_ENGINE_VALUES: readonly ["claude", "codex", "gemini", "openai"];
4
4
  export type MartinEngine = (typeof MARTIN_ENGINE_VALUES)[number];
@@ -14,6 +14,7 @@ export interface InspectableLoopRecord extends Omit<LoopRunRecord, "attempts" |
14
14
  metadata?: Record<string, string>;
15
15
  receiptIntegrity?: ReceiptIntegritySummary;
16
16
  receiptScope?: ReceiptScope;
17
+ terminationEnvelope?: TerminationEnvelopeV1;
17
18
  routingEconomics?: import("../vendor/contracts/index.js").RoutingEconomics;
18
19
  }
19
20
  export interface LoopPreview {
@@ -105,11 +105,6 @@ function discoverCommonInstallDirectories(command) {
105
105
  if (localAppData) {
106
106
  dirs.push(join(localAppData, "OpenAI", "Codex", "bin"));
107
107
  }
108
- // Claude Code native installer places binary at %USERPROFILE%\.local\bin
109
- const userProfile = process.env.USERPROFILE ?? process.env.HOMEPATH;
110
- if (userProfile) {
111
- dirs.push(join(userProfile, ".local", "bin"));
112
- }
113
108
  // Scoop
114
109
  if (home) {
115
110
  dirs.push(join(home, "scoop", "shims"));
@@ -166,13 +161,8 @@ function isOnPathDirectly(command, resolvedPath) {
166
161
  * Returns a platform-specific one-liner install command for a known CLI.
167
162
  */
168
163
  function suggestInstallCommand(command) {
169
- if (command === "claude") {
170
- const installCmd = process.platform === "win32"
171
- ? "irm https://claude.ai/install.ps1 | iex"
172
- : "curl -fsSL https://claude.ai/install.sh | bash";
173
- return `Install with: ${installCmd}`;
174
- }
175
164
  const npmInstalls = {
165
+ claude: "npm install -g @anthropic-ai/claude-code",
176
166
  codex: "npm install -g @openai/codex",
177
167
  gemini: "npm install -g @google/gemini-cli"
178
168
  };
@@ -439,7 +429,6 @@ export function buildSuggestedResourceUris(loopId) {
439
429
  "martin://runs/triage",
440
430
  "martin://runs/latest",
441
431
  "martin://runs/latest/summary",
442
- "martin://runs/latest/receipt",
443
432
  "martin://runs/latest/proof-card",
444
433
  "martin://runs/latest/budget-status",
445
434
  "martin://runs/latest/verifier-evidence",
@@ -11,8 +11,8 @@
11
11
  *
12
12
  * MCP tools and integration tests use the same factories.
13
13
  */
14
- import { readGitExecutionArtifacts, resolveGitRepositoryRoot, runSubprocess, runVerification } from "./cli-bridge.js";
15
- import { buildCodexExecArgs, DEFAULT_CODEX_CHATGPT_MODEL } from "./codex-launcher.js";
14
+ import { readGitChangedFiles, readGitExecutionArtifacts, resolveGitRepositoryRoot, runSubprocess, runVerification } from "./cli-bridge.js";
15
+ import { buildCodexExecArgs } from "./codex-launcher.js";
16
16
  import { createAdapterCapabilities, normalizeStructuredErrors, normalizeUsage } from "./runtime-support.js";
17
17
  // ---------------------------------------------------------------------------
18
18
  // Cost estimation
@@ -381,14 +381,10 @@ function createStreamingUsageInspector(capUsd, modelLabel, promptTokenEstimate)
381
381
  catch {
382
382
  return;
383
383
  }
384
- // result events contain aggregate usage that duplicates previously streamed
385
- // assistant-message usage events — skip to avoid double-counting.
384
+ extractUsageFromEvent(event, terminate);
386
385
  if (event.type === "result") {
387
386
  finalResult = event;
388
387
  }
389
- else {
390
- extractUsageFromEvent(event, terminate);
391
- }
392
388
  };
393
389
  return {
394
390
  onChunk: (chunk, terminate) => {
@@ -514,6 +510,14 @@ export function createAgentCliAdapter(options) {
514
510
  async execute(request) {
515
511
  const prompt = buildPrompt(request);
516
512
  const estimatedUsage = estimateUsage(prompt, options.model ?? options.command, options.command);
513
+ const repoRoot = request.context.repoRoot;
514
+ const gitRepoRoot = repoRoot ? resolveGitRepositoryRoot(repoRoot) : undefined;
515
+ // A governed run may begin in a deliberately dirty workspace. Capture that
516
+ // baseline so existing operator work is neither reported as this run's
517
+ // execution nor treated as scope creep.
518
+ const baselineChangedFiles = gitRepoRoot
519
+ ? new Set(await readGitChangedFiles(gitRepoRoot, 5_000, options.spawnImpl))
520
+ : new Set();
517
521
  // Preflight: bail if projected cost exceeds remaining budget
518
522
  if (request.context.remainingBudgetUsd > 0) {
519
523
  const projected = estimatePromptCost(prompt, options.model ?? "", options.command);
@@ -549,7 +553,8 @@ export function createAgentCliAdapter(options) {
549
553
  timeoutMs,
550
554
  spawnImpl: options.spawnImpl,
551
555
  ...(stdinData === undefined ? {} : { stdinData }),
552
- ...(streamingUsage ? { onStdoutChunk: streamingUsage.onChunk } : {})
556
+ ...(streamingUsage ? { onStdoutChunk: streamingUsage.onChunk } : {}),
557
+ ...(request.signal !== undefined ? { signal: request.signal } : {})
553
558
  });
554
559
  if (agentResult.terminationReason) {
555
560
  const snapshot = streamingUsage?.snapshot();
@@ -695,30 +700,36 @@ export function createAgentCliAdapter(options) {
695
700
  : undefined
696
701
  });
697
702
  const verificationStack = request.context.verificationStack;
698
- const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, verificationStack, options.spawnImpl);
703
+ const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, verificationStack, options.spawnImpl, {
704
+ runId: request.loopId,
705
+ workspaceId: request.workspaceId,
706
+ cwd: workingDirectory,
707
+ });
699
708
  // Check for zero-diff (agent ran but made no file changes)
700
- const repoRoot = request.context.repoRoot;
701
- const gitRepoRoot = repoRoot ? resolveGitRepositoryRoot(repoRoot) : undefined;
702
- let noDiff = false;
703
- if (gitRepoRoot) {
704
- noDiff = await checkNoDiff(gitRepoRoot, options.spawnImpl);
705
- }
709
+ const postRunChangedFiles = gitRepoRoot
710
+ ? await readGitChangedFiles(gitRepoRoot, 5_000, options.spawnImpl)
711
+ : [];
712
+ const agentChangedFiles = postRunChangedFiles.filter((file) => !baselineChangedFiles.has(file));
713
+ const noDiff = gitRepoRoot !== undefined && agentChangedFiles.length === 0;
706
714
  // Extract structured errors from stderr/stdout for better failure context
707
715
  const structuredErrors = normalizeStructuredErrors(extractStructuredErrors(agentResult.stderr, agentResult.stdout));
708
- const executionArtifacts = gitRepoRoot
716
+ const rawExecutionArtifacts = gitRepoRoot
709
717
  ? await readGitExecutionArtifacts(gitRepoRoot, 5000, options.spawnImpl)
710
718
  : undefined;
719
+ const executionArtifacts = rawExecutionArtifacts
720
+ ? {
721
+ ...(agentChangedFiles.length > 0 ? { changedFiles: agentChangedFiles } : {}),
722
+ ...(baselineChangedFiles.size === 0 && rawExecutionArtifacts.diffStats
723
+ ? { diffStats: rawExecutionArtifacts.diffStats }
724
+ : {})
725
+ }
726
+ : undefined;
711
727
  // Scope contract enforcement: check touched files against allowedPaths/deniedPaths
712
728
  let scopeViolations = [];
713
729
  const scopeCtx = request.context;
714
730
  if (gitRepoRoot && (scopeCtx.allowedPaths?.length || scopeCtx.deniedPaths?.length)) {
715
- const diffResult = await runSubprocess("git", ["diff", "--name-only", "HEAD"], {
716
- cwd: gitRepoRoot,
717
- timeoutMs: 5000,
718
- spawnImpl: options.spawnImpl
719
- });
720
- if (diffResult.exitCode === 0 && diffResult.stdout.trim()) {
721
- const touchedFiles = diffResult.stdout.trim().split("\n").filter(Boolean);
731
+ if (agentChangedFiles.length > 0) {
732
+ const touchedFiles = agentChangedFiles;
722
733
  const allowed = scopeCtx.allowedPaths ?? [];
723
734
  const denied = scopeCtx.deniedPaths ?? [];
724
735
  for (const file of touchedFiles) {
@@ -741,7 +752,7 @@ export function createAgentCliAdapter(options) {
741
752
  status: "completed",
742
753
  summary,
743
754
  usage,
744
- verification: { passed: true, summary: verification.summary },
755
+ verification,
745
756
  ...(executionArtifacts
746
757
  ? {
747
758
  execution: {
@@ -786,7 +797,7 @@ export function createAgentCliAdapter(options) {
786
797
  }
787
798
  // Reset tracked files to HEAD so next attempt starts from clean state
788
799
  try {
789
- if (gitRepoRoot) {
800
+ if (gitRepoRoot && baselineChangedFiles.size === 0) {
790
801
  await runSubprocess("git", ["restore", "--staged", "--worktree", "."], {
791
802
  cwd: gitRepoRoot,
792
803
  timeoutMs: 5000
@@ -803,7 +814,7 @@ export function createAgentCliAdapter(options) {
803
814
  ? `${summary}${errorBlock}${scopeViolations.length > 0 ? `\nScope violations: ${scopeViolations.join(", ")}` : ""}`
804
815
  : summary,
805
816
  usage,
806
- verification: { passed: false, summary: verification.summary },
817
+ verification,
807
818
  ...(executionArtifacts
808
819
  ? {
809
820
  execution: {
@@ -906,7 +917,7 @@ export function createCodexCliAdapter(options = {}) {
906
917
  const sandbox = options.sandbox ?? "workspace-write";
907
918
  const workingDirectory = options.workingDirectory ?? process.cwd();
908
919
  const command = options.command ?? "codex";
909
- const launchModel = options.model ?? DEFAULT_CODEX_CHATGPT_MODEL;
920
+ const launchModel = options.model;
910
921
  return createAgentCliAdapter({
911
922
  command,
912
923
  adapterIdSuffix: "codex",
@@ -1152,11 +1163,3 @@ function extractStructuredErrors(stderr, stdout) {
1152
1163
  }
1153
1164
  return errors.slice(0, 10); // cap at 10 to avoid bloating prompts
1154
1165
  }
1155
- async function checkNoDiff(repoRoot, spawnImpl) {
1156
- const result = await runSubprocess("git", ["diff", "--name-only", "HEAD"], {
1157
- cwd: repoRoot,
1158
- timeoutMs: 5000,
1159
- spawnImpl
1160
- });
1161
- return result.exitCode === 0 && result.stdout.trim().length === 0;
1162
- }
@@ -1,11 +1,14 @@
1
1
  import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
2
  import { diffStatsFromNumstat } from "./runtime-support.js";
3
+ import type { VerifierExecutionBinding } from "../core/index.js";
3
4
  export type SpawnLike = (command: string, args?: readonly string[], options?: SpawnOptions) => ChildProcess;
4
5
  export interface SubprocessResult {
5
6
  exitCode: number;
6
7
  stdout: string;
7
8
  stderr: string;
8
9
  timedOut: boolean;
10
+ completed: boolean;
11
+ crashed: boolean;
9
12
  /**
10
13
  * True when the subprocess was terminated early because its combined
11
14
  * stdout+stderr exceeded `maxOutputBytes` — a circuit breaker against
@@ -30,10 +33,13 @@ export interface VerificationOutcome {
30
33
  summary: string;
31
34
  steps: VerificationStepOutcome[];
32
35
  warnings?: string[];
36
+ binding: VerifierExecutionBinding;
33
37
  }
34
38
  export interface VerificationStepOutcome {
35
39
  command: string;
36
40
  launched: boolean;
41
+ completed: boolean;
42
+ crashed: boolean;
37
43
  exitCode?: number;
38
44
  timedOut: boolean;
39
45
  fastFail: boolean;
@@ -60,12 +66,14 @@ export declare function runSubprocess(command: string, args: string[], options:
60
66
  * a runaway final usage figure.
61
67
  */
62
68
  onStdoutChunk?: (chunk: Buffer, terminate: (reason: string) => void) => void;
69
+ /** Optional abort signal — kills the subprocess when aborted. */
70
+ signal?: AbortSignal;
63
71
  }): Promise<SubprocessResult>;
64
72
  export declare function runVerification(commands: string[], cwd: string, timeoutMs: number, verificationStack?: Array<{
65
73
  command: string;
66
74
  type: string;
67
75
  fastFail?: boolean;
68
- }>, spawnImpl?: SpawnLike): Promise<VerificationOutcome>;
76
+ }>, spawnImpl?: SpawnLike, binding?: Omit<VerifierExecutionBinding, "commands">): Promise<VerificationOutcome>;
69
77
  export declare function readGitExecutionArtifacts(repoRoot: string, timeoutMs: number, spawnImpl?: SpawnLike): Promise<{
70
78
  changedFiles?: string[];
71
79
  diffStats?: ReturnType<typeof diffStatsFromNumstat>;