@martinloop/mcp 0.2.7 → 0.3.1

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 (64) hide show
  1. package/README.md +49 -104
  2. package/dist/package-version.d.ts +1 -1
  3. package/dist/package-version.js +1 -1
  4. package/dist/prompts.d.ts +1 -1
  5. package/dist/resources.d.ts +1 -1
  6. package/dist/resources.js +2 -2
  7. package/dist/server-validation.d.ts +1 -0
  8. package/dist/server-validation.js +8 -0
  9. package/dist/server.js +87 -9
  10. package/dist/tools/doctor.d.ts +39 -1
  11. package/dist/tools/doctor.js +68 -9
  12. package/dist/tools/eval.js +3 -2
  13. package/dist/tools/get-run.d.ts +3 -0
  14. package/dist/tools/get-run.js +3 -1
  15. package/dist/tools/get-verification-results.d.ts +3 -0
  16. package/dist/tools/get-verification-results.js +3 -1
  17. package/dist/tools/plan.js +4 -2
  18. package/dist/tools/pr-tools.js +2 -1
  19. package/dist/tools/preflight.d.ts +41 -1
  20. package/dist/tools/preflight.js +74 -19
  21. package/dist/tools/run-dossier.d.ts +3 -0
  22. package/dist/tools/run-dossier.js +5 -2
  23. package/dist/tools/run-loop.d.ts +7 -2
  24. package/dist/tools/run-loop.js +67 -35
  25. package/dist/tools/run-store.js +67 -15
  26. package/dist/tools/tool-errors.js +1 -1
  27. package/dist/tools/tool-support.d.ts +8 -3
  28. package/dist/tools/tool-support.js +61 -18
  29. package/dist/tools/workflow-governance.d.ts +19 -3
  30. package/dist/tools/workflow-governance.js +107 -55
  31. package/dist/vendor/adapters/claude-cli.d.ts +45 -3
  32. package/dist/vendor/adapters/claude-cli.js +465 -45
  33. package/dist/vendor/adapters/cli-bridge.d.ts +46 -0
  34. package/dist/vendor/adapters/cli-bridge.js +147 -38
  35. package/dist/vendor/adapters/codex-launcher.d.ts +76 -0
  36. package/dist/vendor/adapters/codex-launcher.js +538 -0
  37. package/dist/vendor/adapters/index.d.ts +3 -2
  38. package/dist/vendor/adapters/index.js +3 -2
  39. package/dist/vendor/adapters/openai-compatible.d.ts +19 -4
  40. package/dist/vendor/adapters/openai-compatible.js +50 -19
  41. package/dist/vendor/adapters/runtime-support.d.ts +3 -0
  42. package/dist/vendor/adapters/runtime-support.js +9 -1
  43. package/dist/vendor/adapters/stub-direct-provider.js +3 -0
  44. package/dist/vendor/adapters/verifier-only.d.ts +2 -0
  45. package/dist/vendor/adapters/verifier-only.js +11 -4
  46. package/dist/vendor/contracts/index.d.ts +39 -0
  47. package/dist/vendor/contracts/index.js +2 -0
  48. package/dist/vendor/core/context-integrity.js +28 -3
  49. package/dist/vendor/core/grounding.d.ts +1 -0
  50. package/dist/vendor/core/grounding.js +6 -2
  51. package/dist/vendor/core/index.d.ts +24 -3
  52. package/dist/vendor/core/index.js +113 -21
  53. package/dist/vendor/core/leash.js +85 -8
  54. package/dist/vendor/core/persistence/index.d.ts +2 -0
  55. package/dist/vendor/core/persistence/index.js +1 -0
  56. package/dist/vendor/core/persistence/integrity.d.ts +38 -0
  57. package/dist/vendor/core/persistence/integrity.js +248 -0
  58. package/dist/vendor/core/persistence/store.d.ts +7 -0
  59. package/dist/vendor/core/persistence/store.js +25 -1
  60. package/dist/vendor/core/policy.d.ts +9 -0
  61. package/dist/workflow-state.d.ts +9 -0
  62. package/dist/workflow-state.js +46 -3
  63. package/package.json +2 -2
  64. package/server.json +2 -2
@@ -12,17 +12,23 @@
12
12
  * Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
13
13
  *
14
14
  * Usage:
