@martinloop/mcp 0.3.9 → 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.
- package/README.md +82 -141
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +47 -95
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +152 -7
- package/dist/tools/preflight.d.ts +2 -0
- package/dist/tools/preflight.js +42 -2
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +1 -4
- package/dist/tools/run-loop.js +6 -15
- package/dist/tools/tool-support.d.ts +2 -1
- package/dist/tools/tool-support.js +1 -12
- package/dist/vendor/adapters/claude-cli.js +38 -35
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +1 -3
- package/dist/vendor/adapters/index.js +1 -3
- package/dist/vendor/adapters/openai-compatible.js +51 -12
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/delivery.d.ts +47 -0
- package/dist/vendor/contracts/delivery.js +18 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +41 -2
- package/dist/vendor/contracts/index.js +24 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
- package/dist/vendor/contracts/verified-handoff.js +35 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/delivery/index.d.ts +6 -0
- package/dist/vendor/core/delivery/index.js +4 -0
- package/dist/vendor/core/delivery/message-client.d.ts +18 -0
- package/dist/vendor/core/delivery/message-client.js +38 -0
- package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
- package/dist/vendor/core/delivery/message-ledger.js +97 -0
- package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
- package/dist/vendor/core/delivery/message-schema.js +111 -0
- package/dist/vendor/core/delivery/update-check.d.ts +19 -0
- package/dist/vendor/core/delivery/update-check.js +107 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -0
- package/dist/vendor/core/index.d.ts +44 -1
- package/dist/vendor/core/index.js +1088 -662
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +5 -2
- package/dist/vendor/core/rollback.js +15 -1
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +67 -0
- package/dist/vendor/core/verified-handoff.js +210 -0
- package/package.json +3 -13
- package/server.json +3 -3
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/verifier-only.js +0 -63
|
@@ -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);
|
|
19
17
|
case "martin_plan":
|
|
20
18
|
return validatePlanInput(args);
|
|
21
19
|
case "martin_preflight":
|
|
22
20
|
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,22 +254,27 @@ 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
|
+
}
|
|
257
263
|
function validateEstimateInput(args) {
|
|
258
264
|
const record = requireObject(args);
|
|
259
|
-
assertAllowedKeys(record, ["objective", "engine", "budgetUsd", "fileScope"]);
|
|
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");
|
|
260
268
|
return {
|
|
261
269
|
objective: requireString(record.objective, "objective"),
|
|
262
|
-
...
|
|
270
|
+
...(engine ? { engine } : {}),
|
|
263
271
|
...optionalPositiveNumber(record.budgetUsd, "budgetUsd"),
|
|
264
|
-
...
|
|
272
|
+
...(fileScope ? { fileScope } : {}),
|
|
273
|
+
...(record.workingDirectory !== undefined
|
|
274
|
+
? { workingDirectory: resolveSafeRepoRoot(requireString(record.workingDirectory, "workingDirectory")) }
|
|
275
|
+
: {})
|
|
265
276
|
};
|
|
266
277
|
}
|
|
267
|
-
function validatePreflightInput(args) {
|
|
268
|
-
return validateRunInput(args);
|
|
269
|
-
}
|
|
270
|
-
function validatePlanInput(args) {
|
|
271
|
-
return validateRunInput(args);
|
|
272
|
-
}
|
|
273
278
|
function validateLogsInput(args) {
|
|
274
279
|
const record = requireObject(args);
|
|
275
280
|
assertAllowedKeys(record, ["file", "loopId", "runsDir", "latest", "limit"]);
|
package/dist/server.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* Manual start:
|
|
17
17
|
* node dist/server.js
|
|
18
18
|
*/
|
|
19
|
+
import { type Server as NodeHttpServer } from "node:http";
|
|
19
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
20
21
|
export declare function createMartinMcpServer(serverInfo?: {
|
|
21
22
|
name?: string;
|
|
@@ -88,4 +89,26 @@ export declare function connectMartinMcpStdioServer(): Promise<Server<{
|
|
|
88
89
|
} | undefined;
|
|
89
90
|
} | undefined;
|
|
90
91
|
}>>;
|
|
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>;
|
|
91
114
|
export declare function isDirectExecutionEntry(entryPath: string | undefined, moduleUrl?: string): boolean;
|
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
|
-
|
|
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
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CodexHostPlatform } from "../vendor/adapters/index.js";
|
|
2
|
+
import type { UpdateAvailableField } from "../vendor/contracts/index.js";
|
|
2
3
|
import { type MartinEngine } from "./tool-support.js";
|
|
3
4
|
import { buildPolicyPackDefinition, type MartinPlanProposal, type MartinPolicyPack, type MartinRiskAssessment, type MartinRunContract } from "./workflow-governance.js";
|
|
4
5
|
export interface MartinPreflightInput {
|
|
@@ -108,5 +109,6 @@ export interface MartinPreflightOutput {
|
|
|
108
109
|
risk: MartinRiskAssessment;
|
|
109
110
|
runContract: MartinRunContract;
|
|
110
111
|
plan: MartinPlanProposal;
|
|
112
|
+
updateAvailable?: UpdateAvailableField;
|
|
111
113
|
}
|
|
112
114
|
export declare function martinPreflightTool(input: MartinPreflightInput): Promise<MartinPreflightOutput>;
|
package/dist/tools/preflight.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { probeCodexLaunch, resolveCliCommandAvailability } from "../vendor/adapters/index.js";
|
|
2
|
-
import { resolveRunsRoot } from "../vendor/core/index.js";
|
|
2
|
+
import { fetchSelectedMessage, getMcpInstalledVersion, isCooldownExpired, isDismissed, isNewerVersion, loadDeliveryRecord, recordShown, resolveDefaultLedgerPath, resolveRunsRoot, saveDeliveryRecord, } from "../vendor/core/index.js";
|
|
3
|
+
import { MARTIN_MCP_PACKAGE_VERSION } from "../package-version.js";
|
|
3
4
|
import { resolveSafeRepoRoot } from "../server-validation.js";
|
|
4
5
|
import { createSkippedCliAvailability, formatUsd, getEngineAvailability, resolveExecutionMode } from "./tool-support.js";
|
|
5
6
|
import { buildPlanProposal, normalizeLoopBudget, buildRunContract, buildPolicyPackDefinition, inspectRepoSignals } from "./workflow-governance.js";
|
|
6
7
|
export async function martinPreflightTool(input) {
|
|
8
|
+
// Fire delivery fetch early so it races with the rest of preflight work.
|
|
9
|
+
const mcpVersion = getMcpInstalledVersion() ?? MARTIN_MCP_PACKAGE_VERSION;
|
|
10
|
+
const deliveryFetchPromise = fetchSelectedMessage({ clientVersion: mcpVersion, clientKind: "mcp", trigger: "version_check" }, { timeoutMs: 3_000 }).catch(() => null);
|
|
7
11
|
const executionMode = resolveExecutionMode();
|
|
8
12
|
const workspaceRoot = resolveSafeRepoRoot();
|
|
9
13
|
const workingDirectory = resolveSafeRepoRoot(input.workingDirectory);
|
|
@@ -140,6 +144,42 @@ export async function martinPreflightTool(input) {
|
|
|
140
144
|
policy,
|
|
141
145
|
risk: runContract.risk,
|
|
142
146
|
runContract,
|
|
143
|
-
plan
|
|
147
|
+
plan,
|
|
148
|
+
...await resolvePreflightUpdateAvailable(deliveryFetchPromise, mcpVersion)
|
|
144
149
|
};
|
|
145
150
|
}
|
|
151
|
+
async function resolvePreflightUpdateAvailable(fetchPromise, currentVersion) {
|
|
152
|
+
try {
|
|
153
|
+
const message = await fetchPromise;
|
|
154
|
+
if (!message)
|
|
155
|
+
return {};
|
|
156
|
+
if (message.action.type !== "upgrade_mcp")
|
|
157
|
+
return {};
|
|
158
|
+
const targetVersion = message.action.targetVersion;
|
|
159
|
+
if (!targetVersion)
|
|
160
|
+
return {};
|
|
161
|
+
if (!isNewerVersion(currentVersion, targetVersion))
|
|
162
|
+
return {};
|
|
163
|
+
const ledgerPath = resolveDefaultLedgerPath();
|
|
164
|
+
const record = loadDeliveryRecord(ledgerPath);
|
|
165
|
+
const nowMs = Date.now();
|
|
166
|
+
if (!isCooldownExpired(record, nowMs))
|
|
167
|
+
return {};
|
|
168
|
+
if (isDismissed(record, message.id))
|
|
169
|
+
return {};
|
|
170
|
+
try {
|
|
171
|
+
saveDeliveryRecord(ledgerPath, recordShown(record, message, nowMs));
|
|
172
|
+
}
|
|
173
|
+
catch { /* ledger write failure must not surface */ }
|
|
174
|
+
return {
|
|
175
|
+
updateAvailable: {
|
|
176
|
+
targetVersion,
|
|
177
|
+
kind: "mcp",
|
|
178
|
+
message: message.body
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -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
|
|
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}`,
|
package/dist/tools/run-loop.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type
|
|
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>;
|
package/dist/tools/run-loop.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
|
-
import { createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, probeCodexLaunch, resolveCliCommandAvailability
|
|
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
|
-
?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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",
|