@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.
Files changed (35) hide show
  1. package/README.md +136 -38
  2. package/dist/server-validation.d.ts +10 -0
  3. package/dist/server-validation.js +234 -0
  4. package/dist/server.js +42 -13
  5. package/dist/tools/get-status.d.ts +10 -2
  6. package/dist/tools/get-status.js +11 -4
  7. package/dist/tools/inspect-loop.d.ts +4 -2
  8. package/dist/tools/inspect-loop.js +4 -7
  9. package/dist/tools/run-loop.d.ts +2 -0
  10. package/dist/tools/run-loop.js +10 -3
  11. package/dist/tools/run-store.d.ts +20 -0
  12. package/dist/tools/run-store.js +109 -0
  13. package/dist/vendor/adapters/claude-cli.d.ts +19 -4
  14. package/dist/vendor/adapters/claude-cli.js +55 -24
  15. package/dist/vendor/adapters/cli-bridge.d.ts +1 -0
  16. package/dist/vendor/adapters/cli-bridge.js +154 -28
  17. package/dist/vendor/adapters/index.d.ts +1 -0
  18. package/dist/vendor/adapters/index.js +1 -0
  19. package/dist/vendor/adapters/verifier-only.d.ts +7 -0
  20. package/dist/vendor/adapters/verifier-only.js +57 -0
  21. package/dist/vendor/contracts/index.d.ts +3 -1
  22. package/dist/vendor/core/compiler.d.ts +2 -0
  23. package/dist/vendor/core/compiler.js +10 -4
  24. package/dist/vendor/core/context-integrity.d.ts +26 -0
  25. package/dist/vendor/core/context-integrity.js +56 -0
  26. package/dist/vendor/core/index.d.ts +7 -4
  27. package/dist/vendor/core/index.js +222 -64
  28. package/dist/vendor/core/persistence/index.d.ts +2 -0
  29. package/dist/vendor/core/persistence/index.js +1 -0
  30. package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
  31. package/dist/vendor/core/persistence/runs-reader.js +84 -0
  32. package/dist/vendor/core/persistence/store.d.ts +6 -1
  33. package/dist/vendor/core/persistence/store.js +5 -0
  34. package/dist/vendor/core/policy.d.ts +6 -0
  35. 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.1",
3
+ "version": "0.1.2",
4
4
  "mcpName": "io.github.keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
7
- "description": "Martin Loop MCP server installable stdio server for martin_run, martin_inspect, and martin_status.",
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": ">=18.0.0"
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
  }