@martinloop/mcp 0.3.5 → 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.
Files changed (118) hide show
  1. package/README.md +137 -137
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +2 -0
  4. package/dist/prompts.js +19 -1
  5. package/dist/resources.d.ts +3 -0
  6. package/dist/resources.js +125 -17
  7. package/dist/server-validation.d.ts +1 -1
  8. package/dist/server-validation.js +12 -0
  9. package/dist/server.js +18 -7
  10. package/dist/tools/doctor.js +6 -0
  11. package/dist/tools/run-loop.d.ts +2 -1
  12. package/dist/tools/run-loop.js +5 -1
  13. package/dist/tools/tool-support.d.ts +1 -1
  14. package/dist/tools/tool-support.js +2 -1
  15. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +146 -0
  16. package/dist/vendor/adapters/adapters/src/claude-cli.js +1100 -0
  17. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +103 -0
  18. package/dist/vendor/adapters/adapters/src/cli-bridge.js +569 -0
  19. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +78 -0
  20. package/dist/vendor/adapters/adapters/src/codex-launcher.js +663 -0
  21. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +10 -0
  22. package/dist/vendor/adapters/adapters/src/direct-provider.js +40 -0
  23. package/dist/vendor/adapters/adapters/src/index.d.ts +8 -0
  24. package/dist/vendor/adapters/adapters/src/index.js +8 -0
  25. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +62 -0
  26. package/dist/vendor/adapters/adapters/src/openai-compatible.js +306 -0
  27. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +17 -0
  28. package/dist/vendor/adapters/adapters/src/runtime-support.js +59 -0
  29. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +8 -0
  30. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +40 -0
  31. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +8 -0
  32. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +12 -0
  33. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +9 -0
  34. package/dist/vendor/adapters/adapters/src/verifier-only.js +63 -0
  35. package/dist/vendor/adapters/claude-cli.js +21 -20
  36. package/dist/vendor/adapters/cli-bridge.d.ts +7 -0
  37. package/dist/vendor/adapters/cli-bridge.js +94 -8
  38. package/dist/vendor/adapters/codex-launcher.js +18 -7
  39. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +48 -0
  40. package/dist/vendor/adapters/contracts/src/context-graph.js +26 -0
  41. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +78 -0
  42. package/dist/vendor/adapters/contracts/src/execution-policy.js +43 -0
  43. package/dist/vendor/adapters/contracts/src/governance.d.ts +24 -0
  44. package/dist/vendor/adapters/contracts/src/governance.js +11 -0
  45. package/dist/vendor/adapters/contracts/src/identity.d.ts +29 -0
  46. package/dist/vendor/adapters/contracts/src/identity.js +30 -0
  47. package/dist/vendor/adapters/contracts/src/index.d.ts +412 -0
  48. package/dist/vendor/adapters/contracts/src/index.js +225 -0
  49. package/dist/vendor/adapters/contracts/src/operator.d.ts +19 -0
  50. package/dist/vendor/adapters/contracts/src/operator.js +11 -0
  51. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +26 -0
  52. package/dist/vendor/adapters/contracts/src/trajectory.js +27 -0
  53. package/dist/vendor/adapters/core/src/compiler.d.ts +52 -0
  54. package/dist/vendor/adapters/core/src/compiler.js +52 -0
  55. package/dist/vendor/adapters/core/src/context-graph.d.ts +3 -0
  56. package/dist/vendor/adapters/core/src/context-graph.js +371 -0
  57. package/dist/vendor/adapters/core/src/context-integrity.d.ts +26 -0
  58. package/dist/vendor/adapters/core/src/context-integrity.js +80 -0
  59. package/dist/vendor/adapters/core/src/grounding.d.ts +38 -0
  60. package/dist/vendor/adapters/core/src/grounding.js +273 -0
  61. package/dist/vendor/adapters/core/src/identity.d.ts +20 -0
  62. package/dist/vendor/adapters/core/src/identity.js +86 -0
  63. package/dist/vendor/adapters/core/src/index.d.ts +186 -0
  64. package/dist/vendor/adapters/core/src/index.js +1495 -0
  65. package/dist/vendor/adapters/core/src/leash.d.ts +48 -0
  66. package/dist/vendor/adapters/core/src/leash.js +497 -0
  67. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +18 -0
  68. package/dist/vendor/adapters/core/src/persistence/compiler.js +34 -0
  69. package/dist/vendor/adapters/core/src/persistence/index.d.ts +10 -0
  70. package/dist/vendor/adapters/core/src/persistence/index.js +5 -0
  71. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +39 -0
  72. package/dist/vendor/adapters/core/src/persistence/integrity.js +252 -0
  73. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +23 -0
  74. package/dist/vendor/adapters/core/src/persistence/ledger.js +9 -0
  75. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +63 -0
  76. package/dist/vendor/adapters/core/src/persistence/memory-store.js +115 -0
  77. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +66 -0
  78. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +140 -0
  79. package/dist/vendor/adapters/core/src/persistence/store.d.ts +89 -0
  80. package/dist/vendor/adapters/core/src/persistence/store.js +125 -0
  81. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +85 -0
  82. package/dist/vendor/adapters/core/src/persistence/trace-store.js +125 -0
  83. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +2 -0
  84. package/dist/vendor/adapters/core/src/policy-compiler.js +195 -0
  85. package/dist/vendor/adapters/core/src/policy.d.ts +156 -0
  86. package/dist/vendor/adapters/core/src/policy.js +704 -0
  87. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +53 -0
  88. package/dist/vendor/adapters/core/src/repo-analyzer.js +263 -0
  89. package/dist/vendor/adapters/core/src/rollback.d.ts +15 -0
  90. package/dist/vendor/adapters/core/src/rollback.js +253 -0
  91. package/dist/vendor/adapters/core/src/routing.d.ts +82 -0
  92. package/dist/vendor/adapters/core/src/routing.js +336 -0
  93. package/dist/vendor/adapters/core/src/test-integrity.d.ts +64 -0
  94. package/dist/vendor/adapters/core/src/test-integrity.js +197 -0
  95. package/dist/vendor/adapters/core/src/trajectory.d.ts +18 -0
  96. package/dist/vendor/adapters/core/src/trajectory.js +295 -0
  97. package/dist/vendor/adapters/openai-compatible.js +84 -51
  98. package/dist/vendor/adapters/verifier-only.js +2 -2
  99. package/dist/vendor/contracts/index.d.ts +2 -2
  100. package/dist/vendor/contracts/index.js +1 -0
  101. package/dist/vendor/core/compiler.js +4 -9
  102. package/dist/vendor/core/index.d.ts +10 -2
  103. package/dist/vendor/core/index.js +32 -91
  104. package/dist/vendor/core/persistence/memory-store.d.ts +63 -0
  105. package/dist/vendor/core/persistence/memory-store.js +115 -0
  106. package/dist/vendor/core/persistence/trace-store.d.ts +85 -0
  107. package/dist/vendor/core/persistence/trace-store.js +125 -0
  108. package/dist/vendor/core/repo-analyzer.d.ts +53 -0
  109. package/dist/vendor/core/repo-analyzer.js +263 -0
  110. package/dist/vendor/core/rollback.js +2 -3
  111. package/dist/vendor/core/routing.d.ts +42 -0
  112. package/dist/vendor/core/routing.js +156 -5
  113. package/dist/vendor/core/test-integrity.d.ts +64 -0
  114. package/dist/vendor/core/test-integrity.js +197 -0
  115. package/dist/workflow-state.d.ts +1 -1
  116. package/dist/workflow-state.js +12 -4
  117. package/package.json +1 -1
  118. package/server.json +2 -2
