@martinloop/mcp 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +137 -145
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +1 -1
  4. package/dist/package-version.d.ts +1 -1
  5. package/dist/package-version.js +1 -1
  6. package/dist/resources.d.ts +2 -1
  7. package/dist/resources.js +251 -197
  8. package/dist/server-validation.d.ts +1 -1
  9. package/dist/server-validation.js +11 -16
  10. package/dist/server.d.ts +0 -23
  11. package/dist/server.js +7 -152
  12. package/dist/tools/run-loop.d.ts +2 -1
  13. package/dist/tools/run-loop.js +5 -1
  14. package/dist/tools/tool-support.js +1 -0
  15. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +146 -0
  16. package/dist/vendor/adapters/adapters/src/claude-cli.js +1100 -0
  17. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +103 -0
  18. package/dist/vendor/adapters/adapters/src/cli-bridge.js +569 -0
  19. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +78 -0
  20. package/dist/vendor/adapters/adapters/src/codex-launcher.js +663 -0
  21. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +10 -0
  22. package/dist/vendor/adapters/adapters/src/direct-provider.js +40 -0
  23. package/dist/vendor/adapters/adapters/src/index.d.ts +8 -0
  24. package/dist/vendor/adapters/adapters/src/index.js +8 -0
  25. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +62 -0
  26. package/dist/vendor/adapters/adapters/src/openai-compatible.js +306 -0
  27. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +17 -0
  28. package/dist/vendor/adapters/adapters/src/runtime-support.js +59 -0
  29. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +8 -0
  30. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +40 -0
  31. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +8 -0
  32. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +12 -0
  33. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +9 -0
  34. package/dist/vendor/adapters/adapters/src/verifier-only.js +63 -0
  35. package/dist/vendor/adapters/claude-cli.d.ts +2 -2
  36. package/dist/vendor/adapters/claude-cli.js +28 -42
  37. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +48 -0
  38. package/dist/vendor/adapters/contracts/src/context-graph.js +26 -0
  39. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +78 -0
  40. package/dist/vendor/adapters/contracts/src/execution-policy.js +43 -0
  41. package/dist/vendor/adapters/contracts/src/governance.d.ts +24 -0
  42. package/dist/vendor/adapters/contracts/src/governance.js +11 -0
  43. package/dist/vendor/adapters/contracts/src/identity.d.ts +29 -0
  44. package/dist/vendor/adapters/contracts/src/identity.js +30 -0
  45. package/dist/vendor/adapters/contracts/src/index.d.ts +412 -0
  46. package/dist/vendor/adapters/contracts/src/index.js +225 -0
  47. package/dist/vendor/adapters/contracts/src/operator.d.ts +19 -0
  48. package/dist/vendor/adapters/contracts/src/operator.js +11 -0
  49. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +26 -0
  50. package/dist/vendor/adapters/contracts/src/trajectory.js +27 -0
  51. package/dist/vendor/adapters/core/src/compiler.d.ts +52 -0
  52. package/dist/vendor/adapters/core/src/compiler.js +52 -0
  53. package/dist/vendor/adapters/core/src/context-graph.d.ts +3 -0
  54. package/dist/vendor/adapters/core/src/context-graph.js +371 -0
  55. package/dist/vendor/adapters/core/src/context-integrity.d.ts +26 -0
  56. package/dist/vendor/adapters/core/src/context-integrity.js +80 -0
  57. package/dist/vendor/adapters/core/src/grounding.d.ts +38 -0
  58. package/dist/vendor/adapters/core/src/grounding.js +273 -0
  59. package/dist/vendor/adapters/core/src/identity.d.ts +20 -0
  60. package/dist/vendor/adapters/core/src/identity.js +86 -0
  61. package/dist/vendor/adapters/core/src/index.d.ts +186 -0
  62. package/dist/vendor/adapters/core/src/index.js +1495 -0
  63. package/dist/vendor/adapters/core/src/leash.d.ts +48 -0
  64. package/dist/vendor/adapters/core/src/leash.js +497 -0
  65. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +18 -0
  66. package/dist/vendor/adapters/core/src/persistence/compiler.js +34 -0
  67. package/dist/vendor/adapters/core/src/persistence/index.d.ts +10 -0
  68. package/dist/vendor/adapters/core/src/persistence/index.js +5 -0
  69. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +39 -0
  70. package/dist/vendor/adapters/core/src/persistence/integrity.js +252 -0
  71. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +23 -0
  72. package/dist/vendor/adapters/core/src/persistence/ledger.js +9 -0
  73. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +63 -0
  74. package/dist/vendor/adapters/core/src/persistence/memory-store.js +115 -0
  75. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +66 -0
  76. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +140 -0
  77. package/dist/vendor/adapters/core/src/persistence/store.d.ts +89 -0
  78. package/dist/vendor/adapters/core/src/persistence/store.js +125 -0
  79. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +85 -0
  80. package/dist/vendor/adapters/core/src/persistence/trace-store.js +125 -0
  81. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +2 -0
  82. package/dist/vendor/adapters/core/src/policy-compiler.js +195 -0
  83. package/dist/vendor/adapters/core/src/policy.d.ts +156 -0
  84. package/dist/vendor/adapters/core/src/policy.js +704 -0
  85. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +53 -0
  86. package/dist/vendor/adapters/core/src/repo-analyzer.js +263 -0
  87. package/dist/vendor/adapters/core/src/rollback.d.ts +15 -0
  88. package/dist/vendor/adapters/core/src/rollback.js +253 -0
  89. package/dist/vendor/adapters/core/src/routing.d.ts +82 -0
  90. package/dist/vendor/adapters/core/src/routing.js +336 -0
  91. package/dist/vendor/adapters/core/src/test-integrity.d.ts +64 -0
  92. package/dist/vendor/adapters/core/src/test-integrity.js +197 -0
  93. package/dist/vendor/adapters/core/src/trajectory.d.ts +18 -0
  94. package/dist/vendor/adapters/core/src/trajectory.js +295 -0
  95. package/dist/vendor/adapters/openai-compatible.js +6 -6
  96. package/dist/vendor/adapters/verifier-only.js +2 -2
  97. package/dist/vendor/contracts/index.d.ts +1 -1
  98. package/dist/vendor/core/compiler.js +4 -9
  99. package/dist/vendor/core/index.js +1 -88
  100. package/dist/vendor/core/rollback.js +2 -3
  101. package/package.json +76 -76
  102. package/server.json +21 -21
