@martinloop/mcp 0.2.0 → 0.2.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 (96) hide show
  1. package/README.md +118 -182
  2. package/dist/discovery-metadata.d.ts +21 -0
  3. package/dist/discovery-metadata.js +152 -0
  4. package/dist/discovery-support.d.ts +62 -0
  5. package/dist/discovery-support.js +224 -0
  6. package/dist/package-version.d.ts +1 -0
  7. package/dist/package-version.js +3 -0
  8. package/dist/prompts.d.ts +13 -3
  9. package/dist/prompts.js +537 -74
  10. package/dist/resources.d.ts +35 -5
  11. package/dist/resources.js +788 -71
  12. package/dist/server-validation.d.ts +2 -3
  13. package/dist/server-validation.js +375 -119
  14. package/dist/server.d.ts +76 -7
  15. package/dist/server.js +1478 -394
  16. package/dist/tools/doctor.d.ts +2 -0
  17. package/dist/tools/doctor.js +18 -6
  18. package/dist/tools/eval.d.ts +24 -0
  19. package/dist/tools/eval.js +65 -0
  20. package/dist/tools/get-attempt.d.ts +13 -6
  21. package/dist/tools/get-attempt.js +14 -5
  22. package/dist/tools/get-run.d.ts +19 -12
  23. package/dist/tools/get-run.js +20 -11
  24. package/dist/tools/get-status.d.ts +19 -0
  25. package/dist/tools/get-status.js +30 -2
  26. package/dist/tools/get-verification-results.d.ts +10 -7
  27. package/dist/tools/get-verification-results.js +11 -6
  28. package/dist/tools/inspect-loop.d.ts +9 -0
  29. package/dist/tools/inspect-loop.js +11 -2
  30. package/dist/tools/list-runs.d.ts +25 -5
  31. package/dist/tools/list-runs.js +21 -4
  32. package/dist/tools/logs.d.ts +25 -0
  33. package/dist/tools/logs.js +49 -0
  34. package/dist/tools/plan.d.ts +20 -0
  35. package/dist/tools/plan.js +10 -0
  36. package/dist/tools/pr-tools.d.ts +31 -0
  37. package/dist/tools/pr-tools.js +111 -0
  38. package/dist/tools/preflight.d.ts +10 -0
  39. package/dist/tools/preflight.js +18 -4
  40. package/dist/tools/run-controls.d.ts +36 -0
  41. package/dist/tools/run-controls.js +88 -0
  42. package/dist/tools/run-dossier.d.ts +51 -4
  43. package/dist/tools/run-dossier.js +100 -5
  44. package/dist/tools/run-loop.d.ts +19 -0
  45. package/dist/tools/run-loop.js +61 -4
  46. package/dist/tools/run-store.d.ts +57 -3
  47. package/dist/tools/run-store.js +404 -53
  48. package/dist/tools/tool-errors.d.ts +37 -0
  49. package/dist/tools/tool-errors.js +170 -0
  50. package/dist/tools/tool-response.d.ts +16 -0
  51. package/dist/tools/tool-response.js +34 -0
  52. package/dist/tools/tool-support.d.ts +92 -2
  53. package/dist/tools/tool-support.js +385 -63
  54. package/dist/tools/triage-runs.d.ts +33 -0
  55. package/dist/tools/triage-runs.js +138 -0
  56. package/dist/tools/workflow-governance.d.ts +133 -0
  57. package/dist/tools/workflow-governance.js +581 -0
  58. package/dist/vendor/adapters/claude-cli.js +0 -1
  59. package/dist/vendor/adapters/cli-bridge.d.ts +5 -0
  60. package/dist/vendor/adapters/cli-bridge.js +16 -9
  61. package/dist/vendor/adapters/direct-provider.js +0 -1
  62. package/dist/vendor/adapters/index.d.ts +2 -1
  63. package/dist/vendor/adapters/index.js +2 -1
  64. package/dist/vendor/adapters/openai-compatible.d.ts +47 -0
  65. package/dist/vendor/adapters/openai-compatible.js +242 -0
  66. package/dist/vendor/adapters/runtime-support.js +0 -1
  67. package/dist/vendor/adapters/stub-agent-cli.js +0 -1
  68. package/dist/vendor/adapters/stub-direct-provider.js +0 -1
  69. package/dist/vendor/adapters/verifier-only.js +0 -1
  70. package/dist/vendor/contracts/governance.js +0 -1
  71. package/dist/vendor/contracts/index.d.ts +2 -0
  72. package/dist/vendor/contracts/index.js +1 -1
  73. package/dist/vendor/contracts/operator.d.ts +19 -0
  74. package/dist/vendor/contracts/operator.js +11 -0
  75. package/dist/vendor/core/compiler.js +0 -1
  76. package/dist/vendor/core/context-integrity.js +0 -1
  77. package/dist/vendor/core/grounding.js +0 -1
  78. package/dist/vendor/core/index.js +1 -2
  79. package/dist/vendor/core/leash.js +19 -12
  80. package/dist/vendor/core/persistence/compiler.js +0 -1
  81. package/dist/vendor/core/persistence/index.js +0 -1
  82. package/dist/vendor/core/persistence/ledger.js +0 -1
  83. package/dist/vendor/core/persistence/runs-reader.js +0 -1
  84. package/dist/vendor/core/persistence/store.js +0 -1
  85. package/dist/vendor/core/policy.js +0 -1
  86. package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
  87. package/dist/vendor/core/red-blue/red-phase.js +135 -0
  88. package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
  89. package/dist/vendor/core/red-blue/risk-tiers.js +32 -0
  90. package/dist/vendor/core/rollback.js +2 -3
  91. package/dist/workflow-state.d.ts +25 -0
  92. package/dist/workflow-state.js +102 -0
  93. package/package.json +12 -7
  94. package/server.json +2 -2
  95. package/dist/tools/cockpit-support.d.ts +0 -69
  96. package/dist/tools/cockpit-support.js +0 -108
