@martinloop/mcp 0.5.5 → 0.5.6
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 +175 -175
- package/dist/arcade/capabilities.d.ts +36 -0
- package/dist/arcade/capabilities.js +37 -0
- package/dist/arcade/resource.d.ts +31 -0
- package/dist/arcade/resource.js +52 -0
- package/dist/arcade/status.d.ts +23 -0
- package/dist/arcade/status.js +25 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +3 -3
- package/dist/resources.js +147 -147
- package/dist/server.js +49 -32
- package/dist/tools/run-loop.d.ts +1 -2
- package/dist/tools/run-loop.js +1 -2
- package/dist/vendor/adapters/claude-cli.js +2 -1
- package/dist/vendor/adapters/cli-bridge.js +7 -1
- package/dist/vendor/adapters/index.d.ts +1 -0
- package/dist/vendor/adapters/index.js +1 -0
- package/dist/vendor/adapters/openai-compatible.js +12 -9
- package/dist/vendor/adapters/stub-agent-cli.d.ts +9 -0
- package/dist/vendor/adapters/stub-agent-cli.js +43 -0
- package/dist/vendor/adapters/stub-direct-provider.d.ts +8 -0
- package/dist/vendor/adapters/stub-direct-provider.js +16 -0
- package/dist/vendor/core/index.js +8 -63
- package/dist/vendor/core/policy.d.ts +1 -0
- package/dist/vendor/core/policy.js +8 -0
- package/dist/vendor/core/rollback.d.ts +3 -0
- package/dist/vendor/core/rollback.js +33 -20
- package/dist/vendor/core/verified-handoff.js +25 -6
- package/dist/vendor/presentation/verified-handoff.js +10 -13
- package/package.json +81 -81
- package/server.json +21 -21
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createAdapterCapabilities } from "./runtime-support.js";
|
|
2
|
+
export function createStubAgentCliAdapter(options) {
|
|
3
|
+
const metadata = {
|
|
4
|
+
command: options.command.join(" "),
|
|
5
|
+
providerId: "agent-cli",
|
|
6
|
+
model: options.profile ?? options.command[0] ?? "default",
|
|
7
|
+
transport: "cli",
|
|
8
|
+
capabilities: createAdapterCapabilities(),
|
|
9
|
+
...(options.profile ? { profile: options.profile } : {})
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
adapterId: `agent-cli:stub:${options.command.join(":")}`,
|
|
13
|
+
kind: "agent-cli",
|
|
14
|
+
label: options.label ?? `Stub agent CLI (${options.command.join(" ")})`,
|
|
15
|
+
metadata,
|
|
16
|
+
async execute(request) {
|
|
17
|
+
if (options.responder) {
|
|
18
|
+
return await options.responder(request);
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
status: "failed",
|
|
22
|
+
summary: `Stub agent CLI ${options.command.join(" ")} did not execute a live session.`,
|
|
23
|
+
usage: {
|
|
24
|
+
actualUsd: 0,
|
|
25
|
+
tokensIn: 0,
|
|
26
|
+
tokensOut: 0,
|
|
27
|
+
provenance: "unavailable"
|
|
28
|
+
},
|
|
29
|
+
verification: {
|
|
30
|
+
passed: false,
|
|
31
|
+
summary: "No CLI execution was attempted."
|
|
32
|
+
},
|
|
33
|
+
failure: {
|
|
34
|
+
message: `Agent CLI ${options.command.join(" ")} is not configured for live execution.`,
|
|
35
|
+
classHint: "environment_mismatch"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Internal test-compatibility re-export. Public builders remove the entire
|
|
42
|
+
// stub compatibility surface before constructing distributable packages.
|
|
43
|
+
export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../core/index.js";
|
|
2
|
+
export interface StubDirectProviderAdapterOptions {
|
|
3
|
+
providerId: string;
|
|
4
|
+
model: string;
|
|
5
|
+
label?: string;
|
|
6
|
+
responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
|
|
7
|
+
}
|
|
8
|
+
export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
+
export function createStubDirectProviderAdapter(options) {
|
|
3
|
+
const adapter = createDirectProviderAdapter({
|
|
4
|
+
providerId: options.providerId,
|
|
5
|
+
model: options.model,
|
|
6
|
+
label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
|
|
7
|
+
capabilities: {
|
|
8
|
+
workspaceMutations: false
|
|
9
|
+
},
|
|
10
|
+
responder: options.responder
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
...adapter,
|
|
14
|
+
adapterId: `direct:stub:${options.providerId}:${options.model}`,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
1
|
import { DEFAULT_AGENT_EXECUTION_INTENT, normalizeProviderExecutionTimeoutMs, appendLoopEvent, createLoopRecord } from "../contracts/index.js";
|
|
4
2
|
import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe } from "./policy.js";
|
|
5
3
|
import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
|
|
@@ -796,6 +794,7 @@ export async function runMartin(input) {
|
|
|
796
794
|
const attemptCompletedAt = now();
|
|
797
795
|
const compiledContext = compilePromptPacket(request);
|
|
798
796
|
const expectedVerifierBinding = resolveExpectedVerifierBinding(request);
|
|
797
|
+
const verifierConfigured = expectedVerifierBinding.commands.length > 0;
|
|
799
798
|
const verification = normalizeVerificationOutcome(result, expectedVerifierBinding);
|
|
800
799
|
// PATCH → VERIFY
|
|
801
800
|
currentPhase = "VERIFY";
|
|
@@ -1135,17 +1134,12 @@ export async function runMartin(input) {
|
|
|
1135
1134
|
// Uses the task's repoRoot to build/load the grounding index, then scans any diff
|
|
1136
1135
|
let groundingScanResult;
|
|
1137
1136
|
const patchDiff = buildPatchDiff(result, changedFiles);
|
|
1138
|
-
|
|
1139
|
-
const hasReportedOrObservedChanges = changedFiles.length > 0 || (result.execution?.changedFiles?.length ?? 0) > 0;
|
|
1140
|
-
const groundingEvidenceRequired = result.status === "completed" && workspaceMutationCapable && hasReportedOrObservedChanges;
|
|
1141
|
-
let groundingEvidenceAvailable = !groundingEvidenceRequired;
|
|
1142
|
-
if (groundingEvidenceRequired && patchDiff && input.task.repoRoot) {
|
|
1137
|
+
if (patchDiff && input.task.repoRoot) {
|
|
1143
1138
|
try {
|
|
1144
1139
|
const groundingIndex = await loadOrBuildRepoGroundingIndex(input.task.repoRoot);
|
|
1145
|
-
groundingScanResult =
|
|
1140
|
+
groundingScanResult = scanPatchForGroundingViolations(patchDiff, groundingIndex, {
|
|
1146
1141
|
allowedPaths: input.task.allowedPaths
|
|
1147
|
-
})
|
|
1148
|
-
groundingEvidenceAvailable = true;
|
|
1142
|
+
});
|
|
1149
1143
|
if (input.store && groundingScanResult.violations.length > 0) {
|
|
1150
1144
|
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1151
1145
|
kind: "grounding.violations_found",
|
|
@@ -1161,22 +1155,10 @@ export async function runMartin(input) {
|
|
|
1161
1155
|
}
|
|
1162
1156
|
}
|
|
1163
1157
|
catch {
|
|
1164
|
-
//
|
|
1165
|
-
// grounding inputs follow the same recover-forward trust contract.
|
|
1158
|
+
// Grounding scan is best-effort — never fail the loop because of a scan error
|
|
1166
1159
|
}
|
|
1167
1160
|
}
|
|
1168
|
-
|
|
1169
|
-
// The completed work remains useful, but unavailable grounding evidence
|
|
1170
|
-
// cannot be converted into a zero-violation governance claim.
|
|
1171
|
-
loop = {
|
|
1172
|
-
...loop,
|
|
1173
|
-
metadata: {
|
|
1174
|
-
...loop.metadata,
|
|
1175
|
-
groundingEvidenceStatus: "unavailable"
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && groundingEvidenceAvailable;
|
|
1161
|
+
const shouldEvaluatePatchTruth = result.status === "completed" && tracksWorkspaceMutations && verifierConfigured;
|
|
1180
1162
|
let patchDecision;
|
|
1181
1163
|
if (shouldEvaluatePatchTruth) {
|
|
1182
1164
|
patchDecision = evaluatePatchDecision({
|
|
@@ -1237,7 +1219,7 @@ export async function runMartin(input) {
|
|
|
1237
1219
|
}
|
|
1238
1220
|
else {
|
|
1239
1221
|
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
1240
|
-
kind: verification.passed ? "attempt.kept" : "attempt.discarded",
|
|
1222
|
+
kind: verification.passed || !verifierConfigured ? "attempt.kept" : "attempt.discarded",
|
|
1241
1223
|
runId: loop.loopId,
|
|
1242
1224
|
attemptIndex: currentAttemptIndex,
|
|
1243
1225
|
payload: { reason: verification.summary }
|
|
@@ -1314,6 +1296,7 @@ export async function runMartin(input) {
|
|
|
1314
1296
|
lastResult: effectiveResult,
|
|
1315
1297
|
lastFailure: failure,
|
|
1316
1298
|
costState,
|
|
1299
|
+
verificationRequired: verifierConfigured,
|
|
1317
1300
|
canSwitchAdapter: failure?.recommendedIntervention === "switch_adapter" &&
|
|
1318
1301
|
adapterChain[currentAdapterIndex] !== undefined &&
|
|
1319
1302
|
currentAdapter.adapterId !== executingAdapter.adapterId
|
|
@@ -1578,9 +1561,6 @@ function mergeCostProvenance(previous, current) {
|
|
|
1578
1561
|
return COST_PROVENANCE_RANK[current] < COST_PROVENANCE_RANK[previous] ? current : previous;
|
|
1579
1562
|
}
|
|
1580
1563
|
function resolveChangedFiles(result, repoRoot, rollbackBoundary) {
|
|
1581
|
-
if (repoRoot && rollbackBoundary?.headRef) {
|
|
1582
|
-
return listAttemptChangedFilesSinceBoundary({ repoRoot, boundary: rollbackBoundary });
|
|
1583
|
-
}
|
|
1584
1564
|
if (result.execution?.changedFiles !== undefined) {
|
|
1585
1565
|
return result.execution.changedFiles;
|
|
1586
1566
|
}
|
|
@@ -1601,41 +1581,6 @@ function buildPatchDiff(result, changedFiles) {
|
|
|
1601
1581
|
}
|
|
1602
1582
|
return undefined;
|
|
1603
1583
|
}
|
|
1604
|
-
function reconcileExistingGroundingFiles(scan, repoRoot, boundary) {
|
|
1605
|
-
const resolvedFiles = new Set(scan.resolvedFiles);
|
|
1606
|
-
const violations = scan.violations.filter((violation) => {
|
|
1607
|
-
if (violation.kind !== "file_not_found") {
|
|
1608
|
-
return true;
|
|
1609
|
-
}
|
|
1610
|
-
const absolutePath = resolve(repoRoot, violation.reference);
|
|
1611
|
-
const relativePath = relative(repoRoot, absolutePath);
|
|
1612
|
-
const isInsideRepo = relativePath.length > 0 && !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
1613
|
-
if (!isInsideRepo || !isTrackedRepoFileAtBoundary(repoRoot, violation.reference, boundary)) {
|
|
1614
|
-
return true;
|
|
1615
|
-
}
|
|
1616
|
-
resolvedFiles.add(violation.reference);
|
|
1617
|
-
return false;
|
|
1618
|
-
});
|
|
1619
|
-
return {
|
|
1620
|
-
...scan,
|
|
1621
|
-
violations,
|
|
1622
|
-
resolvedFiles: [...resolvedFiles]
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
|
-
function isTrackedRepoFileAtBoundary(repoRoot, filePath, boundary) {
|
|
1626
|
-
if (!boundary) {
|
|
1627
|
-
return false;
|
|
1628
|
-
}
|
|
1629
|
-
const normalizedPath = filePath.replace(/\\/gu, "/");
|
|
1630
|
-
if (boundary.snapshots.some((snapshot) => snapshot.existed && snapshot.path === normalizedPath)) {
|
|
1631
|
-
return true;
|
|
1632
|
-
}
|
|
1633
|
-
if (!boundary.headRef) {
|
|
1634
|
-
return false;
|
|
1635
|
-
}
|
|
1636
|
-
const result = spawnSync("git", ["-C", repoRoot, "cat-file", "-e", `${boundary.headRef}:${normalizedPath}`], { encoding: "utf8", stdio: "ignore", windowsHide: true });
|
|
1637
|
-
return result.status === 0;
|
|
1638
|
-
}
|
|
1639
1584
|
function createBudgetSettlement(input) {
|
|
1640
1585
|
const totalActualUsd = getUsageUsd(input.usage);
|
|
1641
1586
|
return {
|
|
@@ -97,6 +97,7 @@ export declare function inferExit(input: {
|
|
|
97
97
|
lastFailure?: FailureAssessment;
|
|
98
98
|
costState: CostGovernorState;
|
|
99
99
|
canSwitchAdapter?: boolean;
|
|
100
|
+
verificationRequired?: boolean;
|
|
100
101
|
}): ExitDecision;
|
|
101
102
|
export interface BudgetPreflightInput {
|
|
102
103
|
promptCharCount: number;
|
|
@@ -210,6 +210,14 @@ export function calculateCostPerOutcome(input) {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
export function inferExit(input) {
|
|
213
|
+
if (input.lastResult.status === "completed" && input.verificationRequired === false) {
|
|
214
|
+
return {
|
|
215
|
+
shouldExit: true,
|
|
216
|
+
lifecycleState: "completed",
|
|
217
|
+
status: "completed",
|
|
218
|
+
reason: "Execution-only work completed without verifier evidence; outcome is not VERIFIED."
|
|
219
|
+
};
|
|
220
|
+
}
|
|
213
221
|
if (input.lastResult.status === "completed" && input.lastResult.verification.passed) {
|
|
214
222
|
return {
|
|
215
223
|
shouldExit: true,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { PatchDecision, RollbackBoundaryArtifact, RollbackOutcomeArtifact } from "../contracts/index.js";
|
|
2
|
+
export declare class RollbackStateUnavailableError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
2
5
|
export declare function listAttemptChangedFilesSinceBoundary(input: {
|
|
3
6
|
repoRoot?: string;
|
|
4
7
|
boundary?: RollbackBoundaryArtifact;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
2
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
3
|
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
export class RollbackStateUnavailableError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "RollbackStateUnavailableError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
5
10
|
export function listAttemptChangedFilesSinceBoundary(input) {
|
|
6
11
|
if (!input.repoRoot) {
|
|
7
12
|
return [];
|
|
@@ -12,24 +17,11 @@ export function listAttemptChangedFilesSinceBoundary(input) {
|
|
|
12
17
|
}
|
|
13
18
|
const baselineTracked = new Set(input.boundary.trackedDirtyFiles);
|
|
14
19
|
const baselineUntracked = new Set(input.boundary.untrackedFiles);
|
|
15
|
-
const baselineContentChanges = input.boundary.snapshots
|
|
16
|
-
.filter((snapshot) => snapshot.existed && repoFileDiffersFromSnapshot(input.repoRoot, snapshot))
|
|
17
|
-
.map((snapshot) => snapshot.path);
|
|
18
20
|
return uniqueSorted([
|
|
19
21
|
...repoState.trackedDirtyFiles.filter((filePath) => !baselineTracked.has(filePath)),
|
|
20
|
-
...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
|
|
21
|
-
...baselineContentChanges
|
|
22
|
+
...repoState.untrackedFiles.filter((filePath) => !baselineUntracked.has(filePath))
|
|
22
23
|
]);
|
|
23
24
|
}
|
|
24
|
-
function repoFileDiffersFromSnapshot(repoRoot, snapshot) {
|
|
25
|
-
try {
|
|
26
|
-
const current = readFileSync(resolveRepoPath(repoRoot, snapshot.path));
|
|
27
|
-
return current.toString("base64") !== snapshot.contentBase64;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
25
|
export async function captureRollbackBoundary(input) {
|
|
34
26
|
if (!input.repoRoot) {
|
|
35
27
|
return undefined;
|
|
@@ -147,6 +139,7 @@ function readRepoState(repoRoot) {
|
|
|
147
139
|
};
|
|
148
140
|
}
|
|
149
141
|
function readGitLines(repoRoot, args) {
|
|
142
|
+
let lastFailure = "unknown Git failure";
|
|
150
143
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
151
144
|
const result = spawnSync("git", args, {
|
|
152
145
|
cwd: repoRoot,
|
|
@@ -158,13 +151,14 @@ function readGitLines(repoRoot, args) {
|
|
|
158
151
|
.map((line) => normalizeRepoPath(line))
|
|
159
152
|
.filter(Boolean));
|
|
160
153
|
}
|
|
154
|
+
lastFailure = gitFailureMessage(result);
|
|
161
155
|
// Retry once after a short delay — handles git lock-file contention
|
|
162
156
|
// that occurs when another process holds .git/index.lock.
|
|
163
157
|
if (attempt === 0 && result.status !== 0) {
|
|
164
|
-
|
|
158
|
+
delaySynchronously(500);
|
|
165
159
|
}
|
|
166
160
|
}
|
|
167
|
-
|
|
161
|
+
throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${lastFailure}`);
|
|
168
162
|
}
|
|
169
163
|
function readGitScalar(repoRoot, args) {
|
|
170
164
|
const result = spawnSync("git", args, {
|
|
@@ -172,7 +166,7 @@ function readGitScalar(repoRoot, args) {
|
|
|
172
166
|
encoding: "utf8"
|
|
173
167
|
});
|
|
174
168
|
if (result.status !== 0 || typeof result.stdout !== "string") {
|
|
175
|
-
|
|
169
|
+
throw new RollbackStateUnavailableError(`Git rollback state unavailable for ${args.join(" ")}: ${gitFailureMessage(result)}`);
|
|
176
170
|
}
|
|
177
171
|
const value = result.stdout.trim();
|
|
178
172
|
return value.length > 0 ? value : undefined;
|
|
@@ -188,7 +182,10 @@ async function readRollbackSnapshot(repoRoot, filePath) {
|
|
|
188
182
|
contentBase64: contents.toString("base64")
|
|
189
183
|
};
|
|
190
184
|
}
|
|
191
|
-
catch {
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (!isFileNotFoundError(error)) {
|
|
187
|
+
throw new RollbackStateUnavailableError(`Rollback snapshot unavailable for ${normalizeRepoPath(filePath)}: ${toErrorMessage(error)}`);
|
|
188
|
+
}
|
|
192
189
|
return {
|
|
193
190
|
path: normalizeRepoPath(filePath),
|
|
194
191
|
existed: false,
|
|
@@ -214,7 +211,7 @@ function restoreTrackedFileFromHead(repoRoot, filePath) {
|
|
|
214
211
|
return;
|
|
215
212
|
}
|
|
216
213
|
// Retry once after a short delay (git lock-file contention)
|
|
217
|
-
|
|
214
|
+
delaySynchronously(500);
|
|
218
215
|
const retry = spawnSync("git", ["restore", "--staged", "--worktree", "--source=HEAD", "--", filePath], { cwd: repoRoot, encoding: "utf8" });
|
|
219
216
|
if (retry.status === 0) {
|
|
220
217
|
return;
|
|
@@ -264,3 +261,19 @@ function emptyRepoState() {
|
|
|
264
261
|
function toErrorMessage(error) {
|
|
265
262
|
return error instanceof Error ? error.message : String(error);
|
|
266
263
|
}
|
|
264
|
+
function gitFailureMessage(result) {
|
|
265
|
+
if (result.error) {
|
|
266
|
+
return result.error.message;
|
|
267
|
+
}
|
|
268
|
+
if (typeof result.stderr === "string" && result.stderr.trim().length > 0) {
|
|
269
|
+
return result.stderr.trim();
|
|
270
|
+
}
|
|
271
|
+
return `git exited with status ${String(result.status)}`;
|
|
272
|
+
}
|
|
273
|
+
function isFileNotFoundError(error) {
|
|
274
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
275
|
+
}
|
|
276
|
+
function delaySynchronously(milliseconds) {
|
|
277
|
+
const signal = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
278
|
+
Atomics.wait(signal, 0, 0, milliseconds);
|
|
279
|
+
}
|
|
@@ -33,6 +33,9 @@ export function verifierActuallyPassed(evidence, expected) {
|
|
|
33
33
|
if (!evidence?.passed || !evidence.binding || !evidence.steps) {
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
36
|
+
if (expected.commands.length === 0) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
36
39
|
if (evidence.binding.runId !== expected.runId ||
|
|
37
40
|
evidence.binding.workspaceId !== expected.workspaceId ||
|
|
38
41
|
evidence.binding.cwd !== expected.cwd ||
|
|
@@ -93,7 +96,6 @@ export function deriveVerifiedHandoffExecutionBoundary(loop) {
|
|
|
93
96
|
const persistedMode = isExecutionMode(loop.metadata["executionMode"])
|
|
94
97
|
? loop.metadata["executionMode"]
|
|
95
98
|
: undefined;
|
|
96
|
-
const groundingEvidenceAvailable = loop.metadata["groundingEvidenceStatus"] !== "unavailable";
|
|
97
99
|
const executionMode = hasSimulatedAdapter
|
|
98
100
|
? "simulated"
|
|
99
101
|
: hasVerifierOnlyAdapter
|
|
@@ -101,7 +103,7 @@ export function deriveVerifiedHandoffExecutionBoundary(loop) {
|
|
|
101
103
|
: persistedMode ?? (adapterIds.length > 0 ? "governed" : "simulated");
|
|
102
104
|
return {
|
|
103
105
|
executionMode,
|
|
104
|
-
governanceClaimEligible: executionMode === "governed" && adapterIds.length > 0
|
|
106
|
+
governanceClaimEligible: executionMode === "governed" && adapterIds.length > 0,
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
function toEvidenceStatus(status) {
|
|
@@ -157,17 +159,34 @@ export function buildVerifiedHandoff(input) {
|
|
|
157
159
|
"Test integrity was not evaluated for this run.",
|
|
158
160
|
};
|
|
159
161
|
const executionBoundary = deriveVerifiedHandoffExecutionBoundary(input.loop);
|
|
162
|
+
const verifierStepsActuallyPassed = input.verification.steps.length > 0 &&
|
|
163
|
+
input.verification.steps.every((step) => step.launched === true &&
|
|
164
|
+
step.completed === true &&
|
|
165
|
+
step.crashed === false &&
|
|
166
|
+
step.timedOut !== true &&
|
|
167
|
+
step.exitCode === 0);
|
|
168
|
+
const effectiveVerificationStatus = input.verification.status === "passed" && !verifierStepsActuallyPassed
|
|
169
|
+
? input.verification.steps.length === 0
|
|
170
|
+
? "not_run"
|
|
171
|
+
: "contradicted"
|
|
172
|
+
: input.verification.status;
|
|
173
|
+
const verificationWarnings = [
|
|
174
|
+
...input.verification.warnings,
|
|
175
|
+
...(effectiveVerificationStatus !== input.verification.status
|
|
176
|
+
? ["A VERIFIED claim requires at least one launched, completed, exit-zero verifier step."]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
160
179
|
const outcome = resolveVerifiedHandoffOutcome({
|
|
161
180
|
lifecycleState: input.loop.lifecycleState,
|
|
162
181
|
executionStatus: input.executionStatus,
|
|
163
|
-
verificationStatus:
|
|
182
|
+
verificationStatus: effectiveVerificationStatus,
|
|
164
183
|
receiptIntegrity: input.receiptIntegrity.state,
|
|
165
184
|
scopeStatus: scope.status,
|
|
166
185
|
testIntegrityStatus: testIntegrity.status,
|
|
167
186
|
mutationRequired: input.mutationRequired ?? input.loop.task.mutationMode === "edit",
|
|
168
187
|
changedFileCount: scope.changedFiles.length,
|
|
169
188
|
definitionOfDonePreSatisfied: input.definitionOfDonePreSatisfied,
|
|
170
|
-
evidenceContradicted: input.evidenceContradicted ??
|
|
189
|
+
evidenceContradicted: input.evidenceContradicted ?? effectiveVerificationStatus === "contradicted",
|
|
171
190
|
governanceClaimEligible: executionBoundary.governanceClaimEligible,
|
|
172
191
|
unresolvedWorkCount: unresolvedWork.length,
|
|
173
192
|
});
|
|
@@ -196,10 +215,10 @@ export function buildVerifiedHandoff(input) {
|
|
|
196
215
|
lifecycleState: input.loop.lifecycleState,
|
|
197
216
|
},
|
|
198
217
|
verification: {
|
|
199
|
-
status: toEvidenceStatus(
|
|
218
|
+
status: toEvidenceStatus(effectiveVerificationStatus),
|
|
200
219
|
summary: input.verification.summary,
|
|
201
220
|
checks: input.verification.steps.map(toCheck),
|
|
202
|
-
warnings:
|
|
221
|
+
warnings: verificationWarnings,
|
|
203
222
|
},
|
|
204
223
|
requirements: input.requirements ?? [],
|
|
205
224
|
scope,
|
|
@@ -22,19 +22,6 @@ function evidenceTone(status) {
|
|
|
22
22
|
function humanize(value) {
|
|
23
23
|
return value.replaceAll("_", " ").toLowerCase();
|
|
24
24
|
}
|
|
25
|
-
function formatCost(usd, provenance) {
|
|
26
|
-
if (provenance === "unavailable") {
|
|
27
|
-
return "unavailable";
|
|
28
|
-
}
|
|
29
|
-
const amount = "$" + usd.toFixed(2);
|
|
30
|
-
if (provenance === "actual") {
|
|
31
|
-
return amount + " provider-settled actual";
|
|
32
|
-
}
|
|
33
|
-
if (provenance === "calculated") {
|
|
34
|
-
return amount + " calculated from observed usage";
|
|
35
|
-
}
|
|
36
|
-
return amount + " estimated";
|
|
37
|
-
}
|
|
38
25
|
function checkSymbol(status) {
|
|
39
26
|
if (status === "PASSED") {
|
|
40
27
|
return "✓";
|
|
@@ -44,6 +31,16 @@ function checkSymbol(status) {
|
|
|
44
31
|
}
|
|
45
32
|
return "•";
|
|
46
33
|
}
|
|
34
|
+
function formatCost(usd, provenance) {
|
|
35
|
+
if (provenance === "unavailable")
|
|
36
|
+
return "unavailable";
|
|
37
|
+
const amount = "$" + usd.toFixed(2);
|
|
38
|
+
if (provenance === "actual")
|
|
39
|
+
return amount + " provider-settled actual";
|
|
40
|
+
if (provenance === "calculated")
|
|
41
|
+
return amount + " calculated from observed usage";
|
|
42
|
+
return amount + " estimated";
|
|
43
|
+
}
|
|
47
44
|
/**
|
|
48
45
|
* Single source of truth for the trust-authority outcome calculation.
|
|
49
46
|
* Both terminal and Markdown renderers call this — never duplicate.
|
package/package.json
CHANGED
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
|
-
"private": false,
|
|
6
|
-
"type": "module",
|
|
7
|
-
"description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
|
|
8
|
-
"license": "Apache-2.0",
|
|
9
|
-
"author": "MartinLoop contributors",
|
|
10
|
-
"homepage": "https://martinloop.com/",
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/Keesan12/martin-loop.git",
|
|
14
|
-
"directory": "packages/mcp"
|
|
15
|
-
},
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/Keesan12/martin-loop/issues"
|
|
18
|
-
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"mcp",
|
|
21
|
-
"model-context-protocol",
|
|
22
|
-
"martin-loop",
|
|
23
|
-
"ai-agent",
|
|
24
|
-
"claude",
|
|
25
|
-
"codex",
|
|
26
|
-
"martin_doctor",
|
|
27
|
-
"martin_triage_runs",
|
|
28
|
-
"martin_run_dossier",
|
|
29
|
-
"mcp-resources",
|
|
30
|
-
"mcp-prompts"
|
|
31
|
-
],
|
|
32
|
-
"bin": {
|
|
33
|
-
"mcp": "./dist/server.js",
|
|
34
|
-
"martin-loop-mcp": "./dist/server.js"
|
|
35
|
-
},
|
|
36
|
-
"exports": {
|
|
37
|
-
"./server.json": "./server.json",
|
|
38
|
-
"./package.json": "./package.json"
|
|
39
|
-
},
|
|
40
|
-
"files": [
|
|
41
|
-
"dist",
|
|
42
|
-
"README.md",
|
|
43
|
-
"server.json"
|
|
44
|
-
],
|
|
45
|
-
"engines": {
|
|
46
|
-
"node": ">=20.0.0"
|
|
47
|
-
},
|
|
48
|
-
"publishConfig": {
|
|
49
|
-
"access": "public"
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"build": "node ./scripts/build-package.mjs",
|
|
53
|
-
"prepack": "pnpm build",
|
|
54
|
-
"smoke:pack": "node ./scripts/smoke-package.mjs",
|
|
55
|
-
"smoke:published": "node ./scripts/smoke-published-package.mjs",
|
|
56
|
-
"smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
|
|
57
|
-
"verify:release": "node --test ../../scripts/tests/publish-mcp-workflow.test.mjs ../../scripts/tests/mcp-publish-reliability.test.mjs ../../scripts/tests/mcp-release-docs.test.mjs",
|
|
58
|
-
"test:baseline": "node ./scripts/run-test-lane.mjs baseline",
|
|
59
|
-
"test": "vitest run --maxWorkers=1",
|
|
60
|
-
"lint": "tsc -p tsconfig.json --noEmit",
|
|
61
|
-
"start": "node dist/server.js",
|
|
62
|
-
"inspect:live": "node ./scripts/inspect-live.mjs",
|
|
63
|
-
"mcpb:build": "node ./mcpb/build-mcpb.mjs",
|
|
64
|
-
"mcpb:validate": "mcpb validate ./dist-mcpb/martinloop",
|
|
65
|
-
"mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
|
|
66
|
-
},
|
|
67
|
-
"dependencies": {
|
|
68
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
69
|
-
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
70
|
-
"@opentelemetry/api-logs": "^0.214.0",
|
|
71
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
72
|
-
"@opentelemetry/resources": "^2.6.1",
|
|
73
|
-
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
74
|
-
"ts-morph": "^21.0.0"
|
|
75
|
-
},
|
|
76
|
-
"devDependencies": {
|
|
77
|
-
"@anthropic-ai/mcpb": "2.1.2",
|
|
78
|
-
"@martin/contracts": "workspace:*",
|
|
79
|
-
"@martin/presentation": "workspace:*"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@martinloop/mcp",
|
|
3
|
+
"version": "0.5.6",
|
|
4
|
+
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"description": "MCP server for AI coding agents — verifier gates, stop limits, and Verified Handoffs.",
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
|
+
"author": "MartinLoop contributors",
|
|
10
|
+
"homepage": "https://martinloop.com/",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/Keesan12/martin-loop.git",
|
|
14
|
+
"directory": "packages/mcp"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Keesan12/martin-loop/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"martin-loop",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"claude",
|
|
25
|
+
"codex",
|
|
26
|
+
"martin_doctor",
|
|
27
|
+
"martin_triage_runs",
|
|
28
|
+
"martin_run_dossier",
|
|
29
|
+
"mcp-resources",
|
|
30
|
+
"mcp-prompts"
|
|
31
|
+
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"mcp": "./dist/server.js",
|
|
34
|
+
"martin-loop-mcp": "./dist/server.js"
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
"./server.json": "./server.json",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md",
|
|
43
|
+
"server.json"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=20.0.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "node ./scripts/build-package.mjs",
|
|
53
|
+
"prepack": "pnpm build",
|
|
54
|
+
"smoke:pack": "node ./scripts/smoke-package.mjs",
|
|
55
|
+
"smoke:published": "node ./scripts/smoke-published-package.mjs",
|
|
56
|
+
"smoke:published:pack": "node ./scripts/smoke-published-package.mjs --package-spec=pack",
|
|
57
|
+
"verify:release": "node --test ../../scripts/tests/publish-mcp-workflow.test.mjs ../../scripts/tests/mcp-publish-reliability.test.mjs ../../scripts/tests/mcp-release-docs.test.mjs",
|
|
58
|
+
"test:baseline": "node ./scripts/run-test-lane.mjs baseline",
|
|
59
|
+
"test": "vitest run --maxWorkers=1",
|
|
60
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
61
|
+
"start": "node dist/server.js",
|
|
62
|
+
"inspect:live": "node ./scripts/inspect-live.mjs",
|
|
63
|
+
"mcpb:build": "node ./mcpb/build-mcpb.mjs",
|
|
64
|
+
"mcpb:validate": "mcpb validate ./dist-mcpb/martinloop",
|
|
65
|
+
"mcpb:smoke": "node ./mcpb/smoke-mcpb.mjs"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
69
|
+
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
70
|
+
"@opentelemetry/api-logs": "^0.214.0",
|
|
71
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
72
|
+
"@opentelemetry/resources": "^2.6.1",
|
|
73
|
+
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
74
|
+
"ts-morph": "^21.0.0"
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@anthropic-ai/mcpb": "2.1.2",
|
|
78
|
+
"@martin/contracts": "workspace:*",
|
|
79
|
+
"@martin/presentation": "workspace:*"
|
|
80
|
+
}
|
|
81
|
+
}
|