@@ -14,12 +14,12 @@ export function validateToolInput(name, args) {
14
14
  return validateStatusInput(args);
15
15
  case "martin_doctor":
16
16
  return validateDoctorInput(args);
17
+ case "martin_estimate":
18
+ return validateEstimateInput(args);
17
19
  case "martin_plan":
18
20
  return validatePlanInput(args);
19
21
  case "martin_preflight":
20
22
  return validatePreflightInput(args);
21
- case "martin_estimate":
22
- return validateEstimateInput(args);
23
23
  case "martin_logs":
24
24
  return validateLogsInput(args);
25
25
  case "martin_cancel":
@@ -254,27 +254,22 @@ function validateDoctorInput(args) {
254
254
  ...optionalEnumAsObject(record.engine, "engine", MARTIN_ENGINE_VALUES)
255
255
  };
256
256
  }
257
- function validatePreflightInput(args) {
258
- return validateRunInput(args);
259
- }
260
- function validatePlanInput(args) {
261
- return validateRunInput(args);
262
- }
263
257
  function validateEstimateInput(args) {
264
258
  const record = requireObject(args);
265
- assertAllowedKeys(record, ["objective", "engine", "budgetUsd", "fileScope", "workingDirectory"]);
266
- const engine = optionalEnum(record.engine, "engine", MARTIN_ENGINE_VALUES);
267
- const fileScope = normalizeSafePathPatterns(record.fileScope, "fileScope");
259
+ assertAllowedKeys(record, ["objective", "engine", "budgetUsd", "fileScope"]);
268
260
  return {
269
261
  objective: requireString(record.objective, "objective"),
270
- ...(engine ? { engine } : {}),
262
+ ...optionalEnumAsObject(record.engine, "engine", MARTIN_ENGINE_VALUES),
271
263
  ...optionalPositiveNumber(record.budgetUsd, "budgetUsd"),
272
- ...(fileScope ? { fileScope } : {}),
273
- ...(record.workingDirectory !== undefined
274
- ? { workingDirectory: resolveSafeRepoRoot(requireString(record.workingDirectory, "workingDirectory")) }
275
- : {})
264
+ ...optionalStringArrayAsObject(record.fileScope, "fileScope")
276
265
  };
277
266
  }
