@martinloop/mcp 0.1.1 → 0.1.2
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 +136 -38
- package/dist/server-validation.d.ts +10 -0
- package/dist/server-validation.js +234 -0
- package/dist/server.js +42 -13
- package/dist/tools/get-status.d.ts +10 -2
- package/dist/tools/get-status.js +11 -4
- package/dist/tools/inspect-loop.d.ts +4 -2
- package/dist/tools/inspect-loop.js +4 -7
- package/dist/tools/run-loop.d.ts +2 -0
- package/dist/tools/run-loop.js +10 -3
- package/dist/tools/run-store.d.ts +20 -0
- package/dist/tools/run-store.js +109 -0
- package/dist/vendor/adapters/claude-cli.d.ts +19 -4
- package/dist/vendor/adapters/claude-cli.js +55 -24
- package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
- package/dist/vendor/adapters/cli-bridge.js +154 -28
- package/dist/vendor/adapters/index.d.ts +1 -0
- package/dist/vendor/adapters/index.js +1 -0
- package/dist/vendor/adapters/verifier-only.d.ts +7 -0
- package/dist/vendor/adapters/verifier-only.js +57 -0
- package/dist/vendor/contracts/index.d.ts +3 -1
- package/dist/vendor/core/compiler.d.ts +2 -0
- package/dist/vendor/core/compiler.js +10 -4
- package/dist/vendor/core/context-integrity.d.ts +26 -0
- package/dist/vendor/core/context-integrity.js +56 -0
- package/dist/vendor/core/index.d.ts +7 -4
- package/dist/vendor/core/index.js +222 -64
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
- package/dist/vendor/core/persistence/runs-reader.js +84 -0
- package/dist/vendor/core/persistence/store.d.ts +6 -1
- package/dist/vendor/core/persistence/store.js +5 -0
- package/dist/vendor/core/policy.d.ts +6 -0
- package/package.json +13 -5
|
@@ -2,5 +2,7 @@ export { makeLedgerEvent } from "./ledger.js";
|
|
|
2
2
|
export type { LedgerEvent, LedgerEventDraft, LedgerEventKind } from "./ledger.js";
|
|
3
3
|
export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
|
|
4
4
|
export type { AttemptArtifacts, RunContract, RunStore } from "./store.js";
|
|
5
|
+
export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile } from "./runs-reader.js";
|
|
6
|
+
export type { LoopAttemptRecord, LoopRunRecord } from "./runs-reader.js";
|
|
5
7
|
export { compileAndPersistContext } from "./compiler.js";
|
|
6
8
|
export type { CompileResult } from "./compiler.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { makeLedgerEvent } from "./ledger.js";
|
|
2
2
|
export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
|
|
3
|
+
export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile } from "./runs-reader.js";
|
|
3
4
|
export { compileAndPersistContext } from "./compiler.js";
|
|
4
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads completed loop records from ~/.martin/runs/ for analysis.
|
|
3
|
+
* Used by the Trust Calibration Engine and other offline analytics.
|
|
4
|
+
*
|
|
5
|
+
* Supports both storage layouts:
|
|
6
|
+
* - legacy root JSONL files: <runsRoot>/*.jsonl
|
|
7
|
+
* - canonical run trees: <runsRoot>/<loopId>/loop-record.json
|
|
8
|
+
*/
|
|
9
|
+
export interface LoopAttemptRecord {
|
|
10
|
+
index: number;
|
|
11
|
+
model?: string;
|
|
12
|
+
adapterId?: string;
|
|
13
|
+
failureClass?: string;
|
|
14
|
+
intervention?: string;
|
|
15
|
+
startedAt?: string;
|
|
16
|
+
completedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LoopRunRecord {
|
|
19
|
+
loopId: string;
|
|
20
|
+
status: string;
|
|
21
|
+
lifecycleState: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
budget: {
|
|
25
|
+
maxUsd: number;
|
|
26
|
+
softLimitUsd: number;
|
|
27
|
+
maxIterations: number;
|
|
28
|
+
maxTokens: number;
|
|
29
|
+
};
|
|
30
|
+
cost: {
|
|
31
|
+
actualUsd: number;
|
|
32
|
+
tokensIn: number;
|
|
33
|
+
tokensOut: number;
|
|
34
|
+
avoidedUsd?: number;
|
|
35
|
+
};
|
|
36
|
+
attempts: LoopAttemptRecord[];
|
|
37
|
+
task: {
|
|
38
|
+
title: string;
|
|
39
|
+
objective: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function readLoopRecordsFromFile(file: string): Promise<LoopRunRecord[]>;
|
|
43
|
+
export declare function readLatestLoopRecordFromFile(file: string): Promise<LoopRunRecord | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Reads all loop records from the given directory (default: ~/.martin/runs/).
|
|
46
|
+
* Returns an empty array if the directory doesn't exist or has no records.
|
|
47
|
+
*/
|
|
48
|
+
export declare function readAllLoopRecords(runsDir?: string): Promise<LoopRunRecord[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Returns the most recently updated loop record, or null if none exist.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readLatestLoopRecord(runsDir?: string): Promise<LoopRunRecord | null>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads completed loop records from ~/.martin/runs/ for analysis.
|
|
3
|
+
* Used by the Trust Calibration Engine and other offline analytics.
|
|
4
|
+
*
|
|
5
|
+
* Supports both storage layouts:
|
|
6
|
+
* - legacy root JSONL files: <runsRoot>/*.jsonl
|
|
7
|
+
* - canonical run trees: <runsRoot>/<loopId>/loop-record.json
|
|
8
|
+
*/
|
|
9
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { extname, join } from "node:path";
|
|
12
|
+
export async function readLoopRecordsFromFile(file) {
|
|
13
|
+
const text = await readFile(file, "utf8");
|
|
14
|
+
const extension = extname(file).toLowerCase();
|
|
15
|
+
if (extension === ".jsonl") {
|
|
16
|
+
return text
|
|
17
|
+
.split(/\r?\n/u)
|
|
18
|
+
.map((line) => line.trim())
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.map((line) => JSON.parse(line));
|
|
21
|
+
}
|
|
22
|
+
const parsed = JSON.parse(text);
|
|
23
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
24
|
+
}
|
|
25
|
+
export async function readLatestLoopRecordFromFile(file) {
|
|
26
|
+
const records = await readLoopRecordsFromFile(file);
|
|
27
|
+
if (records.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
return records.reduce((latest, record) => {
|
|
30
|
+
const currentTimestamp = new Date(record.updatedAt ?? record.createdAt).getTime();
|
|
31
|
+
const latestTimestamp = new Date(latest.updatedAt ?? latest.createdAt).getTime();
|
|
32
|
+
return currentTimestamp > latestTimestamp ? record : latest;
|
|
33
|
+
}, records[0]);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Reads all loop records from the given directory (default: ~/.martin/runs/).
|
|
37
|
+
* Returns an empty array if the directory doesn't exist or has no records.
|
|
38
|
+
*/
|
|
39
|
+
export async function readAllLoopRecords(runsDir) {
|
|
40
|
+
const dir = runsDir ?? join(homedir(), ".martin", "runs");
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const records = [];
|
|
49
|
+
const jsonlFiles = entries
|
|
50
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
51
|
+
.map((entry) => entry.name);
|
|
52
|
+
for (const file of jsonlFiles) {
|
|
53
|
+
try {
|
|
54
|
+
records.push(...(await readLoopRecordsFromFile(join(dir, file))));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// skip malformed files or lines
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const runDirectories = entries.filter((entry) => entry.isDirectory());
|
|
61
|
+
for (const entry of runDirectories) {
|
|
62
|
+
try {
|
|
63
|
+
records.push(...(await readLoopRecordsFromFile(join(dir, entry.name, "loop-record.json"))));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// skip missing or malformed canonical records
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return records;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Returns the most recently updated loop record, or null if none exist.
|
|
73
|
+
*/
|
|
74
|
+
export async function readLatestLoopRecord(runsDir) {
|
|
75
|
+
const records = await readAllLoopRecords(runsDir);
|
|
76
|
+
if (records.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
return records.reduce((latest, r) => {
|
|
79
|
+
const a = new Date(r.updatedAt ?? r.createdAt).getTime();
|
|
80
|
+
const b = new Date(latest.updatedAt ?? latest.createdAt).getTime();
|
|
81
|
+
return a > b ? r : latest;
|
|
82
|
+
}, records[0]);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=runs-reader.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoopBudget, LoopTask, MachineState } from "../../contracts/index.js";
|
|
1
|
+
import type { LoopBudget, LoopRecord, LoopTask, MachineState } from "../../contracts/index.js";
|
|
2
2
|
import { type LedgerEvent } from "./ledger.js";
|
|
3
3
|
export interface RunContract {
|
|
4
4
|
runId: string;
|
|
@@ -53,6 +53,11 @@ export interface RunStore {
|
|
|
53
53
|
* Write artifacts for a completed attempt to artifacts/attempt-<n>/.
|
|
54
54
|
*/
|
|
55
55
|
writeAttemptArtifacts(runId: string, attemptIndex: number, artifacts: AttemptArtifacts): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Persist the latest canonical loop record snapshot when the caller has one.
|
|
58
|
+
* Optional to avoid breaking custom RunStore implementations.
|
|
59
|
+
*/
|
|
60
|
+
writeLoopRecord?(runId: string, loop: LoopRecord): Promise<void>;
|
|
56
61
|
}
|
|
57
62
|
export declare function resolveRunsRoot(env?: NodeJS.ProcessEnv): string;
|
|
58
63
|
export declare function runDir(runsRoot: string, runId: string): string;
|
|
@@ -74,6 +74,11 @@ export function createFileRunStore(options = {}) {
|
|
|
74
74
|
if (artifacts.rollbackOutcome !== undefined) {
|
|
75
75
|
await writeJsonFile(join(dir, "rollback-outcome.json"), artifacts.rollbackOutcome);
|
|
76
76
|
}
|
|
77
|
+
},
|
|
78
|
+
async writeLoopRecord(runId, loop) {
|
|
79
|
+
const dir = runDir(runsRoot, runId);
|
|
80
|
+
await mkdir(dir, { recursive: true });
|
|
81
|
+
await writeJsonFile(join(dir, "loop-record.json"), loop);
|
|
77
82
|
}
|
|
78
83
|
};
|
|
79
84
|
}
|
|
@@ -18,6 +18,12 @@ export interface ExitDecision {
|
|
|
18
18
|
lifecycleState: LoopLifecycleState;
|
|
19
19
|
status: LoopStatus;
|
|
20
20
|
reason: string;
|
|
21
|
+
/** Machine-readable stop classifier for non-attempt exits such as preflight safety blocks. */
|
|
22
|
+
failureClass?: FailureClass;
|
|
23
|
+
/** Machine-readable safety surface, when the stop came from a safety leash. */
|
|
24
|
+
safetySurface?: string;
|
|
25
|
+
/** Stable reason code for dashboards, MCP, and downstream automation. */
|
|
26
|
+
reasonCode?: string;
|
|
21
27
|
}
|
|
22
28
|
export interface MartinAdapterResultLike {
|
|
23
29
|
status: "completed" | "failed";
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"mcpName": "io.github.keesan12/martin-loop",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Governed MCP server for AI coding agents with budgets, verifier gates, policy checks, and audit trails.",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"author": "Vakeesan Mahalingam and Gobi Shanthan",
|
|
10
10
|
"homepage": "https://martinloop.com/",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"main": "./dist/server.js",
|
|
28
28
|
"types": "./dist/server.d.ts",
|
|
29
29
|
"bin": {
|
|
30
|
-
"mcp": "dist/server.js"
|
|
30
|
+
"mcp": "./dist/server.js",
|
|
31
|
+
"martin-loop-mcp": "./dist/server.js"
|
|
31
32
|
},
|
|
32
33
|
"exports": {
|
|
33
34
|
".": {
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"README.md"
|
|
42
43
|
],
|
|
43
44
|
"engines": {
|
|
44
|
-
"node": ">=
|
|
45
|
+
"node": ">=20.0.0"
|
|
45
46
|
},
|
|
46
47
|
"publishConfig": {
|
|
47
48
|
"access": "public"
|
|
@@ -50,11 +51,18 @@
|
|
|
50
51
|
"build": "node ./scripts/build-package.mjs",
|
|
51
52
|
"prepack": "pnpm build",
|
|
52
53
|
"smoke:pack": "node ./scripts/smoke-package.mjs",
|
|
54
|
+
"smoke:published": "node ./scripts/smoke-published-package.mjs",
|
|
53
55
|
"test": "vitest run",
|
|
54
56
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
55
57
|
"start": "node dist/server.js"
|
|
56
58
|
},
|
|
57
59
|
"dependencies": {
|
|
58
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
60
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
61
|
+
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
62
|
+
"@opentelemetry/api-logs": "^0.214.0",
|
|
63
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
64
|
+
"@opentelemetry/resources": "^2.6.1",
|
|
65
|
+
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
66
|
+
"ts-morph": "^21.0.0"
|
|
59
67
|
}
|
|
60
68
|
}
|