@martinloop/mcp 0.2.0 → 0.2.5
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 +131 -158
- package/dist/discovery-metadata.d.ts +16 -0
- package/dist/discovery-metadata.js +62 -0
- package/dist/discovery-support.d.ts +62 -0
- package/dist/discovery-support.js +224 -0
- package/dist/package-version.d.ts +1 -0
- package/dist/package-version.js +3 -0
- package/dist/prompts.d.ts +13 -3
- package/dist/prompts.js +445 -74
- package/dist/resources.d.ts +27 -5
- package/dist/resources.js +557 -71
- package/dist/server-validation.d.ts +2 -3
- package/dist/server-validation.js +262 -122
- package/dist/server.d.ts +76 -7
- package/dist/server.js +1126 -400
- package/dist/tools/doctor.js +14 -6
- package/dist/tools/get-attempt.d.ts +13 -6
- package/dist/tools/get-attempt.js +14 -5
- package/dist/tools/get-run.d.ts +19 -12
- package/dist/tools/get-run.js +20 -11
- package/dist/tools/get-status.d.ts +11 -0
- package/dist/tools/get-status.js +12 -2
- package/dist/tools/get-verification-results.d.ts +10 -7
- package/dist/tools/get-verification-results.js +11 -6
- package/dist/tools/inspect-loop.d.ts +9 -0
- package/dist/tools/inspect-loop.js +11 -2
- package/dist/tools/list-runs.d.ts +25 -5
- package/dist/tools/list-runs.js +21 -4
- package/dist/tools/preflight.js +7 -2
- package/dist/tools/run-dossier.d.ts +37 -4
- package/dist/tools/run-dossier.js +40 -5
- package/dist/tools/run-loop.d.ts +19 -0
- package/dist/tools/run-loop.js +41 -3
- package/dist/tools/run-store.d.ts +57 -3
- package/dist/tools/run-store.js +404 -53
- package/dist/tools/tool-errors.d.ts +37 -0
- package/dist/tools/tool-errors.js +170 -0
- package/dist/tools/tool-response.d.ts +16 -0
- package/dist/tools/tool-response.js +34 -0
- package/dist/tools/tool-support.d.ts +92 -2
- package/dist/tools/tool-support.js +358 -63
- package/dist/tools/triage-runs.d.ts +33 -0
- package/dist/tools/triage-runs.js +138 -0
- package/dist/vendor/adapters/claude-cli.js +0 -1
- package/dist/vendor/adapters/cli-bridge.js +0 -1
- package/dist/vendor/adapters/direct-provider.js +0 -1
- package/dist/vendor/adapters/index.js +0 -1
- package/dist/vendor/adapters/runtime-support.js +0 -1
- package/dist/vendor/adapters/stub-agent-cli.js +0 -1
- package/dist/vendor/adapters/stub-direct-provider.js +0 -1
- package/dist/vendor/adapters/verifier-only.js +0 -1
- package/dist/vendor/contracts/governance.js +0 -1
- package/dist/vendor/contracts/index.d.ts +2 -0
- package/dist/vendor/contracts/index.js +1 -1
- package/dist/vendor/contracts/operator.d.ts +19 -0
- package/dist/vendor/contracts/operator.js +11 -0
- package/dist/vendor/core/compiler.js +0 -1
- package/dist/vendor/core/context-integrity.js +0 -1
- package/dist/vendor/core/grounding.js +0 -1
- package/dist/vendor/core/index.js +1 -2
- package/dist/vendor/core/leash.js +19 -12
- package/dist/vendor/core/persistence/compiler.js +0 -1
- package/dist/vendor/core/persistence/index.js +0 -1
- package/dist/vendor/core/persistence/ledger.js +0 -1
- package/dist/vendor/core/persistence/runs-reader.js +0 -1
- package/dist/vendor/core/persistence/store.js +0 -1
- package/dist/vendor/core/policy.js +0 -1
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +135 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
- package/dist/vendor/core/rollback.js +2 -3
- package/package.json +10 -5
- package/server.json +2 -2
- package/dist/tools/cockpit-support.d.ts +0 -69
- package/dist/tools/cockpit-support.js +0 -108
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { resolveRunsRoot } from "./vendor/core/index.js";
|
|
2
|
+
import { resolveSafeRepoRoot, resolveSafeRunsRootPath } from "./server-validation.js";
|
|
3
|
+
import { loadLoopRecordForStatus } from "./tools/run-store.js";
|
|
4
|
+
import { buildLoopPreview } from "./tools/tool-support.js";
|
|
5
|
+
import { invalidArgumentsError } from "./tools/tool-errors.js";
|
|
6
|
+
export function resolveMartinDiscoveryContext(input = {}) {
|
|
7
|
+
const fallbackRunsRoot = resolveRunsRoot(process.env);
|
|
8
|
+
return {
|
|
9
|
+
runsRoot: resolveSafeRunsRootPath(input.runsDir, fallbackRunsRoot),
|
|
10
|
+
workingDirectory: resolveSafeRepoRoot(input.workingDirectory),
|
|
11
|
+
...(input.engine ? { engine: input.engine } : {})
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export async function loadPersistedLoopRecord(input) {
|
|
15
|
+
const resolved = await loadLoopRecordForStatus({
|
|
16
|
+
loopId: input.loopId,
|
|
17
|
+
...(input.runsDir ? { runsDir: input.runsDir } : {})
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
source: resolved.source,
|
|
21
|
+
loop: resolved.loop
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function buildAttemptSnapshot(loop, requestedAttemptIndex, ledgerEvents = []) {
|
|
25
|
+
const attempt = requestedAttemptIndex === undefined
|
|
26
|
+
? loop.attempts.at(-1)
|
|
27
|
+
: loop.attempts.find((candidate) => candidate.index === requestedAttemptIndex);
|
|
28
|
+
if (!attempt) {
|
|
29
|
+
throw invalidArgumentsError(requestedAttemptIndex === undefined
|
|
30
|
+
? `Loop '${loop.loopId}' has no attempts yet.`
|
|
31
|
+
: `Attempt ${requestedAttemptIndex} was not found for loop '${loop.loopId}'.`, requestedAttemptIndex === undefined
|
|
32
|
+
? "Run martin_run first or inspect a loop that has executed at least one attempt."
|
|
33
|
+
: "Choose an attemptIndex that exists in the loop record.");
|
|
34
|
+
}
|
|
35
|
+
const verification = buildVerificationSnapshotForAttempt(loop, attempt, ledgerEvents);
|
|
36
|
+
const warnings = collectVerificationSnapshots(loop, ledgerEvents).warnings;
|
|
37
|
+
return {
|
|
38
|
+
loopId: loop.loopId,
|
|
39
|
+
attemptIndex: attempt.index,
|
|
40
|
+
loop: buildPersistedLoopPreview(loop),
|
|
41
|
+
attempt,
|
|
42
|
+
...(verification ? { verification } : {}),
|
|
43
|
+
warnings
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function buildVerificationHistorySnapshot(loop, ledgerEvents = []) {
|
|
47
|
+
const collected = collectVerificationSnapshots(loop, ledgerEvents);
|
|
48
|
+
const verificationHistory = collected.snapshots;
|
|
49
|
+
const latestVerification = selectLatestVerificationSnapshot(verificationHistory, collected.warnings);
|
|
50
|
+
return {
|
|
51
|
+
loopId: loop.loopId,
|
|
52
|
+
loop: buildPersistedLoopPreview(loop),
|
|
53
|
+
verificationCount: verificationHistory.length,
|
|
54
|
+
...(latestVerification ? { latestVerification } : {}),
|
|
55
|
+
verificationHistory,
|
|
56
|
+
warnings: collected.warnings
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function buildPersistedLoopPreview(loop) {
|
|
60
|
+
return buildLoopPreview(loop);
|
|
61
|
+
}
|
|
62
|
+
export function parseAttemptIndex(value) {
|
|
63
|
+
if (!/^\d+$/u.test(value)) {
|
|
64
|
+
throw invalidArgumentsError("Invalid attemptIndex.", "attemptIndex must be a positive integer taken from the loop's attempts array.");
|
|
65
|
+
}
|
|
66
|
+
const parsed = Number.parseInt(value, 10);
|
|
67
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
68
|
+
throw invalidArgumentsError("Invalid attemptIndex.", "attemptIndex must be a positive integer taken from the loop's attempts array.");
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
export function toPrettyJson(value) {
|
|
73
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
74
|
+
}
|
|
75
|
+
function buildVerificationSnapshotForAttempt(loop, attempt, ledgerEvents = []) {
|
|
76
|
+
const matching = collectVerificationSnapshots(loop, ledgerEvents).snapshots.filter((verification) => verification.attemptIndex === attempt.index ||
|
|
77
|
+
(attempt.attemptId !== undefined && verification.attemptId === attempt.attemptId));
|
|
78
|
+
if (hasConflictingStatuses(matching)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return matching.at(-1);
|
|
82
|
+
}
|
|
83
|
+
function getVerificationEvents(loop) {
|
|
84
|
+
return (loop.events ?? []).filter((event) => event.type === "verification.completed");
|
|
85
|
+
}
|
|
86
|
+
function toVerificationSnapshot(loop, event) {
|
|
87
|
+
if (!isTrustedVerificationTimestamp(event.timestamp)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
const payload = isRecord(event.payload) ? event.payload : undefined;
|
|
91
|
+
const attemptId = typeof payload?.["attemptId"] === "string" ? payload["attemptId"] : undefined;
|
|
92
|
+
const attemptIndex = typeof payload?.["attemptIndex"] === "number" && Number.isInteger(payload["attemptIndex"])
|
|
93
|
+
? payload["attemptIndex"]
|
|
94
|
+
: undefined;
|
|
95
|
+
const matchedAttempt = attemptId
|
|
96
|
+
? loop.attempts.find((attempt) => attempt.attemptId === attemptId)
|
|
97
|
+
: attemptIndex !== undefined
|
|
98
|
+
? loop.attempts.find((attempt) => attempt.index === attemptIndex)
|
|
99
|
+
: undefined;
|
|
100
|
+
if (!matchedAttempt) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
...(matchedAttempt.attemptId ? { attemptId: matchedAttempt.attemptId } : {}),
|
|
105
|
+
attemptIndex: matchedAttempt.index,
|
|
106
|
+
timestamp: event.timestamp,
|
|
107
|
+
lifecycleState: event.lifecycleState,
|
|
108
|
+
...(typeof payload?.["passed"] === "boolean" ? { passed: payload["passed"] } : {}),
|
|
109
|
+
...(typeof payload?.["summary"] === "string" ? { summary: payload["summary"] } : {})
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function collectVerificationSnapshots(loop, ledgerEvents) {
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const snapshots = [];
|
|
115
|
+
const warnings = getLedgerWarnings(ledgerEvents);
|
|
116
|
+
const futureEvidenceCount = [
|
|
117
|
+
...getVerificationEvents(loop).map((event) => event.timestamp),
|
|
118
|
+
...ledgerEvents.filter((candidate) => candidate.kind === "verification.completed").map((event) => event.timestamp)
|
|
119
|
+
].filter(isFutureVerificationTimestamp).length;
|
|
120
|
+
if (futureEvidenceCount > 0) {
|
|
121
|
+
warnings.push(`Ignored ${futureEvidenceCount} future-dated verification evidence item(s) that cannot be trusted yet.`);
|
|
122
|
+
}
|
|
123
|
+
for (const event of getVerificationEvents(loop)) {
|
|
124
|
+
const snapshot = toVerificationSnapshot(loop, event);
|
|
125
|
+
if (!snapshot) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const key = verificationSnapshotKey(snapshot);
|
|
129
|
+
seen.add(key);
|
|
130
|
+
snapshots.push(snapshot);
|
|
131
|
+
}
|
|
132
|
+
for (const event of ledgerEvents.filter((candidate) => candidate.kind === "verification.completed")) {
|
|
133
|
+
const snapshot = ledgerEventToVerificationSnapshot(loop, event);
|
|
134
|
+
if (!snapshot) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const key = verificationSnapshotKey(snapshot);
|
|
138
|
+
if (seen.has(key)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
seen.add(key);
|
|
142
|
+
snapshots.push(snapshot);
|
|
143
|
+
}
|
|
144
|
+
snapshots.sort((left, right) => new Date(left.timestamp).getTime() - new Date(right.timestamp).getTime());
|
|
145
|
+
if (hasConflictingStatusesForLatestAttempt(snapshots)) {
|
|
146
|
+
warnings.push("Verification evidence conflicts for the latest attempt; reporting status as unavailable.");
|
|
147
|
+
}
|
|
148
|
+
return { snapshots, warnings };
|
|
149
|
+
}
|
|
150
|
+
function ledgerEventToVerificationSnapshot(loop, event) {
|
|
151
|
+
if (!isTrustedVerificationTimestamp(event.timestamp)) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const payload = isRecord(event.payload) ? event.payload : undefined;
|
|
155
|
+
const matchedAttempt = event.attemptIndex !== undefined
|
|
156
|
+
? loop.attempts.find((attempt) => attempt.index === event.attemptIndex)
|
|
157
|
+
: undefined;
|
|
158
|
+
if (!matchedAttempt) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
...(matchedAttempt?.attemptId ? { attemptId: matchedAttempt.attemptId } : {}),
|
|
163
|
+
...(event.attemptIndex !== undefined ? { attemptIndex: event.attemptIndex } : {}),
|
|
164
|
+
timestamp: event.timestamp,
|
|
165
|
+
lifecycleState: loop.lifecycleState,
|
|
166
|
+
...(typeof payload?.["passed"] === "boolean" ? { passed: payload["passed"] } : {}),
|
|
167
|
+
...(typeof payload?.["summary"] === "string" ? { summary: payload["summary"] } : {})
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function verificationSnapshotKey(snapshot) {
|
|
171
|
+
return [
|
|
172
|
+
snapshot.attemptIndex !== undefined ? `attempt:${snapshot.attemptIndex}` : `attempt-id:${snapshot.attemptId ?? ""}`,
|
|
173
|
+
`timestamp:${snapshot.timestamp}`,
|
|
174
|
+
`summary:${snapshot.summary ?? ""}`,
|
|
175
|
+
`passed:${String(snapshot.passed)}`
|
|
176
|
+
].join(":");
|
|
177
|
+
}
|
|
178
|
+
function selectLatestVerificationSnapshot(snapshots, warnings) {
|
|
179
|
+
if (warnings.includes("Verification evidence conflicts for the latest attempt; reporting status as unavailable.")) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return snapshots.at(-1);
|
|
183
|
+
}
|
|
184
|
+
function hasConflictingStatusesForLatestAttempt(snapshots) {
|
|
185
|
+
const latest = snapshots.at(-1);
|
|
186
|
+
if (!latest) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const latestAttemptSnapshots = snapshots.filter((candidate) => latest.attemptId
|
|
190
|
+
? candidate.attemptId === latest.attemptId
|
|
191
|
+
: latest.attemptIndex !== undefined
|
|
192
|
+
? candidate.attemptIndex === latest.attemptIndex
|
|
193
|
+
: false);
|
|
194
|
+
return hasConflictingStatuses(latestAttemptSnapshots);
|
|
195
|
+
}
|
|
196
|
+
function hasConflictingStatuses(snapshots) {
|
|
197
|
+
const statuses = new Set(snapshots
|
|
198
|
+
.map((snapshot) => snapshot.passed)
|
|
199
|
+
.filter((status) => typeof status === "boolean"));
|
|
200
|
+
return statuses.size > 1;
|
|
201
|
+
}
|
|
202
|
+
function isTrustedVerificationTimestamp(value) {
|
|
203
|
+
const timestamp = new Date(value).getTime();
|
|
204
|
+
if (!Number.isFinite(timestamp)) {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return timestamp <= Date.now() + 5 * 60_000;
|
|
208
|
+
}
|
|
209
|
+
function isFutureVerificationTimestamp(value) {
|
|
210
|
+
const timestamp = new Date(value).getTime();
|
|
211
|
+
if (!Number.isFinite(timestamp)) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
return timestamp > Date.now() + 5 * 60_000;
|
|
215
|
+
}
|
|
216
|
+
function getLedgerWarnings(ledgerEvents) {
|
|
217
|
+
const diagnostics = ledgerEvents;
|
|
218
|
+
return Array.isArray(diagnostics.warnings)
|
|
219
|
+
? diagnostics.warnings.filter((warning) => typeof warning === "string")
|
|
220
|
+
: [];
|
|
221
|
+
}
|
|
222
|
+
function isRecord(value) {
|
|
223
|
+
return typeof value === "object" && value !== null;
|
|
224
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MARTIN_MCP_PACKAGE_VERSION = "0.2.5";
|
package/dist/prompts.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
import type { GetPromptResult,
|
|
2
|
-
export declare
|
|
3
|
-
export
|
|
1
|
+
import type { GetPromptResult, Prompt } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
export declare const MARTIN_PROMPTS: Prompt[];
|
|
3
|
+
export interface MartinGetPromptInput {
|
|
4
|
+
name: string;
|
|
5
|
+
arguments?: Record<string, string>;
|
|
6
|
+
runsDir?: string;
|
|
7
|
+
workingDirectory?: string;
|
|
8
|
+
engine?: "claude" | "codex";
|
|
9
|
+
}
|
|
10
|
+
export declare function listMartinPrompts(): {
|
|
11
|
+
prompts: Prompt[];
|
|
12
|
+
};
|
|
13
|
+
export declare function getMartinPrompt(input: MartinGetPromptInput): Promise<GetPromptResult>;
|