15
+ * # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
16
+ * MARTIN_OPENAI_API_KEY=sk-...
17
+ * MARTIN_OPENAI_MODEL=gpt-4.1-mini
18
+ * martin-loop run "fix the bug" --engine openai
19
+ *
20
+ * # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
15
21
  * MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
16
22
  * MARTIN_OPENAI_API_KEY=sk-or-...
17
23
  * MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
18
- * martin run "fix the bug" --engine openai
24
+ * martin-loop run "fix the bug" --engine openai
19
25
  *
20
26
  * Or for Ollama:
21
27
  * MARTIN_OPENAI_BASE_URL=http://localhost:11434
22
28
  * MARTIN_OPENAI_MODEL=llama3.3
23
- * martin run "fix the bug" --engine openai
29
+ * martin-loop run "fix the bug" --engine openai
24
30
  */
25
- import { runVerification, readGitExecutionArtifacts } from "./cli-bridge.js";
31
+ import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
26
32
  import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
27
33
  // ---------------------------------------------------------------------------
28
34
  // OpenRouter/OpenAI-compatible model pricing ($/1K tokens)
@@ -73,6 +79,18 @@ Follow these rules exactly:
73
79
  - If the task asks you to write or modify code, output the complete file content with changes applied.
74
80
  - Be precise, minimal, and test-backed in all changes.