267
+ function validatePreflightInput(args) {
268
+ return validateRunInput(args);
269
+ }
270
+ function validatePlanInput(args) {
271
+ return validateRunInput(args);
272
+ }
278
273
  function validateLogsInput(args) {
279
274
  const record = requireObject(args);
280
275
  assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "limit"]);
package/dist/server.d.ts CHANGED
@@ -16,7 +16,6 @@
16
16
  * Manual start:
17
17
  * node dist/server.js
18
18
  */
19
- import { type Server as NodeHttpServer } from "node:http";
20
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
20
  export declare function createMartinMcpServer(serverInfo?: {
22
21
  name?: string;
@@ -89,26 +88,4 @@ export declare function connectMartinMcpStdioServer(): Promise<Server<{
89
88
  } | undefined;
90
89
  } | undefined;
91
90
  }>>;
92
- export interface MartinMcpHttpServerOptions {
93
- host?: string;
94
- port?: number;
95
- path?: string;
96
- }
97
- export interface MartinMcpHttpServerHandle {
98
- server: NodeHttpServer;
99
- host: string;
100
- port: number;
101
- path: string;
102
- endpoint: string;
103
- close: () => Promise<void>;
104
- }
105
- export declare function parseMartinMcpServerArgs(argv: string[]): {
106
- transport: "stdio";
107
- } | {
108
- transport: "http";
109
- host: string;
110
- port: number;
111
- path: string;
112
- };
113
- export declare function connectMartinMcpHttpServer(options?: MartinMcpHttpServerOptions): Promise<MartinMcpHttpServerHandle>;
114
91
  export declare function isDirectExecutionEntry(entryPath: string | undefined, moduleUrl?: string): boolean;
package/dist/server.js CHANGED
@@ -16,14 +16,12 @@
16
16
  * Manual start:
17
17
  * node dist/server.js
18
18
  */
19
- import { createServer as createHttpServer } from "node:http";
20
19
  import { fileURLToPath } from "node:url";
21
20
  import { realpathSync } from "node:fs";
22
21
  import path from "node:path";
23
22
  import { resolveRunsRoot } from "./vendor/core/index.js";
24
23
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
25
24
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
26
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
27
25
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
28
26
  import { MARTIN_MCP_PACKAGE_VERSION } from "./package-version.js";
29
27
  import { getMartinPrompt, listMartinPrompts } from "./prompts.js";
@@ -1403,10 +1401,6 @@ export function createMartinMcpServer(serverInfo) {
1403
1401
  type: "array",
1404
1402
  items: { type: "string" },
1405
1403
  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."
1410
1404
  }
1411
1405
  },
1412
1406
  required: ["objective"]
@@ -1524,7 +1518,7 @@ export function createMartinMcpServer(serverInfo) {
1524
1518
  workingDirectory: output.environment.workingDirectory,
1525
1519
  engine: input.engine,
1526
1520
  receiptScope: output.receiptScope
1527
- }).catch(() => { });
1521
+ });
1528
1522
  return createToolSuccessResult(output, output.summary);
1529
1523
  }
1530
1524
  if (name === "martin_plan") {
@@ -1541,7 +1535,7 @@ export function createMartinMcpServer(serverInfo) {
1541
1535
  repoRoot: output.workingDirectory,
1542
1536
  runsRoot: resolveRunsRoot(process.env)
1543
1537
  }
1544
- }).catch(() => { });
1538
+ });
1545
1539
  return createToolSuccessResult(output, `Plan ready for ${output.objective} with ${output.risk.level} risk and ${output.approvalRecommendation.replace(/_/gu, " ")} approval.`);
1546
1540
  }
1547
1541
  if (name === "martin_preflight") {
@@ -1559,7 +1553,7 @@ export function createMartinMcpServer(serverInfo) {
1559
1553
  allowedPaths: output.normalized.allowedPaths,
1560
1554
  deniedPaths: output.normalized.deniedPaths,
1561
1555
  budget: output.normalized.budget
1562
- }).catch(() => { });
1556
+ });
1563
1557
  }
