@martinloop/mcp 0.3.6 → 0.3.7
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 +137 -145
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resources.d.ts +2 -1
- package/dist/resources.js +251 -197
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +11 -16
- package/dist/server.d.ts +0 -23
- package/dist/server.js +7 -152
- package/dist/tools/run-loop.d.ts +2 -1
- package/dist/tools/run-loop.js +5 -1
- package/dist/tools/tool-support.js +1 -0
- package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +146 -0
- package/dist/vendor/adapters/adapters/src/claude-cli.js +1100 -0
- package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +103 -0
- package/dist/vendor/adapters/adapters/src/cli-bridge.js +569 -0
- package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +78 -0
- package/dist/vendor/adapters/adapters/src/codex-launcher.js +663 -0
- package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +10 -0
- package/dist/vendor/adapters/adapters/src/direct-provider.js +40 -0
- package/dist/vendor/adapters/adapters/src/index.d.ts +8 -0
- package/dist/vendor/adapters/adapters/src/index.js +8 -0
- package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +62 -0
- package/dist/vendor/adapters/adapters/src/openai-compatible.js +306 -0
- package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +17 -0
- package/dist/vendor/adapters/adapters/src/runtime-support.js +59 -0
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +8 -0
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +40 -0
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +8 -0
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +12 -0
- package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +9 -0
- package/dist/vendor/adapters/adapters/src/verifier-only.js +63 -0
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +28 -42
- package/dist/vendor/adapters/contracts/src/context-graph.d.ts +48 -0
- package/dist/vendor/adapters/contracts/src/context-graph.js +26 -0
- package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +78 -0
- package/dist/vendor/adapters/contracts/src/execution-policy.js +43 -0
- package/dist/vendor/adapters/contracts/src/governance.d.ts +24 -0
- package/dist/vendor/adapters/contracts/src/governance.js +11 -0
- package/dist/vendor/adapters/contracts/src/identity.d.ts +29 -0
- package/dist/vendor/adapters/contracts/src/identity.js +30 -0
- package/dist/vendor/adapters/contracts/src/index.d.ts +412 -0
- package/dist/vendor/adapters/contracts/src/index.js +225 -0
- package/dist/vendor/adapters/contracts/src/operator.d.ts +19 -0
- package/dist/vendor/adapters/contracts/src/operator.js +11 -0
- package/dist/vendor/adapters/contracts/src/trajectory.d.ts +26 -0
- package/dist/vendor/adapters/contracts/src/trajectory.js +27 -0
- package/dist/vendor/adapters/core/src/compiler.d.ts +52 -0
- package/dist/vendor/adapters/core/src/compiler.js +52 -0
- package/dist/vendor/adapters/core/src/context-graph.d.ts +3 -0
- package/dist/vendor/adapters/core/src/context-graph.js +371 -0
- package/dist/vendor/adapters/core/src/context-integrity.d.ts +26 -0
- package/dist/vendor/adapters/core/src/context-integrity.js +80 -0
- package/dist/vendor/adapters/core/src/grounding.d.ts +38 -0
- package/dist/vendor/adapters/core/src/grounding.js +273 -0
- package/dist/vendor/adapters/core/src/identity.d.ts +20 -0
- package/dist/vendor/adapters/core/src/identity.js +86 -0
- package/dist/vendor/adapters/core/src/index.d.ts +186 -0
- package/dist/vendor/adapters/core/src/index.js +1495 -0
- package/dist/vendor/adapters/core/src/leash.d.ts +48 -0
- package/dist/vendor/adapters/core/src/leash.js +497 -0
- package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +18 -0
- package/dist/vendor/adapters/core/src/persistence/compiler.js +34 -0
- package/dist/vendor/adapters/core/src/persistence/index.d.ts +10 -0
- package/dist/vendor/adapters/core/src/persistence/index.js +5 -0
- package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +39 -0
- package/dist/vendor/adapters/core/src/persistence/integrity.js +252 -0
- package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +23 -0
- package/dist/vendor/adapters/core/src/persistence/ledger.js +9 -0
- package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +63 -0
- package/dist/vendor/adapters/core/src/persistence/memory-store.js +115 -0
- package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +66 -0
- package/dist/vendor/adapters/core/src/persistence/runs-reader.js +140 -0
- package/dist/vendor/adapters/core/src/persistence/store.d.ts +89 -0
- package/dist/vendor/adapters/core/src/persistence/store.js +125 -0
- package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +85 -0
- package/dist/vendor/adapters/core/src/persistence/trace-store.js +125 -0
- package/dist/vendor/adapters/core/src/policy-compiler.d.ts +2 -0
- package/dist/vendor/adapters/core/src/policy-compiler.js +195 -0
- package/dist/vendor/adapters/core/src/policy.d.ts +156 -0
- package/dist/vendor/adapters/core/src/policy.js +704 -0
- package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +53 -0
- package/dist/vendor/adapters/core/src/repo-analyzer.js +263 -0
- package/dist/vendor/adapters/core/src/rollback.d.ts +15 -0
- package/dist/vendor/adapters/core/src/rollback.js +253 -0
- package/dist/vendor/adapters/core/src/routing.d.ts +82 -0
- package/dist/vendor/adapters/core/src/routing.js +336 -0
- package/dist/vendor/adapters/core/src/test-integrity.d.ts +64 -0
- package/dist/vendor/adapters/core/src/test-integrity.js +197 -0
- package/dist/vendor/adapters/core/src/trajectory.d.ts +18 -0
- package/dist/vendor/adapters/core/src/trajectory.js +295 -0
- package/dist/vendor/adapters/openai-compatible.js +6 -6
- package/dist/vendor/adapters/verifier-only.js +2 -2
- package/dist/vendor/contracts/index.d.ts +1 -1
- package/dist/vendor/core/compiler.js +4 -9
- package/dist/vendor/core/index.js +1 -88
- package/dist/vendor/core/rollback.js +2 -3
- package/package.json +76 -76
- package/server.json +21 -21
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { cloneCircuitBreakDecision, cloneTrajectoryAssessment } from "../../../contracts/index.js";
|
|
2
|
+
const STOP_WORDS = new Set([
|
|
3
|
+
"about",
|
|
4
|
+
"after",
|
|
5
|
+
"again",
|
|
6
|
+
"along",
|
|
7
|
+
"also",
|
|
8
|
+
"been",
|
|
9
|
+
"before",
|
|
10
|
+
"being",
|
|
11
|
+
"between",
|
|
12
|
+
"could",
|
|
13
|
+
"failed",
|
|
14
|
+
"failure",
|
|
15
|
+
"from",
|
|
16
|
+
"have",
|
|
17
|
+
"into",
|
|
18
|
+
"just",
|
|
19
|
+
"keep",
|
|
20
|
+
"made",
|
|
21
|
+
"make",
|
|
22
|
+
"need",
|
|
23
|
+
"only",
|
|
24
|
+
"over",
|
|
25
|
+
"same",
|
|
26
|
+
"should",
|
|
27
|
+
"still",
|
|
28
|
+
"than",
|
|
29
|
+
"that",
|
|
30
|
+
"their",
|
|
31
|
+
"them",
|
|
32
|
+
"then",
|
|
33
|
+
"there",
|
|
34
|
+
"these",
|
|
35
|
+
"they",
|
|
36
|
+
"this",
|
|
37
|
+
"those",
|
|
38
|
+
"touching",
|
|
39
|
+
"without",
|
|
40
|
+
"would"
|
|
41
|
+
]);
|
|
42
|
+
const STALL_FAILURE_CLASSES = new Set([
|
|
43
|
+
"logic_error",
|
|
44
|
+
"verification_failure",
|
|
45
|
+
"repo_grounding_failure",
|
|
46
|
+
"scope_creep",
|
|
47
|
+
"no_progress"
|
|
48
|
+
]);
|
|
49
|
+
export function assessTrajectory(input) {
|
|
50
|
+
const attempts = input.attempts.filter((attempt) => attempt.index > 0);
|
|
51
|
+
if (attempts.length === 0) {
|
|
52
|
+
return cloneTrajectoryAssessment({
|
|
53
|
+
status: "healthy",
|
|
54
|
+
confidence: "low",
|
|
55
|
+
summary: "No prior attempts exist, so Martin has no drift evidence yet.",
|
|
56
|
+
signals: []
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const signals = [];
|
|
60
|
+
const recentAttempts = attempts.slice(-4);
|
|
61
|
+
const lastThree = recentAttempts.slice(-3);
|
|
62
|
+
const lastTwo = recentAttempts.slice(-2);
|
|
63
|
+
const oscillation = detectFailureOscillation(lastThree);
|
|
64
|
+
if (oscillation) {
|
|
65
|
+
signals.push(oscillation);
|
|
66
|
+
}
|
|
67
|
+
const repetition = detectAttemptRepetition(lastThree);
|
|
68
|
+
if (repetition) {
|
|
69
|
+
signals.push(repetition);
|
|
70
|
+
}
|
|
71
|
+
const verificationStall = detectVerificationStall(lastTwo);
|
|
72
|
+
if (verificationStall) {
|
|
73
|
+
signals.push(verificationStall);
|
|
74
|
+
}
|
|
75
|
+
const objectiveDrift = detectObjectiveDrift({
|
|
76
|
+
objective: input.objective,
|
|
77
|
+
verificationPlan: input.verificationPlan,
|
|
78
|
+
attempts: lastTwo
|
|
79
|
+
});
|
|
80
|
+
if (objectiveDrift) {
|
|
81
|
+
signals.push(objectiveDrift);
|
|
82
|
+
}
|
|
83
|
+
const status = deriveTrajectoryStatus(signals);
|
|
84
|
+
const confidence = deriveTrajectoryConfidence(signals);
|
|
85
|
+
const recommendedIntervention = deriveRecommendedIntervention(signals);
|
|
86
|
+
if (signals.length === 0) {
|
|
87
|
+
return cloneTrajectoryAssessment({
|
|
88
|
+
status: "healthy",
|
|
89
|
+
confidence: "medium",
|
|
90
|
+
summary: "Recent attempts stay aligned enough that Martin does not need a circuit break yet.",
|
|
91
|
+
signals: []
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const summary = summarizeTrajectory(status, signals);
|
|
95
|
+
return cloneTrajectoryAssessment({
|
|
96
|
+
status,
|
|
97
|
+
confidence,
|
|
98
|
+
summary,
|
|
99
|
+
signals,
|
|
100
|
+
...(recommendedIntervention ? { recommendedIntervention } : {})
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function decideCircuitBreak(input) {
|
|
104
|
+
const assessment = assessTrajectory(input);
|
|
105
|
+
const remainingIterations = input.remainingIterations ?? Number.POSITIVE_INFINITY;
|
|
106
|
+
const stopForHighSignal = assessment.signals.some((signal) => signal.severity === "high");
|
|
107
|
+
const stopForLateDrift = assessment.status === "drifting" &&
|
|
108
|
+
remainingIterations <= 1 &&
|
|
109
|
+
assessment.signals.some((signal) => signal.code === "objective_drift");
|
|
110
|
+
const stopForLateStall = assessment.status === "watch" &&
|
|
111
|
+
remainingIterations <= 1 &&
|
|
112
|
+
assessment.signals.some((signal) => signal.code === "verification_stall");
|
|
113
|
+
const shouldStop = stopForHighSignal || stopForLateDrift || stopForLateStall;
|
|
114
|
+
return cloneCircuitBreakDecision({
|
|
115
|
+
shouldStop,
|
|
116
|
+
reason: shouldStop
|
|
117
|
+
? assessment.summary
|
|
118
|
+
: "Trajectory signals do not justify a hard circuit break yet.",
|
|
119
|
+
assessment,
|
|
120
|
+
...(assessment.recommendedIntervention
|
|
121
|
+
? { recommendedIntervention: assessment.recommendedIntervention }
|
|
122
|
+
: {})
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function detectFailureOscillation(attempts) {
|
|
126
|
+
if (attempts.length < 3) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const [first, second, third] = attempts;
|
|
130
|
+
if (!first?.failureClass ||
|
|
131
|
+
!second?.failureClass ||
|
|
132
|
+
!third?.failureClass ||
|
|
133
|
+
first.failureClass === second.failureClass ||
|
|
134
|
+
first.failureClass !== third.failureClass) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
code: "failure_oscillation",
|
|
139
|
+
severity: "high",
|
|
140
|
+
summary: `Attempts ${first.index}, ${second.index}, and ${third.index} oscillate between failure classes instead of converging.`,
|
|
141
|
+
attemptIndexes: [first.index, second.index, third.index]
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function detectAttemptRepetition(attempts) {
|
|
145
|
+
if (attempts.length < 3) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const tokenSets = attempts.map((attempt) => toTokenSet(attempt.summary));
|
|
149
|
+
if (tokenSets.some((tokens) => tokens.size < 4)) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const pairScores = [
|
|
153
|
+
jaccardSimilarity(tokenSets[0], tokenSets[1]),
|
|
154
|
+
jaccardSimilarity(tokenSets[1], tokenSets[2]),
|
|
155
|
+
jaccardSimilarity(tokenSets[0], tokenSets[2])
|
|
156
|
+
];
|
|
157
|
+
if (pairScores.every((score) => score >= 0.55)) {
|
|
158
|
+
const matchedTerms = intersectSets(tokenSets[0], new Set(intersectSets(tokenSets[1], tokenSets[2])));
|
|
159
|
+
return {
|
|
160
|
+
code: "attempt_repetition",
|
|
161
|
+
severity: "high",
|
|
162
|
+
summary: `Attempts ${attempts.map((attempt) => attempt.index).join(", ")} are materially repetitive and are not exploring a new path.`,
|
|
163
|
+
attemptIndexes: attempts.map((attempt) => attempt.index),
|
|
164
|
+
...(matchedTerms.length > 0 ? { matchedTerms: matchedTerms.slice(0, 6) } : {})
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
function detectVerificationStall(attempts) {
|
|
170
|
+
if (attempts.length < 2) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const relevant = attempts.filter((attempt) => {
|
|
174
|
+
const failureClass = attempt.failureClass;
|
|
175
|
+
return failureClass !== undefined && STALL_FAILURE_CLASSES.has(failureClass);
|
|
176
|
+
});
|
|
177
|
+
if (relevant.length < 2) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const tokenSets = relevant.map((attempt) => toTokenSet(attempt.summary));
|
|
181
|
+
const overlap = tokenSets
|
|
182
|
+
.slice(1)
|
|
183
|
+
.map((tokens, index) => jaccardSimilarity(tokenSets[index], tokens))
|
|
184
|
+
.reduce((highest, score) => Math.max(highest, score), 0);
|
|
185
|
+
if (overlap < 0.35) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
const latest = relevant.at(-1);
|
|
189
|
+
return {
|
|
190
|
+
code: "verification_stall",
|
|
191
|
+
severity: "medium",
|
|
192
|
+
summary: `Recent attempts keep landing on the same verifier-facing failure surface (${latest.failureClass}).`,
|
|
193
|
+
attemptIndexes: relevant.map((attempt) => attempt.index)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function detectObjectiveDrift(input) {
|
|
197
|
+
if (input.attempts.length < 2) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
const objectiveTerms = toTokenSet(`${input.objective} ${(input.verificationPlan ?? []).join(" ")}`);
|
|
201
|
+
if (objectiveTerms.size < 4) {
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
const summaryTerms = input.attempts.map((attempt) => toTokenSet(attempt.summary));
|
|
205
|
+
const overlaps = summaryTerms.map((terms) => jaccardSimilarity(terms, objectiveTerms));
|
|
206
|
+
if (overlaps.some((score) => score >= 0.18)) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
const sharedDriftTerms = intersectSets(summaryTerms[0], summaryTerms[1]);
|
|
210
|
+
if (sharedDriftTerms.length < 3) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
code: "objective_drift",
|
|
215
|
+
severity: "medium",
|
|
216
|
+
summary: "Recent attempt summaries are converging on work that does not overlap with the original objective or verifier plan.",
|
|
217
|
+
attemptIndexes: input.attempts.map((attempt) => attempt.index),
|
|
218
|
+
matchedTerms: sharedDriftTerms.slice(0, 6)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function deriveTrajectoryStatus(signals) {
|
|
222
|
+
if (signals.some((signal) => signal.severity === "high")) {
|
|
223
|
+
return "stalled";
|
|
224
|
+
}
|
|
225
|
+
if (signals.some((signal) => signal.code === "objective_drift")) {
|
|
226
|
+
return "drifting";
|
|
227
|
+
}
|
|
228
|
+
if (signals.some((signal) => signal.severity === "medium")) {
|
|
229
|
+
return "watch";
|
|
230
|
+
}
|
|
231
|
+
return "healthy";
|
|
232
|
+
}
|
|
233
|
+
function deriveTrajectoryConfidence(signals) {
|
|
234
|
+
if (signals.some((signal) => signal.severity === "high")) {
|
|
235
|
+
return "high";
|
|
236
|
+
}
|
|
237
|
+
if (signals.some((signal) => signal.severity === "medium")) {
|
|
238
|
+
return "medium";
|
|
239
|
+
}
|
|
240
|
+
return "low";
|
|
241
|
+
}
|
|
242
|
+
function deriveRecommendedIntervention(signals) {
|
|
243
|
+
if (signals.some((signal) => signal.code === "failure_oscillation" || signal.code === "attempt_repetition")) {
|
|
244
|
+
return "escalate_human";
|
|
245
|
+
}
|
|
246
|
+
if (signals.some((signal) => signal.code === "objective_drift")) {
|
|
247
|
+
return "tighten_task";
|
|
248
|
+
}
|
|
249
|
+
if (signals.some((signal) => signal.code === "verification_stall")) {
|
|
250
|
+
return "run_verifier";
|
|
251
|
+
}
|
|
252
|
+
return undefined;
|
|
253
|
+
}
|
|
254
|
+
function summarizeTrajectory(status, signals) {
|
|
255
|
+
const lead = signals[0];
|
|
256
|
+
if (!lead) {
|
|
257
|
+
return "Trajectory is healthy.";
|
|
258
|
+
}
|
|
259
|
+
switch (status) {
|
|
260
|
+
case "stalled":
|
|
261
|
+
return `Trajectory stalled: ${lead.summary}`;
|
|
262
|
+
case "drifting":
|
|
263
|
+
return `Trajectory drift detected: ${lead.summary}`;
|
|
264
|
+
case "watch":
|
|
265
|
+
return `Trajectory warning: ${lead.summary}`;
|
|
266
|
+
default:
|
|
267
|
+
return lead.summary;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function toTokenSet(text) {
|
|
271
|
+
return new Set((text ?? "")
|
|
272
|
+
.toLowerCase()
|
|
273
|
+
.match(/[a-z][a-z0-9_/-]{2,}/g)
|
|
274
|
+
?.flatMap((token) => splitCompositeToken(token))
|
|
275
|
+
.map((token) => token.trim())
|
|
276
|
+
.filter((token) => token.length >= 3 && !STOP_WORDS.has(token)) ?? []);
|
|
277
|
+
}
|
|
278
|
+
function splitCompositeToken(token) {
|
|
279
|
+
return token
|
|
280
|
+
.split(/[-_/]/g)
|
|
281
|
+
.flatMap((part) => part.split(/(?=[A-Z])/g))
|
|
282
|
+
.map((part) => part.toLowerCase())
|
|
283
|
+
.filter(Boolean);
|
|
284
|
+
}
|
|
285
|
+
function jaccardSimilarity(left, right) {
|
|
286
|
+
if (left.size === 0 || right.size === 0) {
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
const intersection = intersectSets(left, right).length;
|
|
290
|
+
const union = new Set([...left, ...right]).size;
|
|
291
|
+
return union === 0 ? 0 : intersection / union;
|
|
292
|
+
}
|
|
293
|
+
function intersectSets(left, right) {
|
|
294
|
+
return [...left].filter((term) => right.has(term));
|
|
295
|
+
}
|
|
@@ -72,12 +72,12 @@ function estimateCost(model, inputChars, outputChars) {
|
|
|
72
72
|
// ---------------------------------------------------------------------------
|
|
73
73
|
// Prompt builder
|
|
74
74
|
// ---------------------------------------------------------------------------
|
|
75
|
-
const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
|
|
76
|
-
Follow these rules exactly:
|
|
77
|
-
- Read the task description carefully and implement only what is asked.
|
|
78
|
-
- Do not add features, refactors, or improvements beyond the stated task.
|
|
79
|
-
- If the task asks you to write or modify code, output the complete file content with changes applied.
|
|
80
|
-
- Be precise, minimal, and test-backed in all changes.
|
|
75
|
+
const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
|
|
76
|
+
Follow these rules exactly:
|
|
77
|
+
- Read the task description carefully and implement only what is asked.
|
|
78
|
+
- Do not add features, refactors, or improvements beyond the stated task.
|
|
79
|
+
- If the task asks you to write or modify code, output the complete file content with changes applied.
|
|
80
|
+
- Be precise, minimal, and test-backed in all changes.
|
|
81
81
|
- State what you changed and why at the end of your response.`;
|
|
82
82
|
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
83
83
|
export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
@@ -20,11 +20,11 @@ export function createVerifierOnlyAdapter(options = {}) {
|
|
|
20
20
|
const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
|
|
21
21
|
(request.context.verificationStack?.length ?? 0) > 0;
|
|
22
22
|
const baselineChangedFiles = shouldTrackVerifierWrites
|
|
23
|
-
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
23
|
+
? new Set(await readGitChangedFiles(workingDirectory, 5_000, options.spawnImpl))
|
|
24
24
|
: new Set();
|
|
25
25
|
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl);
|
|
26
26
|
const changedFiles = shouldTrackVerifierWrites
|
|
27
|
-
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
27
|
+
? (await readGitChangedFiles(workingDirectory, 5_000, options.spawnImpl)).filter((file) => !baselineChangedFiles.has(file))
|
|
28
28
|
: [];
|
|
29
29
|
const execution = { changedFiles };
|
|
30
30
|
if (verification.passed) {
|
|
@@ -23,7 +23,7 @@ export interface LoopTask {
|
|
|
23
23
|
acceptanceCriteria?: string[];
|
|
24
24
|
}
|
|
25
25
|
export type ExecutionProfile = "strict_local" | "ci_safe" | "staging_controlled" | "research_untrusted";
|
|
26
|
-
export type MutationMode = "edit"
|
|
26
|
+
export type MutationMode = "edit";
|
|
27
27
|
export interface ApprovalPolicy {
|
|
28
28
|
dependencyAdds?: boolean;
|
|
29
29
|
migrations?: boolean;
|
|
@@ -8,15 +8,10 @@ export function compilePromptPacket(request) {
|
|
|
8
8
|
const priorFailurePatterns = request.previousAttempts
|
|
9
9
|
.filter((a) => a.failureClass && a.intervention)
|
|
10
10
|
.map((a) => `${a.failureClass}:${a.intervention}`);
|
|
11
|
-
const guidanceParts =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
]
|
|
16
|
-
: [
|
|
17
|
-
"Only modify files directly required to satisfy the contract.",
|
|
18
|
-
"Do not touch files outside the allowed paths."
|
|
19
|
-
];
|
|
11
|
+
const guidanceParts = [
|
|
12
|
+
"Only modify files directly required to satisfy the contract.",
|
|
13
|
+
"Do not touch files outside the allowed paths."
|
|
14
|
+
];
|
|
20
15
|
if (request.context.allowedPaths && request.context.allowedPaths.length > 0) {
|
|
21
16
|
guidanceParts.push(`Allowed paths: ${request.context.allowedPaths.join(", ")}.`);
|
|
22
17
|
}
|
|
@@ -186,7 +186,6 @@ export async function runMartin(input) {
|
|
|
186
186
|
let currentAdapterIndex = 0;
|
|
187
187
|
let currentAdapter = adapterChain[currentAdapterIndex] ?? input.adapter;
|
|
188
188
|
let useCompressedContext = false;
|
|
189
|
-
const isVerifyOnly = input.task.mutationMode === "verify_only";
|
|
190
189
|
const executionProfile = resolveExecutionProfile({
|
|
191
190
|
executionProfile: input.task.executionProfile,
|
|
192
191
|
allowedNetworkDomains: input.task.allowedNetworkDomains
|
|
@@ -684,93 +683,7 @@ export async function runMartin(input) {
|
|
|
684
683
|
// a git repo) and silently return [], which would falsely trigger no_code_change.
|
|
685
684
|
const changedFileEvidenceAvailable = result.execution?.changedFiles !== undefined || changedFiles.length > 0;
|
|
686
685
|
const isVerifierOnlyAdapter = executingAdapter.adapterId === "direct:verifier:verify-only";
|
|
687
|
-
const patchTruthCountsEdits = !
|
|
688
|
-
if (isVerifyOnly && changedFiles.length > 0) {
|
|
689
|
-
const patchDecision = evaluatePatchDecision({
|
|
690
|
-
verificationPassed: verification.passed,
|
|
691
|
-
previousVerifierScore,
|
|
692
|
-
verifierScore: verification.passed ? 1 : 0,
|
|
693
|
-
scopeViolationCount: changedFiles.length,
|
|
694
|
-
changedFileCount: changedFiles.length,
|
|
695
|
-
diffNovelty: 1,
|
|
696
|
-
diffStats: result.execution?.diffStats,
|
|
697
|
-
costUsd: getUsageUsd(result.usage),
|
|
698
|
-
summary: result.summary
|
|
699
|
-
});
|
|
700
|
-
const verifyOnlyExitDecision = {
|
|
701
|
-
shouldExit: true,
|
|
702
|
-
lifecycleState: "human_escalation",
|
|
703
|
-
status: "exited",
|
|
704
|
-
reason: "Verify-only mode forbids file changes.",
|
|
705
|
-
failureClass: "safety_leash_blocked",
|
|
706
|
-
safetySurface: "filesystem",
|
|
707
|
-
reasonCode: "verify_only_write_attempt"
|
|
708
|
-
};
|
|
709
|
-
const rollbackOutcome = await restoreRollbackBoundary({
|
|
710
|
-
repoRoot: request.context.repoRoot,
|
|
711
|
-
boundary: rollbackBoundary,
|
|
712
|
-
restoredAt: attemptCompletedAt,
|
|
713
|
-
decision: patchDecision.decision
|
|
714
|
-
});
|
|
715
|
-
if (input.store) {
|
|
716
|
-
const verifyOnlyViolation = {
|
|
717
|
-
kind: "path_not_allowed",
|
|
718
|
-
message: `Verify-only mode forbids changed files: ${changedFiles.join(", ")}`,
|
|
719
|
-
file: changedFiles[0]
|
|
720
|
-
};
|
|
721
|
-
await input.store.writeAttemptArtifacts(loop.loopId, currentAttemptIndex, {
|
|
722
|
-
compiledContext,
|
|
723
|
-
leash: createLeashArtifact({
|
|
724
|
-
surface: "filesystem",
|
|
725
|
-
reason: verifyOnlyExitDecision.reason,
|
|
726
|
-
violations: [verifyOnlyViolation]
|
|
727
|
-
}, currentAttemptIndex),
|
|
728
|
-
patchScore: patchDecision.score,
|
|
729
|
-
patchDecision: toPatchDecisionArtifact(patchDecision),
|
|
730
|
-
...(rollbackBoundary ? { rollbackBoundary } : {}),
|
|
731
|
-
...(rollbackOutcome ? { rollbackOutcome } : {})
|
|
732
|
-
});
|
|
733
|
-
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
734
|
-
kind: "safety.violations_found",
|
|
735
|
-
runId: loop.loopId,
|
|
736
|
-
attemptIndex: currentAttemptIndex,
|
|
737
|
-
payload: {
|
|
738
|
-
surface: "filesystem",
|
|
739
|
-
blocked: true,
|
|
740
|
-
attemptIndex: currentAttemptIndex,
|
|
741
|
-
violations: [
|
|
742
|
-
{
|
|
743
|
-
kind: "path_not_allowed",
|
|
744
|
-
message: verifyOnlyExitDecision.reason,
|
|
745
|
-
files: changedFiles
|
|
746
|
-
}
|
|
747
|
-
]
|
|
748
|
-
}
|
|
749
|
-
}));
|
|
750
|
-
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
751
|
-
kind: "attempt.discarded",
|
|
752
|
-
runId: loop.loopId,
|
|
753
|
-
attemptIndex: currentAttemptIndex,
|
|
754
|
-
payload: {
|
|
755
|
-
decision: patchDecision.decision,
|
|
756
|
-
reason: patchDecision.summary,
|
|
757
|
-
reasonCodes: patchDecision.reasonCodes,
|
|
758
|
-
score: patchDecision.score.score
|
|
759
|
-
}
|
|
760
|
-
}));
|
|
761
|
-
await input.store.appendLedger(loop.loopId, makeLedgerEvent({
|
|
762
|
-
kind: "run.exited",
|
|
763
|
-
runId: loop.loopId,
|
|
764
|
-
payload: createRunExitPayload(verifyOnlyExitDecision)
|
|
765
|
-
}));
|
|
766
|
-
}
|
|
767
|
-
const finalizedLoop = finalizeLoop(loop, verifyOnlyExitDecision, now(), idFactory);
|
|
768
|
-
await persistLoopRecordIfSupported(input.store, finalizedLoop);
|
|
769
|
-
return {
|
|
770
|
-
loop: finalizedLoop,
|
|
771
|
-
decision: verifyOnlyExitDecision
|
|
772
|
-
};
|
|
773
|
-
}
|
|
686
|
+
const patchTruthCountsEdits = !isVerifierOnlyAdapter && changedFileEvidenceAvailable;
|
|
774
687
|
const filesystemDecision = evaluateFilesystemLeash({
|
|
775
688
|
repoRoot: request.context.repoRoot,
|
|
776
689
|
changedFiles,
|
|
@@ -29,12 +29,11 @@ export async function captureRollbackBoundary(input) {
|
|
|
29
29
|
for (const filePath of snapshotPaths) {
|
|
30
30
|
snapshots.push(await readRollbackSnapshot(input.repoRoot, filePath));
|
|
31
31
|
}
|
|
32
|
+
const headRef = readGitScalar(input.repoRoot, ["rev-parse", "HEAD"]);
|
|
32
33
|
return {
|
|
33
34
|
strategy: "git_head_plus_snapshot",
|
|
34
35
|
capturedAt: input.capturedAt,
|
|
35
|
-
...(
|
|
36
|
-
? { headRef: readGitScalar(input.repoRoot, ["rev-parse", "HEAD"]) }
|
|
37
|
-
: {}),
|
|
36
|
+
...(headRef ? { headRef } : {}),
|
|
38
37
|
trackedDirtyFiles: repoState.trackedDirtyFiles,
|
|
39
38
|
untrackedFiles: repoState.untrackedFiles,
|
|
40
39
|
snapshots
|
package/package.json
CHANGED
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@martinloop/mcp",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
|
-
"private": false,
|
|
6
|
-
"type": "module",
|
|
7
|
-
"description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
|
|
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
|
-
},
|
|
64
|
-
"dependencies": {
|
|
65
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
|
-
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
67
|
-
"@opentelemetry/api-logs": "^0.214.0",
|
|
68
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
69
|
-
"@opentelemetry/resources": "^2.6.1",
|
|
70
|
-
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
71
|
-
"ts-morph": "^21.0.0"
|
|
72
|
-
},
|
|
73
|
-
"devDependencies": {
|
|
74
|
-
"@martin/contracts": "workspace:*"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@martinloop/mcp",
|
|
3
|
+
"version": "0.3.7",
|
|
4
|
+
"mcpName": "io.github.Keesan12/martin-loop",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"description": "Governed MCP server for AI coding agents with budgets, verifier gates, and inspectable runs.",
|
|
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
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
|
+
"@open-policy-agent/opa-wasm": "^1.10.0",
|
|
67
|
+
"@opentelemetry/api-logs": "^0.214.0",
|
|
68
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.214.0",
|
|
69
|
+
"@opentelemetry/resources": "^2.6.1",
|
|
70
|
+
"@opentelemetry/sdk-logs": "^0.214.0",
|
|
71
|
+
"ts-morph": "^21.0.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@martin/contracts": "workspace:*"
|
|
75
|
+
}
|
|
76
|
+
}
|