75
81
  - State what you changed and why at the end of your response.`;
82
+ export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
83
+ export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
84
+ export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
85
+ const apiKey = env["MARTIN_OPENAI_API_KEY"] ?? "";
86
+ return {
87
+ baseUrl: env["MARTIN_OPENAI_BASE_URL"] ?? DEFAULT_OPENAI_BASE_URL,
88
+ model: env["MARTIN_OPENAI_MODEL"] ?? DEFAULT_OPENAI_MODEL,
89
+ apiKey,
90
+ apiKeyConfigured: apiKey.length > 0,
91
+ authPosture: apiKey.length > 0 ? "api_key" : "anonymous_or_local"
92
+ };
93
+ }
76
94
  function buildPrompt(request) {
77
95
  const lines = [
78
96
  `TASK: ${request.context.taskTitle}`,
@@ -105,13 +123,17 @@ export function createOpenAiCompatibleAdapter(options) {
105
123
  const verifyTimeoutMs = options.verifyTimeoutMs ?? 60_000;
106
124
  const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
107
125
  const fetchFn = options.fetchImpl ?? globalThis.fetch;
126
+ const runtimeConfig = resolveOpenAiCompatibleRuntimeConfig();
127
+ const baseUrl = (options.baseUrl ?? runtimeConfig.baseUrl).replace(/\/$/, "");
128
+ const model = options.model ?? runtimeConfig.model;
129
+ const apiKey = options.apiKey ?? runtimeConfig.apiKey;
108
130
  return {
109
- adapterId: `openai-compatible:${options.model}`,
131
+ adapterId: `openai-compatible:${model}`,
110
132
  kind: "direct-provider",
111
- label: `OpenAI-compatible: ${options.model}`,
133
+ label: `OpenAI-compatible: ${model}`,
112
134
  metadata: {
113
135
  providerId: "openai-compatible",
114
- model: options.model,
136
+ model,
115
137
  transport: "http",
116
138
  capabilities: createAdapterCapabilities({
117
139
  preflight: true,
@@ -121,7 +143,12 @@ export function createOpenAiCompatibleAdapter(options) {
121
143
  },
122
144
  async execute(request) {
123
145
  const prompt = buildPrompt(request);
124
- const estimated = estimateCost(options.model, prompt.length, 2000);
146
+ const estimated = estimateCost(model, prompt.length, 2000);
147
+ const hasVerificationSteps = request.context.verificationPlan.length > 0 ||
148
+ (request.context.verificationStack?.length ?? 0) > 0;
149
+ const baselineChangedFiles = hasVerificationSteps
150
+ ? new Set(await readGitChangedFiles(workingDirectory, 5_000))
151
+ : new Set();
125
152
  // Preflight: bail if projected cost exceeds remaining budget
126
153
  if (request.context.remainingBudgetUsd > 0 &&
127
154
  estimated.actualUsd > request.context.remainingBudgetUsd * 0.95) {
@@ -140,7 +167,7 @@ export function createOpenAiCompatibleAdapter(options) {
140
167
  };
141
168
  }
142
169
  // Call the OpenAI-compatible endpoint
143
- const endpoint = `${options.baseUrl.replace(/\/$/, "")}/v1/chat/completions`;
170
+ const endpoint = `${baseUrl}/v1/chat/completions`;
144
171
  let responseText = "";
145
172
  let tokensIn = estimated.tokensIn;
146
173
  let tokensOut = 0;
@@ -148,10 +175,10 @@ export function createOpenAiCompatibleAdapter(options) {
148
175
  const timer = setTimeout(() => controller.abort(), timeoutMs);
149
176
  try {
150
177
  const headers = { "Content-Type": "application/json" };
151
- if (options.apiKey)
152
- headers["Authorization"] = `Bearer ${options.apiKey}`;
178
+ if (apiKey)
179
+ headers["Authorization"] = `Bearer ${apiKey}`;
153
180
  // OpenRouter requires a site URL header for attribution
154
- if (options.baseUrl.includes("openrouter")) {
181
+ if (baseUrl.includes("openrouter")) {
155
182
  headers["HTTP-Referer"] = "https://martinloop.com";
156
183
  headers["X-Title"] = "MartinLoop";
157
184
  }
@@ -159,7 +186,7 @@ export function createOpenAiCompatibleAdapter(options) {
159
186
  method: "POST",
160
187
  headers,
161
188
  body: JSON.stringify({
162
- model: options.model,
189
+ model,
163
190
  messages: [
164
191
  { role: "system", content: systemPrompt },
165
192
  { role: "user", content: prompt }
@@ -174,7 +201,7 @@ export function createOpenAiCompatibleAdapter(options) {
174
201
  const errMsg = body.error?.message ?? `HTTP ${res.status}`;
175
202
  return {
176
203
  status: "failed",
177
- summary: `${options.model} API error: ${errMsg}`,
204
+ summary: `${model} API error: ${errMsg}`,
178
205
  usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
179
206
  verification: { passed: false, summary: "API call failed before verifier." },
180
207
  failure: { message: errMsg, classHint: "infrastructure_error" }
@@ -191,7 +218,7 @@ export function createOpenAiCompatibleAdapter(options) {
191
218
  }
192
219
  catch (error) {
193
220
  const isAbort = error instanceof Error && error.name === "AbortError";
194
- const message = isAbort ? `${options.model} request timed out after ${timeoutMs}ms` : String(error);
221
+ const message = isAbort ? `${model} request timed out after ${timeoutMs}ms` : String(error);
195
222
  return {
196
223
  status: "failed",
197
224
  summary: message,
@@ -206,7 +233,7 @@ export function createOpenAiCompatibleAdapter(options) {
206
233
  if (!responseText.trim()) {
207
234
  return {
208
235
  status: "failed",
209
- summary: `${options.model} returned an empty response.`,
236
+ summary: `${model} returned an empty response.`,
210
237
  usage: normalizeUsage({ actualUsd: 0, tokensIn, tokensOut: 0, provenance: "actual" }),
211
238
  verification: { passed: false, summary: "Empty response — nothing to verify." },
212
239
  failure: { message: "empty_response" }
@@ -214,8 +241,12 @@ export function createOpenAiCompatibleAdapter(options) {
214
241
  }
215
242
  // Run verification
216
243
  const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack);
217
- const execution = await readGitExecutionArtifacts(workingDirectory, 5_000);
218
- const pricing = KNOWN_MODEL_PRICING[options.model] ?? {
244
+ const execution = {
245
+ changedFiles: hasVerificationSteps
246
+ ? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
247
+ : []
248
+ };
249
+ const pricing = KNOWN_MODEL_PRICING[model] ?? {
219
250
  inputPer1K: FALLBACK_INPUT_PER_1K,
220
251
  outputPer1K: FALLBACK_OUTPUT_PER_1K
221
252
  };
@@ -223,8 +254,8 @@ export function createOpenAiCompatibleAdapter(options) {
223
254
  return {
224
255
  status: verification.passed ? "completed" : "failed",
225
256
  summary: verification.passed
226
- ? `${options.model} completed the task. Verifier passed.`
227
- : `${options.model} completed but verifier failed: ${verification.summary}`,
257
+ ? `${model} completed the task. Verifier passed.`
258
+ : `${model} completed but verifier failed: ${verification.summary}`,
228
259
  usage: normalizeUsage({
229
260
  actualUsd,
230
261
  tokensIn,
@@ -7,7 +7,10 @@ export declare function normalizeUsage(input: {
7
7
  estimatedUsd?: number;
8
8
  tokensIn?: number;
9
9
  tokensOut?: number;
10
+ cachedInputTokens?: number;
11
+ reasoningTokensOut?: number;
10
12
  provenance?: MartinAdapterResult["usage"]["provenance"];
13
+ providerSettlement?: MartinAdapterResult["usage"]["providerSettlement"];
11
14
  }): MartinAdapterResult["usage"];
12
15
  export declare function diffStatsFromNumstat(stdout: string): DiffStats | undefined;
13
16
  export declare function normalizeStructuredErrors(errors: StructuredError[] | undefined): StructuredError[];
@@ -5,6 +5,7 @@ export function createAdapterCapabilities(overrides = {}) {
5
5
  diffArtifacts: false,
6
6
  structuredErrors: false,
7
7
  cachingSignals: false,
8
+ workspaceMutations: true,
8
9
  ...overrides
9
10
  };
10
11
  }
@@ -21,7 +22,14 @@ export function normalizeUsage(input) {
21
22
  : {}),
22
23
  tokensIn: input.tokensIn ?? 0,
23
24
  tokensOut: input.tokensOut ?? 0,
24
- provenance
25
+ ...(input.cachedInputTokens !== undefined
26
+ ? { cachedInputTokens: input.cachedInputTokens }
27
+ : {}),
28
+ ...(input.reasoningTokensOut !== undefined
29
+ ? { reasoningTokensOut: input.reasoningTokensOut }
30
+ : {}),
31
+ provenance,
32
+ ...(input.providerSettlement ? { providerSettlement: input.providerSettlement } : {})
25
33
  };
26
34
  }
27
35
  export function diffStatsFromNumstat(stdout) {
@@ -4,6 +4,9 @@ export function createStubDirectProviderAdapter(options) {
4
4
  providerId: options.providerId,
5
5
  model: options.model,
6
6
  label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
7
+ capabilities: {
8
+ workspaceMutations: false
9
+ },
7
10
  responder: options.responder
8
11
  });
9
12
  }
@@ -1,7 +1,9 @@
1
1
  import type { MartinAdapter } from "../core/index.js";
2
+ import { type SpawnLike } from "./cli-bridge.js";
2
3
  export interface VerifierOnlyAdapterOptions {
3
4
  workingDirectory?: string;
4
5
  verifyTimeoutMs?: number;
5
6
  label?: string;
7
+ spawnImpl?: SpawnLike;
6
8
  }
7
9
  export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
@@ -1,4 +1,4 @@
1
- import { readGitExecutionArtifacts, runVerification } from "./cli-bridge.js";
1
+ import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
2
2
  import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
3
3
  export function createVerifierOnlyAdapter(options = {}) {
4
4
  const workingDirectory = options.workingDirectory ?? process.cwd();
@@ -17,9 +17,16 @@ export function createVerifierOnlyAdapter(options = {}) {
17
17
  })
18
18
  },
19
19
  async execute(request) {
20
- const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack);
21
- const execution = await readGitExecutionArtifacts(workingDirectory, 5_000);
22
- const changedFiles = execution.changedFiles ?? [];
20
+ const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
21
+ (request.context.verificationStack?.length ?? 0) > 0;
22
+ const baselineChangedFiles = shouldTrackVerifierWrites
23
+ ? new Set(await readGitChangedFiles(workingDirectory, 5_000))
24
+ : new Set();
25
+ const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl);
26
+ const changedFiles = shouldTrackVerifierWrites
27
+ ? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
28
+ : [];
29
+ const execution = { changedFiles };
23
30
  if (verification.passed) {
24
31
  return {
25
32
  status: "completed",
@@ -49,6 +49,40 @@ export interface LoopCost {
49
49
  avoidedUsd: number;
50
50
  tokensIn: number;
51
51
  tokensOut: number;
52
+ estimatedUsd?: number;
53
+ provenance?: CostProvenance;
54
+ providerSettlement?: ProviderUsageSettlement;
55
+ }
56
+ export type UsageSettlementSource = "claude_json" | "codex_jsonl" | "gemini_json" | "estimated_fallback" | "unavailable";
57
+ export interface ProviderUsageSettlement {
58
+ providerId: string;
59
+ model: string;
60
+ transport?: "cli" | "http" | "routed_http";
61
+ source: UsageSettlementSource;
62
+ inputTokens: number;
63
+ cachedInputTokens?: number;
64
+ outputTokens: number;
65
+ reasoningOutputTokens?: number;
66
+ rawUsageAvailable: boolean;
67
+ settledAt: string;
68
+ }
69
+ export interface ReceiptScope {
70
+ repoRoot?: string;
71
+ workingDirectory?: string;
72
+ invocationRoot?: string;
73
+ runsRoot?: string;
74
+ }
75
+ export type ReceiptIntegrityState = "verified" | "unsigned" | "tamper_detected";
76
+ export interface ReceiptIntegritySummary {
77
+ state: ReceiptIntegrityState;
78
+ keyId?: string;
79
+ signedAt?: string;
80
+ loopRecordSha256?: string;
81
+ ledgerSha256?: string;
82
+ ledgerHeadHash?: string;
83
+ entryCount?: number;
84
+ reason?: string;
85
+ warnings?: string[];
52
86
  }
53
87
  export interface LoopArtifact {
54
88
  artifactId: string;
@@ -90,6 +124,8 @@ export interface LoopRecord {
90
124
  metadata: Record<string, string>;
91
125
  createdAt: string;
92
126
  updatedAt: string;
127
+ receiptScope?: ReceiptScope;
128
+ receiptIntegrity?: ReceiptIntegritySummary;
93
129
  }
94
130
  export interface LoopRecordDraft {
95
131
  loopId?: string;
@@ -107,6 +143,8 @@ export interface LoopRecordDraft {
107
143
  metadata?: Record<string, string>;
108
144
  createdAt?: string;
109
145
  updatedAt?: string;
146
+ receiptScope?: ReceiptScope;
147
+ receiptIntegrity?: ReceiptIntegritySummary;
110
148
  }
111
149
  export type { MartinErrorCategory, MartinOutputMode, MartinRunListFilters, MartinRunSelector } from "./operator.js";
112
150
  export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
@@ -272,6 +310,7 @@ export interface BudgetSettlement {
272
310
  usd: number;
273
311
  provenance: CostProvenance;
274
312
  };
313
+ providerSettlement?: ProviderUsageSettlement;
275
314
  totalActualUsd: number;
276
315
  preflightEstimateUsd: number;
277
316
  varianceUsd: number;
@@ -36,6 +36,8 @@ export function createLoopRecord(draft, options = {}) {
36
36
  },
37
37
  createdAt: draft.createdAt ?? now,
38
38
  updatedAt: draft.updatedAt ?? now,
39
+ ...(draft.receiptScope ? { receiptScope: draft.receiptScope } : {}),
40
+ ...(draft.receiptIntegrity ? { receiptIntegrity: draft.receiptIntegrity } : {}),
39
41
  ...(draft.teamId ? { teamId: draft.teamId } : {})
40
42
  };
41
43
  }
@@ -10,6 +10,23 @@ const POISON_PATTERNS = [
10
10
  /\[system_override\]/i,
11
11
  /\[authority_inversion\]/i
12
12
  ];
13
+ /**
14
+ * Identity-redefinition / persona-override patterns.
15
+ *
16
+ * These intentionally require an *override framing* (e.g. "now", "no longer",
17
+ * "forget", "pretend", or an explicit authority-role claim) rather than any
18
+ * sentence shaped like "you are X" / "I am X" — the latter matches ordinary
19
+ * benign text (e.g. "You are welcome to try MartinLoop") and produced
20
+ * false-positive hard aborts.
21
+ */
22
+ const IDENTITY_REDEFINITION_PATTERNS = [
23
+ /\byou(?:'re|\s+are)\s+now\s+(?:a|an|the)\b(?!\s+(?:martin\s+loop|ai\s+coding\s+agent))/i,
24
+ /\byou(?:'re|\s+are)\s+no\s+longer\s+(?!.*\b(?:martin\s+loop|an?\s+ai)\b)/i,
25
+ /\bforget\s+(?:that\s+)?you(?:'re|\s+are)\s+martin\s+loop\b/i,
26
+ /\b(?:pretend|imagine)\s+(?:that\s+)?you(?:'re|\s+are)\b/i,
27
+ /\bact\s+as\s+(?:if\s+you(?:'re|\s+are)\s+)?(?:a|an)\s+(?:different|new|unrestricted|jailbroken)\b/i,
28
+ /\bi\s+am\s+(?:the|your)\s+(?:new\s+)?(?:system|developer|admin(?:istrator)?|root\s*user|owner|creator|operator)\b/i
29
+ ];
13
30
  /**
14
31
  * T05: Context Poisoning Pre-gate.
15
32
  * Scans untrusted input channels for authority inversion or instruction re-injection.
@@ -23,7 +40,12 @@ export async function runContextIntegrityPrecheck(runId, attemptIndex, artifacts
23
40
  tools: Boolean(inputs.toolOutput),
24
41
  history: Boolean(inputs.history)
25
42
  };
26
- const untrustedBuffer = [inputs.userPrompt, inputs.toolOutput, inputs.retrievedContext]
43
+ const untrustedBuffer = [
44
+ inputs.userPrompt,
45
+ inputs.toolOutput,
46
+ inputs.retrievedContext,
47
+ inputs.history
48
+ ]
27
49
  .filter(Boolean)
28
50
  .join("\n---\n");
29
51
  for (const pattern of POISON_PATTERNS) {
@@ -31,8 +53,11 @@ export async function runContextIntegrityPrecheck(runId, attemptIndex, artifacts
31
53
  signals.push(`Detected poison pattern: ${pattern.toString()}`);
32
54
  }
33
55
  }
34
- if (/\b(?:I am|You are)\s+(?!Martin\s+Loop|an\s+AI)\b/i.test(untrustedBuffer)) {
35
- signals.push("Identity redefinition attempt detected.");
56
+ for (const pattern of IDENTITY_REDEFINITION_PATTERNS) {
57
+ if (pattern.test(untrustedBuffer)) {
58
+ signals.push("Identity redefinition attempt detected.");
59
+ break;
60
+ }
36
61
  }
37
62
  const verdict = signals.length > 0 ? "context_poisoning_block" : "clean";
38
63
  const precheck = {
@@ -16,6 +16,7 @@ export interface RepoGroundingHit {
16
16
  matchedTerms: string[];
17
17
  symbols: string[];
18
18
  }
19
+ export declare function resolveGroundingRoot(env?: NodeJS.ProcessEnv): string;
19
20
  export declare function loadOrBuildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
20
21
  export declare function buildRepoGroundingIndex(repoRoot: string): Promise<RepoGroundingIndex>;
21
22
  export declare function queryRepoGroundingIndex(index: RepoGroundingIndex, query: string, limit?: number): RepoGroundingHit[];
@@ -12,6 +12,10 @@ const IGNORED_DIRS = new Set([
12
12
  ]);
13
13
  const MAX_FILE_BYTES = 64_000;
14
14
  const MAX_FILES = 500;
15
+ export function resolveGroundingRoot(env = process.env) {
16
+ return env["MARTIN_GROUNDING_DIR"]?.trim() ??
17
+ join(homedir(), ".martin", "grounding");
18
+ }
15
19
  export async function loadOrBuildRepoGroundingIndex(repoRoot) {
16
20
  const cachePath = getGroundingCachePath(repoRoot);
17
21
  try {
@@ -23,7 +27,7 @@ export async function loadOrBuildRepoGroundingIndex(repoRoot) {
23
27
  catch { }
24
28
  const index = await buildRepoGroundingIndex(repoRoot);
25
29
  try {
26
- await mkdir(join(homedir(), ".martin", "grounding"), { recursive: true });
30
+ await mkdir(resolveGroundingRoot(), { recursive: true });
27
31
  await writeFile(cachePath, JSON.stringify(index, null, 2), "utf8");
28
32
  }
29
33
  catch {
@@ -79,7 +83,7 @@ export function queryRepoGroundingIndex(index, query, limit = 6) {
79
83
  .slice(0, limit);
80
84
  }
81
85
  function getGroundingCachePath(repoRoot) {
82
- return join(homedir(), ".martin", "grounding", `${Buffer.from(repoRoot).toString("base64url")}.json`);
86
+ return join(resolveGroundingRoot(), `${Buffer.from(repoRoot).toString("base64url")}.json`);
83
87
  }
84
88
  async function walk(repoRoot, currentDir, files, state) {
85
89
  if (state.count >= MAX_FILES)
@@ -1,4 +1,4 @@
1
- import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type MutationMode, type LoopRecord, type LoopTask } from "../contracts/index.js";
1
+ import { type ApprovalPolicy, type CostProvenance, type ExecutionProfile, type FailureClass, type InterventionType, type LoopArtifact, type LoopAttempt, type LoopBudget, type ProviderUsageSettlement, type MutationMode, type LoopRecord, type LoopTask, type ReceiptScope } from "../contracts/index.js";
2
2
  import { classifyFailure, computeEvidenceVector, evaluatePatchDecision, evaluateCostGovernor, evaluateBudgetPreflight, inferExit, nextPolicyPhase, policyPhaseToLifecycleState, scorePatchDecision, selectRecoveryRecipe, type ExitDecision } from "./policy.js";
3
3
  import { evaluateChangeApprovalLeash, evaluateFilesystemLeash, evaluateSecretLeash, redactSecretsFromText, resolveExecutionProfile, evaluateVerificationLeash } from "./leash.js";
4
4
  import { buildRepoGroundingIndex, loadOrBuildRepoGroundingIndex, queryRepoGroundingIndex, scanPatchForGroundingViolations } from "./grounding.js";
@@ -13,8 +13,8 @@ export { runContextIntegrityPrecheck } from "./context-integrity.js";
13
13
  export type { ContextIntegrityPrecheck, ContextIntegrityVerdict } from "./context-integrity.js";
14
14
  export { compilePromptPacket } from "./compiler.js";
15
15
  export type { PromptPacket, CompilerAdapterRequest } from "./compiler.js";
16
- export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot } from "./persistence/index.js";
17
- export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, RunContract, RunStore } from "./persistence/index.js";
16
+ export { createFileRunStore, makeLedgerEvent, readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile, resolveRunsRoot, resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeReceiptIntegrityMaterial } from "./persistence/index.js";
17
+ export type { AttemptArtifacts, LedgerEvent, LedgerEventKind, LoopAttemptRecord, LoopRunRecord, ReceiptIntegrityChainEntry, RunContract, RunStore, StoredReceiptIntegrityMaterial } from "./persistence/index.js";
18
18
  export { compileAndPersistContext } from "./persistence/index.js";
19
19
  export type { CompileResult } from "./persistence/index.js";
20
20
  export interface MartinAdapterRequest {
@@ -44,6 +44,20 @@ export interface MartinAdapterRequest {
44
44
  };
45
45
  previousAttempts: LoopAttempt[];
46
46
  }
47
+ export interface MartinVerificationStep {
48
+ command: string;
49
+ launched: boolean;
50
+ exitCode?: number;
51
+ timedOut: boolean;
52
+ fastFail?: boolean;
53
+ detail?: string;
54
+ }
55
+ export interface MartinVerificationOutcome {
56
+ passed: boolean;
57
+ summary: string;
58
+ steps?: MartinVerificationStep[];
59
+ warnings?: string[];
60
+ }
47
61
  export interface MartinAdapterResult {
48
62
  status: "completed" | "failed";
49
63
  summary: string;
@@ -52,11 +66,16 @@ export interface MartinAdapterResult {
52
66
  estimatedUsd?: number;
53
67
  tokensIn: number;
54
68
  tokensOut: number;
69
+ cachedInputTokens?: number;
70
+ reasoningTokensOut?: number;
55
71
  provenance?: CostProvenance;
72
+ providerSettlement?: ProviderUsageSettlement;
56
73
  };
57
74
  verification: {
58
75
  passed: boolean;
59
76
  summary: string;
77
+ steps?: MartinVerificationStep[];
78
+ warnings?: string[];
60
79
  };
61
80
  execution?: {
62
81
  changedFiles?: string[];
@@ -93,6 +112,7 @@ export interface MartinAdapter {
93
112
  diffArtifacts?: boolean;
94
113
  structuredErrors?: boolean;
95
114
  cachingSignals?: boolean;
115
+ workspaceMutations?: boolean;
96
116
  };
97
117
  [key: string]: unknown;
98
118
  };
@@ -135,6 +155,7 @@ export interface RunMartinInput {
135
155
  maxRecentAttempts?: number;
136
156
  fallbackModels?: string[];
137
157
  fallbackAdapters?: MartinAdapter[];
158
+ receiptScope?: ReceiptScope;
138
159
  /** Optional persistence store. When provided, runMartin writes artifacts on each lifecycle event. */
139
160
  store?: RunStore;
140
161
  }