@@ -0,0 +1,47 @@
1
+ /**
2
+ * OpenAI-compatible adapter for MartinLoop.
3
+ *
4
+ * Routes agent execution to any endpoint that implements the OpenAI
5
+ * Chat Completions API (`POST /v1/chat/completions`). This covers:
6
+ *
7
+ * Hosted via OpenRouter / Together.ai / Fireworks.ai:
8
+ * DeepSeek-V3, DeepSeek-R1, Qwen3-235B, Mistral Large, Codestral,
9
+ * Kimi k2, Nemotron-70B, and hundreds more.
10
+ *
11
+ * Local via Ollama / LM Studio / llama.cpp:
12
+ * Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
13
+ *
14
+ * Usage:
15
+ * MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
16
+ * MARTIN_OPENAI_API_KEY=sk-or-...
17
+ * MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
18
+ * martin run "fix the bug" --engine openai
19
+ *
20
+ * Or for Ollama:
21
+ * MARTIN_OPENAI_BASE_URL=http://localhost:11434
22
+ * MARTIN_OPENAI_MODEL=llama3.3
23
+ * martin run "fix the bug" --engine openai
24
+ */
25
+ import type { MartinAdapter } from "../core/index.js";
26
+ export interface OpenAiCompatibleAdapterOptions {
27
+ /** Base URL of the OpenAI-compatible API. No trailing slash. */
28
+ baseUrl: string;
29
+ /** API key. Empty string for local (Ollama/LM Studio) endpoints. */
30
+ apiKey?: string;
31
+ /** Model identifier passed as-is to the API (e.g. "deepseek/deepseek-chat"). */
32
+ model: string;
33
+ /**
34
+ * System prompt prepended before the MartinLoop task prompt.
35
+ * Default instructs the model to act as a focused coding assistant.
36
+ */
37
+ systemPrompt?: string;
38
+ /** Request timeout in milliseconds. Default: 300_000 (5 min). */
39
+ timeoutMs?: number;
40
+ /** Verifier timeout in milliseconds. Default: 60_000. */
41
+ verifyTimeoutMs?: number;
42
+ /** Working directory for git artifact collection and verification. */
43
+ workingDirectory?: string;
44
+ /** Optional fetch override for testing. */
45
+ fetchImpl?: typeof fetch;
46
+ }
47
+ export declare function createOpenAiCompatibleAdapter(options: OpenAiCompatibleAdapterOptions): MartinAdapter;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * OpenAI-compatible adapter for MartinLoop.
3
+ *
4
+ * Routes agent execution to any endpoint that implements the OpenAI
5
+ * Chat Completions API (`POST /v1/chat/completions`). This covers:
6
+ *
7
+ * Hosted via OpenRouter / Together.ai / Fireworks.ai:
8
+ * DeepSeek-V3, DeepSeek-R1, Qwen3-235B, Mistral Large, Codestral,
9
+ * Kimi k2, Nemotron-70B, and hundreds more.
10
+ *
11
+ * Local via Ollama / LM Studio / llama.cpp:
12
+ * Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
13
+ *
14
+ * Usage:
15
+ * MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
16
+ * MARTIN_OPENAI_API_KEY=sk-or-...
17
+ * MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
18
+ * martin run "fix the bug" --engine openai
19
+ *
20
+ * Or for Ollama:
21
+ * MARTIN_OPENAI_BASE_URL=http://localhost:11434
22
+ * MARTIN_OPENAI_MODEL=llama3.3
23
+ * martin run "fix the bug" --engine openai
24
+ */
25
+ import { runVerification, readGitExecutionArtifacts } from "./cli-bridge.js";
26
+ import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
27
+ // ---------------------------------------------------------------------------
28
+ // OpenRouter/OpenAI-compatible model pricing ($/1K tokens)
29
+ // Automatically used when baseUrl contains openrouter.ai or known providers.
30
+ // Defaults to a conservative blended estimate for unknown models.
31
+ // ---------------------------------------------------------------------------
32
+ const KNOWN_MODEL_PRICING = {
33
+ // DeepSeek
34
+ "deepseek/deepseek-chat": { inputPer1K: 0.00027, outputPer1K: 0.0011 },
35
+ "deepseek/deepseek-r1": { inputPer1K: 0.0008, outputPer1K: 0.0032 },
36
+ "deepseek/deepseek-coder": { inputPer1K: 0.00014, outputPer1K: 0.00028 },
37
+ // Qwen
38
+ "qwen/qwen3-235b-a22b": { inputPer1K: 0.00022, outputPer1K: 0.00088 },
39
+ "qwen/qwen3-32b": { inputPer1K: 0.00009, outputPer1K: 0.00009 },
40
+ "qwen/qwen-2.5-coder-32b-instruct": { inputPer1K: 0.00007, outputPer1K: 0.00007 },
41
+ // Mistral
42
+ "mistralai/codestral-latest": { inputPer1K: 0.0003, outputPer1K: 0.0009 },
43
+ "mistralai/mistral-large": { inputPer1K: 0.003, outputPer1K: 0.009 },
44
+ "mistralai/mistral-small": { inputPer1K: 0.0001, outputPer1K: 0.0003 },
45
+ // Kimi
46
+ "moonshotai/kimi-k2": { inputPer1K: 0.00065, outputPer1K: 0.0026 },
47
+ // Nemotron
48
+ "nvidia/llama-3.1-nemotron-70b-instruct": { inputPer1K: 0.00012, outputPer1K: 0.0003 },
49
+ // Llama (via OpenRouter)
50
+ "meta-llama/llama-3.3-70b-instruct": { inputPer1K: 0.00012, outputPer1K: 0.0003 },
51
+ "meta-llama/llama-3.1-405b-instruct": { inputPer1K: 0.0008, outputPer1K: 0.0008 },
52
+ };
53
+ const FALLBACK_INPUT_PER_1K = 0.0003;
54
+ const FALLBACK_OUTPUT_PER_1K = 0.0012;
55
+ const CHARS_PER_TOKEN = 4;
56
+ function estimateCost(model, inputChars, outputChars) {
57
+ const pricing = KNOWN_MODEL_PRICING[model] ?? {
58
+ inputPer1K: FALLBACK_INPUT_PER_1K,
59
+ outputPer1K: FALLBACK_OUTPUT_PER_1K
60
+ };
61
+ const tokensIn = Math.ceil(inputChars / CHARS_PER_TOKEN);
62
+ const tokensOut = Math.ceil(outputChars / CHARS_PER_TOKEN);
63
+ const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
64
+ return { tokensIn, tokensOut, actualUsd };
65
+ }
66
+ // ---------------------------------------------------------------------------
67
+ // Prompt builder
68
+ // ---------------------------------------------------------------------------
69
+ const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
70
+ Follow these rules exactly:
71
+ - Read the task description carefully and implement only what is asked.
72
+ - Do not add features, refactors, or improvements beyond the stated task.
73
+ - If the task asks you to write or modify code, output the complete file content with changes applied.
74
+ - Be precise, minimal, and test-backed in all changes.
75
+ - State what you changed and why at the end of your response.`;
76
+ function buildPrompt(request) {
77
+ const lines = [
78
+ `TASK: ${request.context.taskTitle}`,
79
+ ``,
80
+ `OBJECTIVE:`,
81
+ request.context.objective,
82
+ ``
83
+ ];
84
+ if (request.context.focus) {
85
+ lines.push(`FOCUS: ${request.context.focus}`, ``);
86
+ }
87
+ if (request.context.verificationPlan.length > 0) {
88
+ lines.push(`VERIFICATION COMMANDS (must pass after your changes):`, ...request.context.verificationPlan.map((cmd) => ` ${cmd}`), ``);
89
+ }
90
+ if (request.previousAttempts.length > 0) {
91
+ const last = request.previousAttempts.at(-1);
92
+ if (last) {
93
+ lines.push(`PREVIOUS ATTEMPT SUMMARY:`, last.summary ?? "", ``);
94
+ }
95
+ }
96
+ lines.push(`BUDGET REMAINING: $${request.context.remainingBudgetUsd.toFixed(4)} | Iterations left: ${request.context.remainingIterations}`);
97
+ return lines.join("\n");
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Factory
101
+ // ---------------------------------------------------------------------------
102
+ export function createOpenAiCompatibleAdapter(options) {
103
+ const workingDirectory = options.workingDirectory ?? process.cwd();
104
+ const timeoutMs = options.timeoutMs ?? 300_000;
105
+ const verifyTimeoutMs = options.verifyTimeoutMs ?? 60_000;
106
+ const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
107
+ const fetchFn = options.fetchImpl ?? globalThis.fetch;
108
+ return {
109
+ adapterId: `openai-compatible:${options.model}`,
110
+ kind: "direct-provider",
111
+ label: `OpenAI-compatible: ${options.model}`,
112
+ metadata: {
113
+ providerId: "openai-compatible",
114
+ model: options.model,
115
+ transport: "http",
116
+ capabilities: createAdapterCapabilities({
117
+ preflight: true,
118
+ usageSettlement: true,
119
+ diffArtifacts: true
120
+ })
121
+ },
122
+ async execute(request) {
123
+ const prompt = buildPrompt(request);
124
+ const estimated = estimateCost(options.model, prompt.length, 2000);
125
+ // Preflight: bail if projected cost exceeds remaining budget
126
+ if (request.context.remainingBudgetUsd > 0 &&
127
+ estimated.actualUsd > request.context.remainingBudgetUsd * 0.95) {
128
+ return {
129
+ status: "failed",
130
+ summary: `Preflight: projected cost $${estimated.actualUsd.toFixed(4)} exceeds remaining budget $${request.context.remainingBudgetUsd.toFixed(4)}.`,
131
+ usage: normalizeUsage({
132
+ actualUsd: estimated.actualUsd,
133
+ estimatedUsd: estimated.actualUsd,
134
+ tokensIn: estimated.tokensIn,
135
+ tokensOut: estimated.tokensOut,
136
+ provenance: "estimated"
137
+ }),
138
+ verification: { passed: false, summary: "Stopped before execution: budget preflight failed." },
139
+ failure: { message: "budget_preflight_exceeded", classHint: "budget_pressure" }
140
+ };
141
+ }
142
+ // Call the OpenAI-compatible endpoint
143
+ const endpoint = `${options.baseUrl.replace(/\/$/, "")}/v1/chat/completions`;
144
+ let responseText = "";
145
+ let tokensIn = estimated.tokensIn;
146
+ let tokensOut = 0;
147
+ const controller = new AbortController();
148
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
149
+ try {
150
+ const headers = { "Content-Type": "application/json" };
151
+ if (options.apiKey)
152
+ headers["Authorization"] = `Bearer ${options.apiKey}`;
153
+ // OpenRouter requires a site URL header for attribution
154
+ if (options.baseUrl.includes("openrouter")) {
155
+ headers["HTTP-Referer"] = "https://martinloop.com";
156
+ headers["X-Title"] = "MartinLoop";
157
+ }
158
+ const res = await fetchFn(endpoint, {
159
+ method: "POST",
160
+ headers,
161
+ body: JSON.stringify({
162
+ model: options.model,
163
+ messages: [
164
+ { role: "system", content: systemPrompt },
165
+ { role: "user", content: prompt }
166
+ ],
167
+ temperature: 0.2,
168
+ max_tokens: 8192
169
+ }),
170
+ signal: controller.signal
171
+ });
172
+ const body = (await res.json());
173
+ if (!res.ok || body.error) {
174
+ const errMsg = body.error?.message ?? `HTTP ${res.status}`;
175
+ return {
176
+ status: "failed",
177
+ summary: `${options.model} API error: ${errMsg}`,
178
+ usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
179
+ verification: { passed: false, summary: "API call failed before verifier." },
180
+ failure: { message: errMsg, classHint: "infrastructure_error" }
181
+ };
182
+ }
183
+ responseText = body.choices?.[0]?.message?.content ?? "";
184
+ if (body.usage) {
185
+ tokensIn = body.usage.prompt_tokens ?? tokensIn;
186
+ tokensOut = body.usage.completion_tokens ?? 0;
187
+ }
188
+ else {
189
+ tokensOut = Math.ceil(responseText.length / CHARS_PER_TOKEN);
190
+ }
191
+ }
192
+ catch (error) {
193
+ const isAbort = error instanceof Error && error.name === "AbortError";
194
+ const message = isAbort ? `${options.model} request timed out after ${timeoutMs}ms` : String(error);
195
+ return {
196
+ status: "failed",
197
+ summary: message,
198
+ usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
199
+ verification: { passed: false, summary: isAbort ? "Request timed out." : "Network error." },
200
+ failure: { message, classHint: "infrastructure_error" }
201
+ };
202
+ }
203
+ finally {
204
+ clearTimeout(timer);
205
+ }
206
+ if (!responseText.trim()) {
207
+ return {
208
+ status: "failed",
209
+ summary: `${options.model} returned an empty response.`,
210
+ usage: normalizeUsage({ actualUsd: 0, tokensIn, tokensOut: 0, provenance: "actual" }),
211
+ verification: { passed: false, summary: "Empty response — nothing to verify." },
212
+ failure: { message: "empty_response" }
213
+ };
214
+ }
215
+ // Run verification
216
+ 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] ?? {
219
+ inputPer1K: FALLBACK_INPUT_PER_1K,
220
+ outputPer1K: FALLBACK_OUTPUT_PER_1K
221
+ };
222
+ const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
223
+ return {
224
+ status: verification.passed ? "completed" : "failed",
225
+ summary: verification.passed
226
+ ? `${options.model} completed the task. Verifier passed.`
227
+ : `${options.model} completed but verifier failed: ${verification.summary}`,
228
+ usage: normalizeUsage({
229
+ actualUsd,
230
+ tokensIn,
231
+ tokensOut,
232
+ provenance: "actual"
233
+ }),
234
+ verification,
235
+ execution,
236
+ ...(verification.passed ? {} : {
237
+ failure: { message: verification.summary }
238
+ })
239
+ };
240
+ }
241
+ };
242
+ }
@@ -49,4 +49,3 @@ export function normalizeStructuredErrors(errors) {
49
49
  function roundUsd(value) {
50
50
  return Math.round(value * 1_000_000) / 1_000_000;
51
51
  }
52
- //# sourceMappingURL=runtime-support.js.map
@@ -38,4 +38,3 @@ export function createStubAgentCliAdapter(options) {
38
38
  }
39
39
  };
40
40
  }
41
- //# sourceMappingURL=stub-agent-cli.js.map
@@ -7,4 +7,3 @@ export function createStubDirectProviderAdapter(options) {
7
7
  responder: options.responder
8
8
  });
9
9
  }
10
- //# sourceMappingURL=stub-direct-provider.js.map
@@ -54,4 +54,3 @@ export function createVerifierOnlyAdapter(options = {}) {
54
54
  }
55
55
  };
56
56
  }
57
- //# sourceMappingURL=verifier-only.js.map
@@ -9,4 +9,3 @@ export function createGovernanceSnapshot(snapshot) {
9
9
  }))
10
10
  };
11
11
  }
12
- //# sourceMappingURL=governance.js.map
@@ -108,6 +108,8 @@ export interface LoopRecordDraft {
108
108
  createdAt?: string;
109
109
  updatedAt?: string;
110
110
  }
111
+ export type { MartinErrorCategory, MartinOutputMode, MartinRunListFilters, MartinRunSelector } from "./operator.js";
112
+ export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
111
113
  export interface LoopEventDraft {
112
114
  type: LoopEventType;
113
115
  lifecycleState?: LoopLifecycleState;
@@ -1,3 +1,4 @@
1
+ export { MARTIN_ERROR_CATEGORIES } from "./operator.js";
1
2
  export const DEFAULT_BUDGET = {
2
3
  maxUsd: 25,
3
4
  softLimitUsd: 15,
@@ -200,4 +201,3 @@ function hasText(value) {
200
201
  return typeof value === "string" && value.trim().length > 0;
201
202
  }
202
203
  export { createGovernanceSnapshot } from "./governance.js";
203
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,19 @@
1
+ export declare const MARTIN_ERROR_CATEGORIES: readonly ["invalid_input", "environment", "auth", "not_found", "store_unreadable", "verification_failed", "policy_blocked", "budget_exit", "transient"];
2
+ export type MartinErrorCategory = (typeof MARTIN_ERROR_CATEGORIES)[number];
3
+ export type MartinOutputMode = "human" | "json" | "quiet";
4
+ export interface MartinRunSelector {
5
+ runsDir?: string;
6
+ file?: string;
7
+ loopId?: string;
8
+ latest?: boolean;
9
+ attemptIndex?: number;
10
+ }
11
+ export interface MartinRunListFilters {
12
+ runsDir?: string;
13
+ limit?: number;
14
+ status?: string;
15
+ lifecycleState?: string;
16
+ adapterId?: string;
17
+ model?: string;
18
+ updatedAfter?: string;
19
+ }
@@ -0,0 +1,11 @@
1
+ export const MARTIN_ERROR_CATEGORIES = [
2
+ "invalid_input",
3
+ "environment",
4
+ "auth",
5
+ "not_found",
6
+ "store_unreadable",
7
+ "verification_failed",
8
+ "policy_blocked",
9
+ "budget_exit",
10
+ "transient"
11
+ ];
@@ -50,4 +50,3 @@ export function compilePromptPacket(request) {
50
50
  }
51
51
  };
52
52
  }
53
- //# sourceMappingURL=compiler.js.map
@@ -53,4 +53,3 @@ export async function runContextIntegrityPrecheck(runId, attemptIndex, artifacts
53
53
  }
54
54
  return precheck;
55
55
  }
56
- //# sourceMappingURL=context-integrity.js.map
@@ -267,4 +267,3 @@ function matchesGlobPattern(filePath, pattern) {
267
267
  .replace(/__DOUBLESTAR__/g, ".*");
268
268
  return new RegExp(`^${regexStr}$`).test(filePath);
269
269
  }
270
- //# sourceMappingURL=grounding.js.map
@@ -1099,7 +1099,7 @@ function resolveChangedFiles(result, repoRoot) {
1099
1099
  return [];
1100
1100
  }
1101
1101
  try {
1102
- const diff = spawnSync("git", ["diff", "--name-only", "HEAD"], {
1102
+ const diff = spawnSync("git", ["diff", "--name-only", "HEAD", "--", "."], {
1103
1103
  cwd: repoRoot,
1104
1104
  encoding: "utf8"
1105
1105
  });
@@ -1254,4 +1254,3 @@ function applyPatchFailureToLoop(loop, input) {
1254
1254
  : attempt)
1255
1255
  };
1256
1256
  }
1257
- //# sourceMappingURL=index.js.map
@@ -9,7 +9,7 @@ const BLOCKED_PATTERNS = [
9
9
  /(^|\s)mkfs(\.|\s|$)/u,
10
10
  /(^|\s)dd\s+if=/u,
11
11
  /(shutdown|reboot)(\s|$)/iu,
12
- /:\(\)\{:\|:&\};:/u,
12
+ /:\(\)\s*\{\s*:\|:&\s*\}\s*;\s*:/u,
13
13
  /chmod\s+-R\s+777\s+\//iu,
14
14
  /(kubectl|docker)\s+.*\b(delete|prune|rm)\b/iu,
15
15
  /ssh\s+/iu,
@@ -314,17 +314,25 @@ function normalizeChangedFile(file, repoRoot) {
314
314
  };
315
315
  }
316
316
  function matchesPathPattern(file, pattern) {
317
- const normalizedFile = file.replace(/\\/gu, "/");
318
- const normalizedPattern = pattern.replace(/\\/gu, "/");
319
- if (normalizedPattern.includes("**")) {
320
- const prefix = normalizedPattern.split("**")[0] ?? normalizedPattern;
321
- return normalizedFile.startsWith(prefix.replace(/\/$/u, ""));
317
+ const normalizedFile = normalizePathForMatching(file);
318
+ const normalizedPattern = normalizePathForMatching(pattern);
319
+ if (!normalizedPattern.includes("*")) {
320
+ return (normalizedFile === normalizedPattern ||
321
+ normalizedFile.startsWith(`${normalizedPattern.replace(/\/$/u, "")}/`));
322
322
  }
323
- if (normalizedPattern.endsWith("*")) {
324
- return normalizedFile.startsWith(normalizedPattern.replace(/\*+$/u, ""));
325
- }
326
- return (normalizedFile === normalizedPattern ||
327
- normalizedFile.startsWith(`${normalizedPattern.replace(/\/$/u, "")}/`));
323
+ const regexStr = normalizedPattern
324
+ .replace(/[.+^${}()|[\]\\]/gu, "\\$&")
325
+ .replace(/\*\*/gu, "__DOUBLESTAR__")
326
+ .replace(/\*/gu, "[^/]*")
327
+ .replace(/__DOUBLESTAR__/gu, ".*");
328
+ return new RegExp(`^${regexStr}$`, "u").test(normalizedFile);
329
+ }
330
+ function normalizePathForMatching(value) {
331
+ return value
332
+ .replace(/\\/gu, "/")
333
+ .replace(/^\.\//u, "")
334
+ .replace(/\/{2,}/gu, "/")
335
+ .replace(/\/$/u, "");
328
336
  }
329
337
  function buildNetworkViolation(command, profile) {
330
338
  const targets = extractNetworkTargets(command);
@@ -405,4 +413,3 @@ function isConfigChangeFile(file) {
405
413
  normalized.startsWith("infrastructure/") ||
406
414
  normalized.startsWith("ops/"));
407
415
  }
408
- //# sourceMappingURL=leash.js.map
@@ -32,4 +32,3 @@ export async function compileAndPersistContext(request, options) {
32
32
  }
33
33
  return { packet };
34
34
  }
35
- //# sourceMappingURL=compiler.js.map
@@ -2,4 +2,3 @@ export { makeLedgerEvent } from "./ledger.js";
2
2
  export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
3
3
  export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile } from "./runs-reader.js";
4
4
  export { compileAndPersistContext } from "./compiler.js";
5
- //# sourceMappingURL=index.js.map
@@ -7,4 +7,3 @@ export function makeLedgerEvent(draft, options = {}) {
7
7
  ...(draft.attemptIndex !== undefined ? { attemptIndex: draft.attemptIndex } : {})
8
8
  };
9
9
  }
10
- //# sourceMappingURL=ledger.js.map
@@ -81,4 +81,3 @@ export async function readLatestLoopRecord(runsDir) {
81
81
  return a > b ? r : latest;
82
82
  }, records[0]);
83
83
  }
84
- //# sourceMappingURL=runs-reader.js.map
@@ -86,4 +86,3 @@ export function createFileRunStore(options = {}) {
86
86
  async function writeJsonFile(path, value) {
87
87
  await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
88
88
  }
89
- //# sourceMappingURL=store.js.map
@@ -622,4 +622,3 @@ function computeDiffRiskScore(input) {
622
622
  function roundScore(value) {
623
623
  return Math.round(value * 100) / 100;
624
624
  }
625
- //# sourceMappingURL=policy.js.map
@@ -0,0 +1,64 @@
1
+ import { type RiskTier } from "./risk-tiers.js";
2
+ export interface RedFinding {
3
+ trapId: string;
4
+ severity: "warn" | "block";
5
+ description: string;
6
+ resolvedAt?: string;
7
+ }
8
+ export interface RedFindings {
9
+ riskTier: RiskTier;
10
+ probesRun: number;
11
+ findingsCount: number;
12
+ findings: RedFinding[];
13
+ modelCallMade: boolean;
14
+ modelUsed?: string;
15
+ budgetUsedUsd: number;
16
+ }
17
+ /** Minimal interface for the Anthropic model client (mockable in tests). */
18
+ export interface MockModelClient {
19
+ complete(prompt: string): Promise<{
20
+ findings: RedFinding[];
21
+ tokensUsed: number;
22
+ costUsd: number;
23
+ }>;
24
+ }
25
+ export interface RunRedPhaseOptions {
26
+ /** Inject a mock or real Anthropic client. Required for release_critical tier. */
27
+ modelClient?: MockModelClient;
28
+ /** Callback fired with each ledger event produced by the phase. */
29
+ onLedgerEvent?: (event: RedLedgerEvent) => void;
30
+ }
31
+ export interface RedLedgerEvent {
32
+ type: "red_phase_findings";
33
+ runId?: string;
34
+ riskTier: RiskTier;
35
+ probesRun: number;
36
+ findingsCount: number;
37
+ modelCallMade: boolean;
38
+ timestamp: string;
39
+ }
40
+ export interface PatchInput {
41
+ patchId: string;
42
+ diff: string;
43
+ changedFiles: string[];
44
+ }
45
+ /**
46
+ * Runs the Red phase for a given patch and risk tier.
47
+ *
48
+ * - baseline: programmatic probes only, no model call
49
+ * - high_risk: paranoid programmatic scan, no model call
50
+ * - release_critical: paranoid scan + one Haiku model call
51
+ */
52
+ export declare function runRedPhase(patch: PatchInput, tier: RiskTier, blueBudgetUsd: number, options?: RunRedPhaseOptions): Promise<RedFindings>;
53
+ /**
54
+ * Returns true only if the findings contain zero block-severity entries.
55
+ * A single block finding rejects the patch regardless of warn count.
56
+ */
57
+ export declare function shouldAcceptPatch(findings: RedFindings): boolean;
58
+ /**
59
+ * Convenience builder for RedFindings — useful in tests and policy engine.
60
+ */
61
+ export declare function buildRedFindings(input: Partial<RedFindings> & {
62
+ riskTier: RiskTier;
63
+ findings: RedFinding[];
64
+ }): RedFindings;