1564
1558
  return createToolSuccessResult(output, output.summary);
1565
1559
  }
@@ -1646,8 +1640,8 @@ export function createMartinMcpServer(serverInfo) {
1646
1640
  const engine = input.engine ?? "claude";
1647
1641
  const budgetUsd = input.budgetUsd ?? 5;
1648
1642
  const fileScope = input.fileScope ?? [];
1643
+ const workingDirectory = resolveSafeRepoRoot();
1649
1644
  const runsRoot = resolveRunsRoot(process.env);
1650
- const workingDirectory = input.workingDirectory ?? resolveSafeRepoRoot();
1651
1645
  const route = classifyRoute({
1652
1646
  objective,
1653
1647
  verificationPlan: [],
@@ -1679,14 +1673,8 @@ export function createMartinMcpServer(serverInfo) {
1679
1673
  step: "estimate",
1680
1674
  workingDirectory,
1681
1675
  objective,
1682
- engine,
1683
- receiptScope: {
1684
- invocationRoot: resolveSafeRepoRoot(),
1685
- workingDirectory,
1686
- repoRoot: workingDirectory,
1687
- runsRoot
1688
- }
1689
- }).catch(() => { });
1676
+ engine
1677
+ });
1690
1678
  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)}.`);
1691
1679
  }
1692
1680
  return createToolErrorResult(toToolFailure(new Error(`Unknown tool: ${name}`)));
@@ -1710,114 +1698,6 @@ export async function connectMartinMcpStdioServer() {
1710
1698
  await server.connect(transport);
1711
1699
  return server;
1712
1700
  }
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
- }
1821
1701
  export function isDirectExecutionEntry(entryPath, moduleUrl = import.meta.url) {
1822
1702
  if (typeof entryPath !== "string" || entryPath.length === 0) {
1823
1703
  return false;
@@ -1838,30 +1718,5 @@ function realPathOrResolved(filePath) {
1838
1718
  }
1839
1719
  }
1840
1720
  if (isDirectExecution()) {
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
- }
1721
+ await connectMartinMcpStdioServer();
1867
1722
  }
@@ -1,5 +1,5 @@
1
1
  import { type SpawnLike } from "../vendor/adapters/index.js";
2
- import { type RunStore } from "../vendor/core/index.js";
2
+ import { type RunStore, type RunMartinInput, type RunMartinResult } from "../vendor/core/index.js";
3
3
  import type { LoopBudget, ReceiptScope } from "../vendor/contracts/index.js";
4
4
  import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, type MartinEngine } from "./tool-support.js";
5
5
  export interface RunLoopInput {
@@ -46,4 +46,5 @@ export interface RunLoopOutput {
46
46
  }
47
47
  export declare function __setProofModeVerifierSpawnImplForTests(spawnImpl?: SpawnLike): void;
48
48
  export declare function __setRunStoreOverrideForTests(store?: RunStore): void;
49
+ export declare function __setRunMartinImplForTests(impl?: (input: RunMartinInput) => Promise<RunMartinResult>): void;
49
50
  export declare function runLoopTool(input: RunLoopInput): Promise<RunLoopOutput>;
@@ -6,12 +6,16 @@ import { buildArtifactSummary, buildVerificationSummary, buildLoopPreview, build
6
6
  import { normalizeLoopBudget } from "./workflow-governance.js";
7
7
  let proofModeVerifierSpawnImpl;
8
8
  let runStoreOverrideForTests;
9
+ let runMartinImpl = runMartin;
9
10
  export function __setProofModeVerifierSpawnImplForTests(spawnImpl) {
10
11
  proofModeVerifierSpawnImpl = spawnImpl;
11
12
  }
12
13
  export function __setRunStoreOverrideForTests(store) {
13
14
  runStoreOverrideForTests = store;
14
15
  }
16
+ export function __setRunMartinImplForTests(impl) {
17
+ runMartinImpl = impl ?? runMartin;
18
+ }
15
19
  export async function runLoopTool(input) {
16
20
  const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
17
21
  const engine = input.engine ?? "claude";
@@ -98,7 +102,7 @@ export async function runLoopTool(input) {
98
102
  partialBudget.maxTokens = input.maxTokens;
99
103
  }
100
104
  const budget = normalizeLoopBudget(partialBudget);
101
- const result = await runMartin({
105
+ const result = await runMartinImpl({
102
106
  workspaceId: input.workspaceId ?? "ws_mcp",
103
107
  projectId: input.projectId ?? "proj_mcp",
104
108
  store: runStoreOverrideForTests ?? createFileRunStore({ runsRoot }),
@@ -429,6 +429,7 @@ export function buildSuggestedResourceUris(loopId) {
429
429
  "martin://runs/triage",
430
430
  "martin://runs/latest",
431
431
  "martin://runs/latest/summary",
432
+ "martin://runs/latest/receipt",
432
433
  "martin://runs/latest/proof-card",
433
434
  "martin://runs/latest/budget-status",
434
435
  "martin://runs/latest/verifier-evidence",
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Real agent-CLI adapters.
3
+ *
4
+ * Exports a generic factory (`createAgentCliAdapter`) and two pre-configured
5
+ * factories (`createClaudeCliAdapter`, `createCodexCliAdapter`) that spawn
6
+ * the respective AI coding CLI as a child subprocess.
7
+ *
8
+ * Usage in CLI:
9
+ * createClaudeCliAdapter({ workingDirectory: process.cwd() })
10
+ * createCodexCliAdapter({ workingDirectory: process.cwd() })
11
+ *
12
+ * MCP tools and integration tests use the same factories.
13
+ */
14
+ import type { MartinAdapter, MartinAdapterRequest } from "../../../core/index.js";
15
+ import { type SpawnLike } from "./cli-bridge.js";
16
+ /**
17
+ * Given a prompt string, returns the full argv array to pass to spawn().
18
+ * Example for Claude: () => ["--output-format", "json", "--print"]
19
+ * Example for Codex: () => ["exec", "--sandbox", "workspace-write", "-"]
20
+ */
21
+ export type CliArgsBuilder = (prompt: string, request: MartinAdapterRequest) => string[];
22
+ export type CliStdinBuilder = (prompt: string) => string | undefined;
23
+ export interface AgentCliAdapterOptions {
24
+ /** The executable to spawn (e.g. "claude", "codex"). */
25
+ command: string;
26
+ /** Converts a prompt string into the argv array passed to spawn(). */
27
+ argsBuilder: CliArgsBuilder;
28
+ /** Optional stdin payload for CLIs that accept prompt input via stdin or `-`. */
29
+ stdinBuilder?: CliStdinBuilder;
30
+ /** Adapter ID suffix. Defaults to command. */
31
+ adapterIdSuffix?: string;
32
+ /** Working directory for all subprocesses. Defaults to process.cwd(). */
33
+ workingDirectory?: string;
34
+ /** Timeout for the agent subprocess in ms. Defaults to 300_000 (5 min). */
35
+ timeoutMs?: number;
36
+ /** Timeout per verification command in ms. Defaults to 120_000 (2 min). */
37
+ verifyTimeoutMs?: number;
38
+ /** Human-readable label shown in loop records. */
39
+ label?: string;
40
+ /** Model name surfaced in adapter metadata (also used for cost estimation). */
41
+ model?: string;
42
+ /**
43
+ * Whether the CLI outputs JSON when --output-format json is passed.
44
+ * Set to false for CLIs that don't support this flag (e.g. Codex).
45
+ * Defaults to true for Claude.
46
+ */
47
+ supportsJsonOutput?: boolean;
48
+ /**
49
+ * Set when `argsBuilder` requests `--output-format stream-json` (newline-
50
+ * delimited JSON events) rather than single-blob `json`. Enables (a)
51
+ * incremental result parsing that scans for the final `result` event, and
52
+ * (b) a live cumulative-cost circuit breaker that terminates the subprocess
53
+ * the moment projected spend crosses the remaining per-attempt budget,
54
+ * rather than only learning about an overspend after the process exits.
55
+ */
56
+ streamingUsageCap?: boolean;
57
+ /** Test-only override for subprocess spawning. */
58
+ spawnImpl?: SpawnLike;
59
+ }
60
+ export interface ClaudeCliAdapterOptions {
61
+ workingDirectory?: string;
62
+ timeoutMs?: number;
63
+ verifyTimeoutMs?: number;
64
+ label?: string;
65
+ /** Override the model passed via --model flag. */
66
+ model?: string;
67
+ /** Extra args appended after core args (before prompt). */
68
+ extraArgs?: string[];
69
+ spawnImpl?: SpawnLike;
70
+ }
71
+ export interface CodexCliAdapterOptions {
72
+ /** Override the executable or absolute command path used to launch Codex. */
73
+ command?: string;
74
+ workingDirectory?: string;
75
+ timeoutMs?: number;
76
+ verifyTimeoutMs?: number;
77
+ label?: string;
78
+ /** Override the model passed via --model flag. */
79
+ model?: string;
80
+ /**
81
+ * Deprecated no-op retained for compatibility.
82
+ *
83
+ * Codex CLI's supported non-interactive entrypoint is `codex exec`.
84
+ * MartinLoop now uses explicit sandboxing instead of the legacy
85
+ * `--full-auto` compatibility path, which can exit before verifier execution.
86
+ */
87
+ fullAuto?: boolean;
88
+ /** Codex sandbox mode for model-generated commands. Defaults to workspace-write. */
89
+ sandbox?: "read-only" | "workspace-write" | "danger-full-access";
90
+ /** Extra args appended after core args (before prompt). */
91
+ extraArgs?: string[];
92
+ spawnImpl?: SpawnLike;
93
+ }
94
+ export interface GeminiCliAdapterOptions {
95
+ workingDirectory?: string;
96
+ timeoutMs?: number;
97
+ verifyTimeoutMs?: number;
98
+ label?: string;
99
+ /** Override the model passed via --model flag. Defaults to the Gemini `flash` alias. */
100
+ model?: string;
101
+ /** Approval mode for headless Gemini runs. Defaults to yolo for autonomous execution. */
102
+ approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
103
+ /** Enable Gemini sandbox mode when the host is configured for it. Disabled by default. */
104
+ sandbox?: boolean;
105
+ /** Extra args appended after core args. */
106
+ extraArgs?: string[];
107
+ spawnImpl?: SpawnLike;
108
+ }
109
+ export declare function createAgentCliAdapter(options: AgentCliAdapterOptions): MartinAdapter;
110
+ /**
111
+ * Spawns `claude --output-format stream-json --verbose --print "<prompt>" [extraArgs]`.
112
+ *
113
+ * `stream-json` emits one JSON event per line — including per-turn usage on
114
+ * each `assistant` message and a final `result` event carrying the same
115
+ * `result`/`usage`/`total_cost_usd` fields as single-blob `json` output — so
116
+ * MartinLoop can both (a) recover real token usage/cost as before, and
117
+ * (b) watch cumulative spend live and self-terminate the subprocess the
118
+ * moment it crosses the remaining per-attempt budget (see
119
+ * `streamingUsageCap` / `createStreamingUsageInspector`), instead of only
120
+ * discovering an overspend after the whole process has already exited.
121
+ *
122
+ * Requires the Claude Code CLI to be installed and authenticated:
123
+ * https://docs.anthropic.com/claude-code
124
+ */
125
+ export declare function createClaudeCliAdapter(options?: ClaudeCliAdapterOptions): MartinAdapter;
126
+ /**
127
+ * Spawns `codex exec --cd <workspace> --sandbox <mode> [--model <model>] [extraArgs] -`.
128
+ *
129
+ * The prompt is delivered via stdin so Windows shell quoting cannot truncate or
130
+ * reinterpret long MartinLoop prompts that contain paths, deny rules, or budget
131
+ * context.
132
+ *
133
+ * Requires the Codex CLI to be installed and authenticated:
134
+ * npm install -g @openai/codex
135
+ */
136
+ export declare function createCodexCliAdapter(options?: CodexCliAdapterOptions): MartinAdapter;
137
+ /**
138
+ * Spawns `gemini --model <model> --prompt "" --approval-mode <mode> --output-format json [...]`.
139
+ *
140
+ * The prompt is delivered via stdin while forcing headless mode with `--prompt ""`,
141
+ * which keeps large MartinLoop prompts off the command line on Windows.
142
+ *
143
+ * Requires the Gemini CLI to be installed and authenticated:
144
+ * npm install -g @google/gemini-cli
145
+ */
146
+ export declare function createGeminiCliAdapter(options?: GeminiCliAdapterOptions): MartinAdapter;