@martinloop/mcp 0.3.3 → 0.3.5
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 +112 -107
- package/dist/discovery-metadata.d.ts +6 -6
- package/dist/discovery-metadata.js +6 -0
- package/dist/discovery-support.js +2 -2
- package/dist/resources.d.ts +1 -0
- package/dist/resources.js +210 -148
- package/dist/server-validation.js +2 -0
- package/dist/server.js +87 -1
- package/dist/tools/eval.js +29 -5
- package/dist/tools/execution-policy.d.ts +11 -0
- package/dist/tools/execution-policy.js +47 -0
- package/dist/tools/run-dossier.js +2 -2
- package/dist/tools/run-loop.d.ts +1 -0
- package/dist/tools/run-loop.js +15 -2
- package/dist/tools/tool-support.d.ts +3 -1
- package/dist/tools/tool-support.js +127 -19
- package/dist/tools/triage-runs.js +31 -6
- package/dist/vendor/adapters/claude-cli.js +100 -21
- package/dist/vendor/adapters/cli-bridge.d.ts +16 -0
- package/dist/vendor/adapters/cli-bridge.js +92 -2
- package/dist/vendor/adapters/codex-launcher.d.ts +2 -0
- package/dist/vendor/adapters/codex-launcher.js +124 -10
- package/dist/vendor/adapters/openai-compatible.js +6 -6
- package/dist/vendor/contracts/adapter-capabilities.d.ts +17 -0
- package/dist/vendor/contracts/adapter-capabilities.js +35 -0
- package/dist/vendor/contracts/context-graph.d.ts +48 -0
- package/dist/vendor/contracts/context-graph.js +26 -0
- package/dist/vendor/contracts/execution-policy.d.ts +78 -0
- package/dist/vendor/contracts/execution-policy.js +43 -0
- package/dist/vendor/contracts/governance.d.ts +6 -3
- package/dist/vendor/contracts/identity.d.ts +29 -0
- package/dist/vendor/contracts/identity.js +30 -0
- package/dist/vendor/contracts/index.d.ts +40 -2
- package/dist/vendor/contracts/index.js +5 -0
- package/dist/vendor/contracts/observation.d.ts +21 -0
- package/dist/vendor/contracts/observation.js +27 -0
- package/dist/vendor/contracts/trajectory.d.ts +26 -0
- package/dist/vendor/contracts/trajectory.js +27 -0
- package/dist/vendor/contracts/verification.d.ts +28 -0
- package/dist/vendor/contracts/verification.js +7 -0
- package/dist/vendor/core/context-graph.d.ts +3 -0
- package/dist/vendor/core/context-graph.js +371 -0
- package/dist/vendor/core/identity.d.ts +20 -0
- package/dist/vendor/core/identity.js +86 -0
- package/dist/vendor/core/index.d.ts +10 -1
- package/dist/vendor/core/index.js +131 -10
- package/dist/vendor/core/persistence/integrity.js +2 -2
- package/dist/vendor/core/policy-compiler.d.ts +2 -0
- package/dist/vendor/core/policy-compiler.js +195 -0
- package/dist/vendor/core/policy.d.ts +15 -0
- package/dist/vendor/core/policy.js +84 -4
- package/dist/vendor/core/rollback.js +32 -12
- package/dist/vendor/core/routing.d.ts +40 -0
- package/dist/vendor/core/routing.js +185 -0
- package/dist/vendor/core/trajectory.d.ts +18 -0
- package/dist/vendor/core/trajectory.js +295 -0
- package/dist/workflow-state.d.ts +16 -0
- package/dist/workflow-state.js +1 -1
- package/package.json +76 -75
- package/server.json +21 -21
|
@@ -137,8 +137,30 @@ export async function runVerification(commands, cwd, timeoutMs, verificationStac
|
|
|
137
137
|
const stepOutcomes = [];
|
|
138
138
|
const warnings = [];
|
|
139
139
|
for (const step of steps) {
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
let bin;
|
|
141
|
+
let args;
|
|
142
|
+
if (containsShellOperator(step.command)) {
|
|
143
|
+
// Shell operators (&&, ||, ;, |) cannot be passed as literal arguments
|
|
144
|
+
// to spawn(). Route through the platform shell so the operator is
|
|
145
|
+
// interpreted correctly.
|
|
146
|
+
if (process.platform === "win32") {
|
|
147
|
+
bin = process.env.ComSpec || "cmd.exe";
|
|
148
|
+
args = ["/d", "/c", step.command];
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
bin = "sh";
|
|
152
|
+
args = ["-c", step.command];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const parts = splitCommand(step.command);
|
|
157
|
+
const first = parts[0];
|
|
158
|
+
if (!first) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
bin = first;
|
|
162
|
+
args = parts.slice(1);
|
|
163
|
+
}
|
|
142
164
|
if (!bin) {
|
|
143
165
|
continue;
|
|
144
166
|
}
|
|
@@ -214,6 +236,36 @@ export async function readGitChangedFiles(repoRoot, timeoutMs, spawnImpl) {
|
|
|
214
236
|
}
|
|
215
237
|
return parsePorcelainEntries(statusResult.stdout).filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
216
238
|
}
|
|
239
|
+
// Ignore paths that are not meaningful code changes for first-delta detection
|
|
240
|
+
const FIRST_DELTA_IGNORE = [
|
|
241
|
+
/^\.martin\//u,
|
|
242
|
+
/^PROGRESS\.md$/u,
|
|
243
|
+
/\.lock$/u,
|
|
244
|
+
/^node_modules\//u,
|
|
245
|
+
/^\.git\//u,
|
|
246
|
+
/^\.cache\//u,
|
|
247
|
+
];
|
|
248
|
+
/**
|
|
249
|
+
* Detects whether a meaningful workspace delta has occurred by checking
|
|
250
|
+
* git status for changed files that aren't MartinLoop metadata, lockfiles,
|
|
251
|
+
* or cache artifacts. Returns the first meaningful changed file if found.
|
|
252
|
+
*/
|
|
253
|
+
export async function detectFirstDelta(repoRoot, timeoutMs, spawnImpl) {
|
|
254
|
+
const changedFiles = await readGitChangedFiles(repoRoot, timeoutMs, spawnImpl);
|
|
255
|
+
for (const file of changedFiles) {
|
|
256
|
+
if (FIRST_DELTA_IGNORE.some((pattern) => pattern.test(file))) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
// Classify: new file = create, deleted = delete, else modify
|
|
260
|
+
const diffResult = await runSubprocess("git", ["status", "--porcelain", "--", file], { cwd: repoRoot, timeoutMs: 3000, spawnImpl });
|
|
261
|
+
const status = diffResult.stdout.trim().slice(0, 2);
|
|
262
|
+
const changeType = status.includes("?") ? "create"
|
|
263
|
+
: status.includes("D") ? "delete"
|
|
264
|
+
: "modify";
|
|
265
|
+
return { detected: true, filePath: file, changeType };
|
|
266
|
+
}
|
|
267
|
+
return { detected: false };
|
|
268
|
+
}
|
|
217
269
|
export function resolveGitRepositoryRoot(workingDirectory) {
|
|
218
270
|
const resolvedWorkingDirectory = resolve(workingDirectory);
|
|
219
271
|
const cached = gitRepositoryRootCache.get(resolvedWorkingDirectory);
|
|
@@ -342,6 +394,44 @@ function windowsPathDirectories() {
|
|
|
342
394
|
.map((entry) => entry.trim().replace(/^"|"$/g, ""))
|
|
343
395
|
.filter(Boolean);
|
|
344
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* Returns true if the command string contains shell operators that cannot be
|
|
399
|
+
* passed as literal arguments to spawn(). These must be routed through a
|
|
400
|
+
* platform shell (cmd.exe /c or sh -c) so the operator is interpreted.
|
|
401
|
+
*/
|
|
402
|
+
export function containsShellOperator(command) {
|
|
403
|
+
// Match &&, ||, ;, or | that are NOT inside quotes.
|
|
404
|
+
// Simple heuristic: scan outside of single/double quoted regions.
|
|
405
|
+
let inSingle = false;
|
|
406
|
+
let inDouble = false;
|
|
407
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
408
|
+
const ch = command[i];
|
|
409
|
+
if (ch === "'" && !inDouble) {
|
|
410
|
+
inSingle = !inSingle;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (ch === '"' && !inSingle) {
|
|
414
|
+
inDouble = !inDouble;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (inSingle || inDouble) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (ch === "&" && command[i + 1] === "&") {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
if (ch === "|" && command[i + 1] === "|") {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
if (ch === ";") {
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
if (ch === "|" && command[i + 1] !== "|") {
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
345
435
|
export function splitCommand(command) {
|
|
346
436
|
const tokens = [];
|
|
347
437
|
let current = "";
|
|
@@ -43,6 +43,7 @@ export interface CodexExecArgsOptions {
|
|
|
43
43
|
mode?: "prompt" | "probe";
|
|
44
44
|
}
|
|
45
45
|
type SpawnSyncLike = typeof spawnSync;
|
|
46
|
+
export declare const DEFAULT_CODEX_CHATGPT_MODEL = "gpt-5.4";
|
|
46
47
|
export interface CodexProbeCandidateResult {
|
|
47
48
|
path: string;
|
|
48
49
|
installKind: CodexInstallKind;
|
|
@@ -72,5 +73,6 @@ export declare function probeCodexLaunch(input: {
|
|
|
72
73
|
env?: NodeJS.ProcessEnv;
|
|
73
74
|
platform?: NodeJS.Platform;
|
|
74
75
|
spawnSyncImpl?: SpawnSyncLike;
|
|
76
|
+
model?: string;
|
|
75
77
|
}): CodexLaunchProbeResult;
|
|
76
78
|
export {};
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
const codexLaunchProbeCache = new Map();
|
|
5
|
+
export const DEFAULT_CODEX_CHATGPT_MODEL = "gpt-5.4";
|
|
5
6
|
const CODEX_LAUNCH_PROBE_PROMPT = [
|
|
6
7
|
"You are validating MartinLoop Codex host readiness.",
|
|
7
8
|
"Do not edit files.",
|
|
@@ -14,7 +15,8 @@ function buildProbeCacheKey(input) {
|
|
|
14
15
|
return JSON.stringify({
|
|
15
16
|
workingDirectory: resolve(input.workingDirectory),
|
|
16
17
|
platform: input.platform,
|
|
17
|
-
candidatePaths: input.candidatePaths
|
|
18
|
+
candidatePaths: input.candidatePaths,
|
|
19
|
+
model: input.model ?? DEFAULT_CODEX_CHATGPT_MODEL
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
function isInsideGitRepository(workingDirectory) {
|
|
@@ -167,6 +169,39 @@ function detectInvocationMode(resolvedPath, platform) {
|
|
|
167
169
|
}
|
|
168
170
|
function classifyProbeFailure(stderr, stdout, diagnosis) {
|
|
169
171
|
const combined = `${stderr}\n${stdout}`;
|
|
172
|
+
if (/MARTIN_REMOTE_TOKEN/iu.test(combined) ||
|
|
173
|
+
/AuthRequired\(AuthRequiredError/iu.test(combined) ||
|
|
174
|
+
/www_authenticate_header/iu.test(combined)) {
|
|
175
|
+
const warnings = [...diagnosis.warnings];
|
|
176
|
+
const configCollisionWarning = "Codex inherited auth-sensitive MCP or plugin state from the operator's default user config.";
|
|
177
|
+
if (!warnings.includes(configCollisionWarning)) {
|
|
178
|
+
warnings.push(configCollisionWarning);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
summary: "Codex inherited auth-sensitive MCP or plugin state from the operator's default user config. Governed Codex runs should launch with `exec --ignore-user-config` so missing remote tokens or third-party auth do not abort MartinLoop work.",
|
|
182
|
+
diagnosis: {
|
|
183
|
+
...diagnosis,
|
|
184
|
+
warnings,
|
|
185
|
+
remediation: "Retry the governed Codex launch with `exec --ignore-user-config`, or remove auth-sensitive MCP/plugin dependencies from the default Codex user config before running MartinLoop."
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (/not supported when using Codex with a ChatGPT account/iu.test(combined) ||
|
|
190
|
+
/gpt-5\.3-codex/iu.test(combined)) {
|
|
191
|
+
const warnings = [...diagnosis.warnings];
|
|
192
|
+
const unsupportedModelWarning = "Codex tried to use a model that is not supported for ChatGPT-account authentication.";
|
|
193
|
+
if (!warnings.includes(unsupportedModelWarning)) {
|
|
194
|
+
warnings.push(unsupportedModelWarning);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
summary: `Codex launched with a model that is not supported for ChatGPT-account authentication. Use an explicit supported model such as \`${DEFAULT_CODEX_CHATGPT_MODEL}\` for governed Codex work.`,
|
|
198
|
+
diagnosis: {
|
|
199
|
+
...diagnosis,
|
|
200
|
+
warnings,
|
|
201
|
+
remediation: `Override the Codex launch model to a ChatGPT-account-supported option such as \`${DEFAULT_CODEX_CHATGPT_MODEL}\` before running governed Codex work.`
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
170
205
|
if (/@openai\/codex-linux-x64/iu.test(combined) ||
|
|
171
206
|
/cannot find module ['"]@openai\/codex-linux-x64['"]/iu.test(combined)) {
|
|
172
207
|
const warnings = [...diagnosis.warnings];
|
|
@@ -205,6 +240,24 @@ function classifyProbeFailure(stderr, stdout, diagnosis) {
|
|
|
205
240
|
}
|
|
206
241
|
};
|
|
207
242
|
}
|
|
243
|
+
if (/writing is blocked by read-only sandbox/iu.test(combined) ||
|
|
244
|
+
/read-only filesystem sandbox/iu.test(combined) ||
|
|
245
|
+
/approval is disabled/iu.test(combined)) {
|
|
246
|
+
const warnings = [...diagnosis.warnings];
|
|
247
|
+
const readOnlySandboxWarning = "Codex stayed in a read-only or approval-disabled sandbox even though MartinLoop requested workspace-write.";
|
|
248
|
+
if (!warnings.includes(readOnlySandboxWarning)) {
|
|
249
|
+
warnings.push(readOnlySandboxWarning);
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
summary: "Codex stayed in a read-only or approval-disabled sandbox even though MartinLoop requested workspace-write.",
|
|
253
|
+
diagnosis: {
|
|
254
|
+
...diagnosis,
|
|
255
|
+
sandboxCompatible: false,
|
|
256
|
+
warnings,
|
|
257
|
+
remediation: "Launch governed Codex runs with `codex exec --ignore-user-config --sandbox workspace-write`. If the session still reports a read-only sandbox, treat it as a host/runtime mismatch and repair or relocate the affected Codex workspace before resuming governed work."
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
208
261
|
return {};
|
|
209
262
|
}
|
|
210
263
|
export function buildCodexExecArgs(options) {
|
|
@@ -213,6 +266,8 @@ export function buildCodexExecArgs(options) {
|
|
|
213
266
|
const extraArgs = options.extraArgs ?? [];
|
|
214
267
|
return [
|
|
215
268
|
"exec",
|
|
269
|
+
// Governed MartinLoop runs should not inherit auth-sensitive default MCP/plugin state.
|
|
270
|
+
"--ignore-user-config",
|
|
216
271
|
"--cd",
|
|
217
272
|
options.workingDirectory,
|
|
218
273
|
"--sandbox",
|
|
@@ -248,24 +303,81 @@ function hasSuccessfulCommandExecution(events) {
|
|
|
248
303
|
}
|
|
249
304
|
export function resolveCliCommandAvailability(command, options = {}) {
|
|
250
305
|
const platform = options.platform ?? process.platform;
|
|
306
|
+
const env = options.env ?? process.env;
|
|
251
307
|
const spawnSyncImpl = options.spawnSyncImpl ?? spawnSync;
|
|
252
308
|
const discovery = readLocatorCandidates(command, platform, spawnSyncImpl);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
309
|
+
if (discovery.candidates.length > 0) {
|
|
310
|
+
const resolvedPath = discovery.candidates[0];
|
|
311
|
+
return {
|
|
256
312
|
command,
|
|
257
313
|
available: true,
|
|
258
314
|
locator: discovery.locator,
|
|
259
315
|
detail: `${command} is available on PATH.`,
|
|
260
316
|
...(resolvedPath ? { resolvedPath } : {}),
|
|
261
317
|
candidatePaths: discovery.candidates
|
|
262
|
-
}
|
|
263
|
-
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
// PATH didn't find it — search common install locations before giving up.
|
|
321
|
+
const offPathCandidate = discoverCommandOffPath(command, platform, env);
|
|
322
|
+
if (offPathCandidate) {
|
|
323
|
+
return {
|
|
264
324
|
command,
|
|
265
|
-
available:
|
|
266
|
-
locator: discovery
|
|
267
|
-
detail: `${command}
|
|
325
|
+
available: true,
|
|
326
|
+
locator: "off-path-discovery",
|
|
327
|
+
detail: `${command} found at ${offPathCandidate} (not on PATH, auto-discovered).`,
|
|
328
|
+
resolvedPath: offPathCandidate,
|
|
329
|
+
candidatePaths: [offPathCandidate]
|
|
268
330
|
};
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
command,
|
|
334
|
+
available: false,
|
|
335
|
+
locator: discovery.locator,
|
|
336
|
+
detail: `${command} is not installed. ${suggestInstall(command)}`
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
function discoverCommandOffPath(command, platform, env) {
|
|
340
|
+
const home = env.HOME ?? env.USERPROFILE ?? "";
|
|
341
|
+
const dirs = [];
|
|
342
|
+
if (platform === "win32") {
|
|
343
|
+
const appData = env.APPDATA;
|
|
344
|
+
if (appData)
|
|
345
|
+
dirs.push(join(appData, "npm"));
|
|
346
|
+
const localAppData = env.LOCALAPPDATA;
|
|
347
|
+
if (localAppData)
|
|
348
|
+
dirs.push(join(localAppData, "OpenAI", "Codex", "bin"));
|
|
349
|
+
if (home)
|
|
350
|
+
dirs.push(join(home, "scoop", "shims"));
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
dirs.push("/usr/local/bin", "/opt/homebrew/bin");
|
|
354
|
+
if (home) {
|
|
355
|
+
dirs.push(join(home, ".local", "bin"), join(home, ".npm-global", "bin"), join(home, ".bun", "bin"), join(home, ".cargo", "bin"));
|
|
356
|
+
}
|
|
357
|
+
const nvmDir = env.NVM_DIR;
|
|
358
|
+
if (nvmDir)
|
|
359
|
+
dirs.push(join(nvmDir, "current", "bin"));
|
|
360
|
+
}
|
|
361
|
+
const extensions = platform === "win32"
|
|
362
|
+
? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim().toLowerCase()).filter(Boolean)
|
|
363
|
+
: [""];
|
|
364
|
+
for (const dir of dirs) {
|
|
365
|
+
for (const ext of extensions) {
|
|
366
|
+
const candidate = ext ? join(dir, `${command}${ext}`) : join(dir, command);
|
|
367
|
+
if (existsSync(candidate)) {
|
|
368
|
+
return candidate;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
function suggestInstall(command) {
|
|
375
|
+
const installs = {
|
|
376
|
+
claude: "Install with: npm install -g @anthropic-ai/claude-code",
|
|
377
|
+
codex: "Install with: npm install -g @openai/codex",
|
|
378
|
+
gemini: "Install with: npm install -g @google/gemini-cli"
|
|
379
|
+
};
|
|
380
|
+
return installs[command] ?? `Install ${command} and ensure it is available.`;
|
|
269
381
|
}
|
|
270
382
|
export function detectCodexHostPlatform(env = process.env, platform = process.platform) {
|
|
271
383
|
if (platform === "win32") {
|
|
@@ -339,6 +451,7 @@ export function probeCodexLaunch(input) {
|
|
|
339
451
|
});
|
|
340
452
|
const args = buildCodexExecArgs({
|
|
341
453
|
workingDirectory: input.workingDirectory,
|
|
454
|
+
model: input.model ?? DEFAULT_CODEX_CHATGPT_MODEL,
|
|
342
455
|
mode: "probe"
|
|
343
456
|
});
|
|
344
457
|
if (!availability.available) {
|
|
@@ -374,7 +487,8 @@ export function probeCodexLaunch(input) {
|
|
|
374
487
|
const cacheKey = buildProbeCacheKey({
|
|
375
488
|
workingDirectory: input.workingDirectory,
|
|
376
489
|
platform,
|
|
377
|
-
candidatePaths
|
|
490
|
+
candidatePaths,
|
|
491
|
+
model: input.model
|
|
378
492
|
});
|
|
379
493
|
if (input.spawnSyncImpl === undefined) {
|
|
380
494
|
const cached = codexLaunchProbeCache.get(cacheKey);
|
|
@@ -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";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type DiffVisibilityLevel = "none" | "git" | "adapter_reported";
|
|
2
|
+
export type VerifierCompatibility = "full" | "verify_only" | "unsupported";
|
|
3
|
+
export type SandboxExpectation = "host_process" | "workspace_write" | "provider_managed" | "not_applicable";
|
|
4
|
+
export type LaunchReadiness = "path_lookup" | "configured_endpoint" | "built_in";
|
|
5
|
+
export type AdapterUsageSettlement = "actual" | "estimated" | "unavailable";
|
|
6
|
+
export interface AdapterCapabilityDescriptor {
|
|
7
|
+
preflight: boolean;
|
|
8
|
+
usageSettlement: AdapterUsageSettlement;
|
|
9
|
+
diffVisibility: DiffVisibilityLevel;
|
|
10
|
+
structuredErrors: boolean;
|
|
11
|
+
cachingSignals: boolean;
|
|
12
|
+
verifierCompatibility: VerifierCompatibility;
|
|
13
|
+
sandboxExpectation: SandboxExpectation;
|
|
14
|
+
launchReadiness: LaunchReadiness;
|
|
15
|
+
}
|
|
16
|
+
export declare function createAdapterCapabilityDescriptor(overrides?: Partial<AdapterCapabilityDescriptor>): AdapterCapabilityDescriptor;
|
|
17
|
+
export declare function getBuiltInEngineCapabilityDescriptor(engine: "claude" | "codex"): AdapterCapabilityDescriptor;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export function createAdapterCapabilityDescriptor(overrides = {}) {
|
|
2
|
+
return {
|
|
3
|
+
preflight: true,
|
|
4
|
+
usageSettlement: "unavailable",
|
|
5
|
+
diffVisibility: "none",
|
|
6
|
+
structuredErrors: false,
|
|
7
|
+
cachingSignals: false,
|
|
8
|
+
verifierCompatibility: "full",
|
|
9
|
+
sandboxExpectation: "host_process",
|
|
10
|
+
launchReadiness: "path_lookup",
|
|
11
|
+
...overrides
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function getBuiltInEngineCapabilityDescriptor(engine) {
|
|
15
|
+
if (engine === "claude") {
|
|
16
|
+
return createAdapterCapabilityDescriptor({
|
|
17
|
+
usageSettlement: "actual",
|
|
18
|
+
diffVisibility: "git",
|
|
19
|
+
structuredErrors: true,
|
|
20
|
+
cachingSignals: true,
|
|
21
|
+
verifierCompatibility: "full",
|
|
22
|
+
sandboxExpectation: "host_process",
|
|
23
|
+
launchReadiness: "path_lookup"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return createAdapterCapabilityDescriptor({
|
|
27
|
+
usageSettlement: "estimated",
|
|
28
|
+
diffVisibility: "git",
|
|
29
|
+
structuredErrors: true,
|
|
30
|
+
cachingSignals: false,
|
|
31
|
+
verifierCompatibility: "full",
|
|
32
|
+
sandboxExpectation: "workspace_write",
|
|
33
|
+
launchReadiness: "path_lookup"
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type ContextGraphNodeKind = "source_file" | "document" | "manifest" | "script" | "config";
|
|
2
|
+
export type ContextGraphEdgeKind = "imports" | "documents";
|
|
3
|
+
export interface ContextGraphNode {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
path: string;
|
|
6
|
+
kind: ContextGraphNodeKind;
|
|
7
|
+
language?: string;
|
|
8
|
+
sizeBytes: number;
|
|
9
|
+
headings: string[];
|
|
10
|
+
symbols: string[];
|
|
11
|
+
keywords: string[];
|
|
12
|
+
imports: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ContextGraphEdge {
|
|
15
|
+
fromNodeId: string;
|
|
16
|
+
toNodeId: string;
|
|
17
|
+
kind: ContextGraphEdgeKind;
|
|
18
|
+
}
|
|
19
|
+
export interface ContextGraphSnapshot {
|
|
20
|
+
schemaVersion: "martin.context-graph.v1";
|
|
21
|
+
repoRoot: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
nodeCount: number;
|
|
24
|
+
edgeCount: number;
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
nodes: ContextGraphNode[];
|
|
27
|
+
edges: ContextGraphEdge[];
|
|
28
|
+
}
|
|
29
|
+
export interface ContextQuery {
|
|
30
|
+
text?: string;
|
|
31
|
+
terms?: string[];
|
|
32
|
+
pathPrefix?: string;
|
|
33
|
+
kinds?: ContextGraphNodeKind[];
|
|
34
|
+
limit?: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ContextGraphBuildOptions {
|
|
37
|
+
maxFiles?: number;
|
|
38
|
+
maxFileBytes?: number;
|
|
39
|
+
}
|
|
40
|
+
export interface ContextGraphHit {
|
|
41
|
+
nodeId: string;
|
|
42
|
+
path: string;
|
|
43
|
+
kind: ContextGraphNodeKind;
|
|
44
|
+
score: number;
|
|
45
|
+
matchedTerms: string[];
|
|
46
|
+
reasons: string[];
|
|
47
|
+
}
|
|
48
|
+
export declare function cloneContextGraphSnapshot(snapshot: ContextGraphSnapshot): ContextGraphSnapshot;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function cloneContextGraphSnapshot(snapshot) {
|
|
2
|
+
return {
|
|
3
|
+
schemaVersion: snapshot.schemaVersion,
|
|
4
|
+
repoRoot: snapshot.repoRoot,
|
|
5
|
+
createdAt: snapshot.createdAt,
|
|
6
|
+
nodeCount: snapshot.nodeCount,
|
|
7
|
+
edgeCount: snapshot.edgeCount,
|
|
8
|
+
truncated: snapshot.truncated,
|
|
9
|
+
nodes: snapshot.nodes.map((node) => ({
|
|
10
|
+
nodeId: node.nodeId,
|
|
11
|
+
path: node.path,
|
|
12
|
+
kind: node.kind,
|
|
13
|
+
...(node.language ? { language: node.language } : {}),
|
|
14
|
+
sizeBytes: node.sizeBytes,
|
|
15
|
+
headings: [...node.headings],
|
|
16
|
+
symbols: [...node.symbols],
|
|
17
|
+
keywords: [...node.keywords],
|
|
18
|
+
imports: [...node.imports]
|
|
19
|
+
})),
|
|
20
|
+
edges: snapshot.edges.map((edge) => ({
|
|
21
|
+
fromNodeId: edge.fromNodeId,
|
|
22
|
+
toNodeId: edge.toNodeId,
|
|
23
|
+
kind: edge.kind
|
|
24
|
+
}))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ApprovalPolicy, LoopBudget, LoopTask, MutationMode } from "./index.js";
|
|
2
|
+
import type { DestructiveActionPolicy, PolicyProfile, TelemetryDestination } from "./governance.js";
|
|
3
|
+
export interface ExecutionPolicy {
|
|
4
|
+
configPath: string;
|
|
5
|
+
budget: LoopBudget;
|
|
6
|
+
governance: {
|
|
7
|
+
policyProfile: PolicyProfile;
|
|
8
|
+
telemetryDestination: TelemetryDestination;
|
|
9
|
+
destructiveActionPolicy: DestructiveActionPolicy;
|
|
10
|
+
};
|
|
11
|
+
task: {
|
|
12
|
+
verificationPlan: string[];
|
|
13
|
+
mutationMode?: MutationMode;
|
|
14
|
+
repoRoot?: string;
|
|
15
|
+
allowedPaths?: string[];
|
|
16
|
+
deniedPaths?: string[];
|
|
17
|
+
acceptanceCriteria?: string[];
|
|
18
|
+
approvalPolicy?: ApprovalPolicy;
|
|
19
|
+
};
|
|
20
|
+
provenance: ExecutionPolicyProvenanceEntry[];
|
|
21
|
+
}
|
|
22
|
+
export interface ExecutionPolicyProvenanceEntry {
|
|
23
|
+
field: string;
|
|
24
|
+
source: "default" | "config" | "request";
|
|
25
|
+
value: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ExecutionPolicyDefaults {
|
|
28
|
+
budget: LoopBudget;
|
|
29
|
+
policyProfile: PolicyProfile;
|
|
30
|
+
telemetryDestination: TelemetryDestination;
|
|
31
|
+
destructiveActionPolicy: DestructiveActionPolicy;
|
|
32
|
+
verifierRules: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface ExecutionPolicyConfigInput {
|
|
35
|
+
policyProfile?: string;
|
|
36
|
+
budget?: Partial<LoopBudget>;
|
|
37
|
+
governance?: {
|
|
38
|
+
destructiveActionPolicy?: string;
|
|
39
|
+
telemetryDestination?: string;
|
|
40
|
+
verifierRules?: string[];
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface ExecutionPolicyRequestInput {
|
|
44
|
+
budget: LoopBudget;
|
|
45
|
+
budgetOverrides?: Partial<Record<keyof LoopBudget, true>>;
|
|
46
|
+
policyProfile?: string;
|
|
47
|
+
telemetryDestination?: string;
|
|
48
|
+
verificationPlan?: string[];
|
|
49
|
+
mutationMode?: LoopTask["mutationMode"];
|
|
50
|
+
repoRoot?: string;
|
|
51
|
+
allowedPaths?: string[];
|
|
52
|
+
deniedPaths?: string[];
|
|
53
|
+
acceptanceCriteria?: string[];
|
|
54
|
+
approvalPolicy?: ApprovalPolicy;
|
|
55
|
+
}
|
|
56
|
+
export interface ExecutionPolicyCompileInput {
|
|
57
|
+
configPath: string;
|
|
58
|
+
defaults: ExecutionPolicyDefaults;
|
|
59
|
+
config?: ExecutionPolicyConfigInput;
|
|
60
|
+
request: ExecutionPolicyRequestInput;
|
|
61
|
+
}
|
|
62
|
+
export type RoutingMode = "direct" | "manager" | "adaptive" | "consensus";
|
|
63
|
+
export interface RoutingPolicy {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
mode: RoutingMode;
|
|
66
|
+
/** Max percentage of total budget that can be spent before first code change. */
|
|
67
|
+
maxPreworkBudgetPct: number;
|
|
68
|
+
/** Hard dollar cap on pre-work spend. */
|
|
69
|
+
maxPreworkCostUsd: number;
|
|
70
|
+
/** Max model calls allowed for manager/router/planner before worker starts. */
|
|
71
|
+
maxManagerCalls: number;
|
|
72
|
+
/** Skip orchestration entirely if task confidence is above this threshold (0-1). */
|
|
73
|
+
skipOrchestrationIfConfidenceAbove: number;
|
|
74
|
+
/** Force direct execution for tasks classified as simple. */
|
|
75
|
+
requireDirectForSimpleTasks: boolean;
|
|
76
|
+
}
|
|
77
|
+
export declare const DEFAULT_ROUTING_POLICY: RoutingPolicy;
|
|
78
|
+
export declare function cloneExecutionPolicy(policy: ExecutionPolicy): ExecutionPolicy;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const DEFAULT_ROUTING_POLICY = {
|
|
2
|
+
enabled: true,
|
|
3
|
+
mode: "adaptive",
|
|
4
|
+
maxPreworkBudgetPct: 25,
|
|
5
|
+
maxPreworkCostUsd: 2.0,
|
|
6
|
+
maxManagerCalls: 2,
|
|
7
|
+
skipOrchestrationIfConfidenceAbove: 0.85,
|
|
8
|
+
requireDirectForSimpleTasks: true
|
|
9
|
+
};
|
|
10
|
+
export function cloneExecutionPolicy(policy) {
|
|
11
|
+
return {
|
|
12
|
+
configPath: policy.configPath,
|
|
13
|
+
budget: {
|
|
14
|
+
maxUsd: policy.budget.maxUsd,
|
|
15
|
+
softLimitUsd: policy.budget.softLimitUsd,
|
|
16
|
+
maxIterations: policy.budget.maxIterations,
|
|
17
|
+
maxTokens: policy.budget.maxTokens
|
|
18
|
+
},
|
|
19
|
+
governance: {
|
|
20
|
+
policyProfile: policy.governance.policyProfile,
|
|
21
|
+
telemetryDestination: policy.governance.telemetryDestination,
|
|
22
|
+
destructiveActionPolicy: policy.governance.destructiveActionPolicy
|
|
23
|
+
},
|
|
24
|
+
task: {
|
|
25
|
+
verificationPlan: [...policy.task.verificationPlan],
|
|
26
|
+
...(policy.task.mutationMode ? { mutationMode: policy.task.mutationMode } : {}),
|
|
27
|
+
...(policy.task.repoRoot ? { repoRoot: policy.task.repoRoot } : {}),
|
|
28
|
+
...(policy.task.allowedPaths ? { allowedPaths: [...policy.task.allowedPaths] } : {}),
|
|
29
|
+
...(policy.task.deniedPaths ? { deniedPaths: [...policy.task.deniedPaths] } : {}),
|
|
30
|
+
...(policy.task.acceptanceCriteria
|
|
31
|
+
? { acceptanceCriteria: [...policy.task.acceptanceCriteria] }
|
|
32
|
+
: {}),
|
|
33
|
+
...(policy.task.approvalPolicy
|
|
34
|
+
? {
|
|
35
|
+
approvalPolicy: {
|
|
36
|
+
...policy.task.approvalPolicy
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
: {})
|
|
40
|
+
},
|
|
41
|
+
provenance: policy.provenance.map((entry) => ({ ...entry }))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
export type PolicyProfile = "strict" | "balanced" | "overnight" | "debug";
|
|
2
|
+
export type DestructiveActionPolicy = "never" | "approval" | "allowed";
|
|
3
|
+
export type TelemetryDestination = "local-only" | "control-plane";
|
|
1
4
|
export type GovernanceSnapshot = {
|
|
2
|
-
policyProfile:
|
|
5
|
+
policyProfile: PolicyProfile;
|
|
3
6
|
maxUsd: number;
|
|
4
7
|
softLimitUsd: number;
|
|
5
8
|
maxTokens: number;
|
|
6
9
|
maxIterations: number;
|
|
7
10
|
allowedAdapters: string[];
|
|
8
11
|
allowedModels: string[];
|
|
9
|
-
destructiveActionPolicy:
|
|
12
|
+
destructiveActionPolicy: DestructiveActionPolicy;
|
|
10
13
|
approvalRequired: boolean;
|
|
11
14
|
verifierRules: string[];
|
|
12
15
|
escalationRoute: string;
|
|
13
|
-
telemetryDestination:
|
|
16
|
+
telemetryDestination: TelemetryDestination;
|
|
14
17
|
retentionPolicy: string;
|
|
15
18
|
provenance: Array<{
|
|
16
19
|
field: string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type IdentityAlgorithm = "ed25519";
|
|
2
|
+
export type IdentityRole = "governor" | "verifier" | "worker" | "reviewer" | "observer";
|
|
3
|
+
export interface IdentityClaims {
|
|
4
|
+
subject: string;
|
|
5
|
+
issuer: string;
|
|
6
|
+
role: IdentityRole;
|
|
7
|
+
scopes: string[];
|
|
8
|
+
issuedAt: string;
|
|
9
|
+
expiresAt?: string;
|
|
10
|
+
workspaceId?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
metadata?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export interface IdentityToken {
|
|
15
|
+
schemaVersion: "martin.identity.v1";
|
|
16
|
+
algorithm: IdentityAlgorithm;
|
|
17
|
+
keyId: string;
|
|
18
|
+
claims: IdentityClaims;
|
|
19
|
+
signature: string;
|
|
20
|
+
}
|
|
21
|
+
export interface IdentityAttestation {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
verifiedAt: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
token: IdentityToken;
|
|
26
|
+
}
|
|
27
|
+
export declare function cloneIdentityClaims(claims: IdentityClaims): IdentityClaims;
|
|
28
|
+
export declare function cloneIdentityToken(token: IdentityToken): IdentityToken;
|
|
29
|
+
export declare function cloneIdentityAttestation(attestation: IdentityAttestation): IdentityAttestation;
|