@@ -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
+ }
@@ -166,70 +166,103 @@ export function createOpenAiCompatibleAdapter(options) {
166
166
  failure: { message: "budget_preflight_exceeded", classHint: "budget_pressure" }
167
167
  };
168
168
  }
169
- // Call the OpenAI-compatible endpoint
169
+ // Call the OpenAI-compatible endpoint with exponential-backoff retry on
170
+ // transient failures (429 rate-limit, 503/5xx server errors, network errors).
171
+ // Auth errors (401/403) and bad-request errors (400) are not retried — they
172
+ // indicate a permanent configuration problem.
173
+ const MAX_RETRIES = 3;
174
+ const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
170
175
  const endpoint = `${baseUrl}/v1/chat/completions`;
171
176
  let responseText = "";
172
177
  let tokensIn = estimated.tokensIn;
173
178
  let tokensOut = 0;
174
- const controller = new AbortController();
175
- const timer = setTimeout(() => controller.abort(), timeoutMs);
176
- try {
177
- const headers = { "Content-Type": "application/json" };
178
- if (apiKey)
179
- headers["Authorization"] = `Bearer ${apiKey}`;
180
- // OpenRouter requires a site URL header for attribution
181
- if (baseUrl.includes("openrouter")) {
182
- headers["HTTP-Referer"] = "https://martinloop.com";
183
- headers["X-Title"] = "MartinLoop";
184
- }
185
- const res = await fetchFn(endpoint, {
186
- method: "POST",
187
- headers,
188
- body: JSON.stringify({
189
- model,
190
- messages: [
191
- { role: "system", content: systemPrompt },
192
- { role: "user", content: prompt }
193
- ],
194
- temperature: 0.2,
195
- max_tokens: 8192
196
- }),
197
- signal: controller.signal
198
- });
199
- const body = (await res.json());
200
- if (!res.ok || body.error) {
201
- const errMsg = body.error?.message ?? `HTTP ${res.status}`;
202
- return {
203
- status: "failed",
204
- summary: `${model} API error: ${errMsg}`,
205
- usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
206
- verification: { passed: false, summary: "API call failed before verifier." },
207
- failure: { message: errMsg, classHint: "infrastructure_error" }
208
- };
179
+ const headers = { "Content-Type": "application/json" };
180
+ if (apiKey)
181
+ headers["Authorization"] = `Bearer ${apiKey}`;
182
+ if (baseUrl.includes("openrouter")) {
183
+ headers["HTTP-Referer"] = "https://martinloop.com";
184
+ headers["X-Title"] = "MartinLoop";
185
+ }
186
+ const requestBody = JSON.stringify({
187
+ model,
188
+ messages: [
189
+ { role: "system", content: systemPrompt },
190
+ { role: "user", content: prompt }
191
+ ],
192
+ temperature: 0.2,
193
+ max_tokens: 8192
194
+ });
195
+ let lastError = "";
196
+ let succeeded = false;
197
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
198
+ const controller = new AbortController();
199
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
200
+ try {
201
+ const res = await fetchFn(endpoint, {
202
+ method: "POST",
203
+ headers,
204
+ body: requestBody,
205
+ signal: controller.signal
206
+ });
207
+ const body = (await res.json());
208
+ if (!res.ok || body.error) {
209
+ const errMsg = body.error?.message ?? `HTTP ${res.status}`;
210
+ if (RETRYABLE_STATUS.has(res.status) && attempt < MAX_RETRIES - 1) {
211
+ lastError = errMsg;
212
+ // Exponential backoff: 1s, 2s, 4s
213
+ await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
214
+ continue;
215
+ }
216
+ return {
217
+ status: "failed",
218
+ summary: `${model} API error: ${errMsg}`,
219
+ usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
220
+ verification: { passed: false, summary: "API call failed before verifier." },
221
+ failure: { message: errMsg, classHint: "infrastructure_error" }
222
+ };
223
+ }
224
+ responseText = body.choices?.[0]?.message?.content ?? "";
225
+ if (body.usage) {
226
+ tokensIn = body.usage.prompt_tokens ?? tokensIn;
227
+ tokensOut = body.usage.completion_tokens ?? 0;
228
+ }
229
+ else {
230
+ tokensOut = Math.ceil(responseText.length / CHARS_PER_TOKEN);
231
+ }
232
+ succeeded = true;
233
+ break;
209
234
  }
210
- responseText = body.choices?.[0]?.message?.content ?? "";
211
- if (body.usage) {
212
- tokensIn = body.usage.prompt_tokens ?? tokensIn;
213
- tokensOut = body.usage.completion_tokens ?? 0;
235
+ catch (error) {
236
+ const isAbort = error instanceof Error && error.name === "AbortError";
237
+ if (isAbort || attempt === MAX_RETRIES - 1) {
238
+ const message = isAbort
239
+ ? `${model} request timed out after ${timeoutMs}ms`
240
+ : String(error);
241
+ return {
242
+ status: "failed",
243
+ summary: message,
244
+ usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
245
+ verification: { passed: false, summary: isAbort ? "Request timed out." : "Network error." },
246
+ failure: { message, classHint: "infrastructure_error" }
247
+ };
248
+ }
249
+ // Transient network error — retry with backoff
250
+ lastError = String(error);
251
+ await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
214
252
  }
215
- else {
216
- tokensOut = Math.ceil(responseText.length / CHARS_PER_TOKEN);
253
+ finally {
254
+ clearTimeout(timer);
217
255
  }
218
256
  }
219
- catch (error) {
220
- const isAbort = error instanceof Error && error.name === "AbortError";
221
- const message = isAbort ? `${model} request timed out after ${timeoutMs}ms` : String(error);
257
+ if (!succeeded) {
222
258
  return {
223
259
  status: "failed",
224
- summary: message,
260
+ summary: `${model} API error after ${MAX_RETRIES} attempts: ${lastError}`,
225
261
  usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
226
- verification: { passed: false, summary: isAbort ? "Request timed out." : "Network error." },
227
- failure: { message, classHint: "infrastructure_error" }
262
+ verification: { passed: false, summary: "API call failed after retries." },
263
+ failure: { message: lastError, classHint: "infrastructure_error" }
228
264
  };
229
265
  }
230
- finally {
231
- clearTimeout(timer);
232
- }
233
266
  if (!responseText.trim()) {
234
267
  return {
235
268
  status: "failed",
@@ -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) {
@@ -1,6 +1,6 @@
1
1
  export type LoopStatus = "queued" | "running" | "verifying" | "completed" | "failed" | "exited";
2
2
  export type LoopLifecycleState = "created" | "running" | "verifying" | "completed" | "budget_exit" | "diminishing_returns" | "stuck_exit" | "human_escalation";
3
- export declare const FAILURE_CLASSES: readonly ["logic_error", "hallucination", "syntax_error", "type_error", "test_regression", "scope_creep", "no_progress", "repo_grounding_failure", "verification_failure", "environment_mismatch", "budget_pressure", "safety_leash_blocked"];
3
+ export declare const FAILURE_CLASSES: readonly ["logic_error", "hallucination", "syntax_error", "type_error", "test_regression", "scope_creep", "no_progress", "repo_grounding_failure", "verification_failure", "environment_mismatch", "budget_pressure", "safety_leash_blocked", "sandbox_write_blocked"];
4
4
  export type FailureClass = (typeof FAILURE_CLASSES)[number];
5
5
  export type InterventionType = "compress_context" | "change_model" | "tighten_task" | "switch_adapter" | "run_verifier" | "escalate_human" | "stop_loop";
6
6
  export type LoopEventType = "run.started" | "attempt.started" | "attempt.completed" | "failure.classified" | "intervention.selected" | "verification.completed" | "budget.updated" | "run.completed";
@@ -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" | "verify_only";
26
+ export type MutationMode = "edit";
27
27
  export interface ApprovalPolicy {
28
28
  dependencyAdds?: boolean;
29
29
  migrations?: boolean;
@@ -11,6 +11,7 @@ export const FAILURE_CLASSES = [
11
11
  "environment_mismatch",
12
12
  "budget_pressure",
13
13
  "safety_leash_blocked",
14
+ "sandbox_write_blocked",
14
15
  ];
15
16
  export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
16
17
  export const DEFAULT_BUDGET = {
@@ -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 = request.context.mutationMode === "verify_only"
12
- ? [
13
- "Do not modify files.",
14
- "Run the verifier only and report whether it passed."
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
  }
@@ -14,8 +14,12 @@ export { createLocalIdentityAuthority, hasIdentityScope, issueIdentityToken, ver
14
14
  export type { LocalIdentityAuthority } from "./identity.js";
15
15
  export { compileExecutionPolicy } from "./policy-compiler.js";
16
16
  export { assessTrajectory, decideCircuitBreak } from "./trajectory.js";
17
- export { classifyRoute, evaluatePreworkBurnPolicy } from "./routing.js";
18
- export type { RouteDecision, RouteClassificationInput } from "./routing.js";
17
+ export { classifyRoute, evaluatePreworkBurnPolicy, resolveModelForTier, selectBestEngine } from "./routing.js";
18
+ export type { RouteDecision, RouteClassificationInput, AvailableEngine } from "./routing.js";
19
+ export { validateTestIntegrity, snapshotTestFiles, identifyNewTests, findTestFiles, isTestFile, extractImports, hasTrivialAssertionsOnly, importsPreExistingCode } from "./test-integrity.js";
20
+ export type { TestIntegrityReport, TestIntegrityFinding } from "./test-integrity.js";
21
+ export { analyzeRepoStyle, buildPromptDirectives } from "./repo-analyzer.js";
22
+ export type { RepoStyleProfile } from "./repo-analyzer.js";
19
23
  export { runContextIntegrityPrecheck } from "./context-integrity.js";
20
24
  export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
21
25
  export { compilePromptPacket } from "./compiler.js";
@@ -24,6 +28,10 @@ export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoop
24
28
  export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
25
29
  export { compileAndPersistContext } from "./persistence/index.js";
26
30
  export type { CompileResult } from "./persistence/index.js";
31
+ export { appendMemory, readMemoryEntries, getPreference, buildMemorySummary, recordPreference, recordConsent } from "./persistence/memory-store.js";
32
+ export type { MemoryEntry, MemoryKind, MemorySummary } from "./persistence/memory-store.js";
33
+ export { appendTraceEntry, readTraceEntries, aggregateTraces, getHistoricalDirectSuccessRate } from "./persistence/trace-store.js";
34
+ export type { TraceEntry, TraceAggregation } from "./persistence/trace-store.js";
27
35
  export interface MartinAdapterRequest {
28
36
  loopId: string;
29
37
  attemptId: string;