@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
@@ -522,10 +522,14 @@ export function createAgentCliAdapter(options) {
522
522
  };
523
523
  }
524
524
  if (agentResult.exitCode !== 0 && agentResult.stdout.trim().length === 0) {
525
+ const fullStderr = agentResult.stderr.trim();
526
+ const stderrSnippet = fullStderr ? fullStderr.slice(0, 2000) : "(no stderr)";
527
+ const stdoutLen = agentResult.stdout.length;
528
+ const diagnosticSummary = `${options.command} exited (code ${String(agentResult.exitCode)}) with empty stdout (${String(stdoutLen)} bytes). stderr: ${stderrSnippet}`;
525
529
  const failureMessage = formatPreVerifierSubprocessFailure(options.command, agentResult.stderr, agentResult.exitCode);
526
530
  return {
527
531
  status: "failed",
528
- summary: `${options.command} subprocess exited before verifier execution.`,
532
+ summary: diagnosticSummary,
529
533
  usage: normalizeUsage({
530
534
  actualUsd: 0,
531
535
  tokensIn: 0,
@@ -534,6 +538,9 @@ export function createAgentCliAdapter(options) {
534
538
  }),
535
539
  verification: { passed: false, summary: `Verifier not run: ${failureMessage}` },
536
540
  failure: {
541
+ // Raw stderr is preserved in diagnosticSummary above; use the
542
+ // normalized/classified message here so downstream consumers and
543
+ // tests get a consistent, parseable failure signal.
537
544
  message: failureMessage
538
545
  }
539
546
  };
@@ -796,11 +803,15 @@ export function createClaudeCliAdapter(options = {}) {
796
803
  "--verbose",
797
804
  "--print",
798
805
  "--dangerously-skip-permissions",
799
- // Prevent the child Claude process from loading the parent's MCP servers.
800
- // Without this, a MartinLoop MCP server in the user's config gets spawned
801
- // inside the governed subprocess, causing conflicts and "MCP server being
802
- // overwritten" errors.
803
- "--strict-mcp-config",
806
+ // Subprocess isolation strategy:
807
+ // --bare: skips hooks (prevents SessionEnd/hook failures causing non-zero exits),
808
+ // MCP server loading, LSP, CLAUDE.md discovery, and background prefetches.
809
+ // Requires ANTHROPIC_API_KEY (OAuth/keychain auth not available in bare mode).
810
+ // --strict-mcp-config: fallback when ANTHROPIC_API_KEY is not set — still
811
+ // prevents parent MCP servers from being inherited by the subprocess.
812
+ ...(process.env["ANTHROPIC_API_KEY"] ? ["--bare"] : ["--strict-mcp-config"]),
813
+ // NOTE: --max-tokens does not exist in the claude CLI. Token cap enforcement
814
+ // is handled at the MartinLoop layer via streamingUsageCap, not via subprocess flags.
804
815
  ...modelArgs,
805
816
  ...extraArgs
806
817
  ],
@@ -900,21 +911,13 @@ function buildPrompt(request) {
900
911
  const lines = [];
901
912
  const mutationMode = request.context.mutationMode ?? "edit";
902
913
  lines.push("You are running in autonomous agentic mode.");
903
- if (mutationMode === "verify_only") {
904
- lines.push("DO NOT EDIT FILES. Run the verifier only and report whether it passes.");
905
- lines.push("Do not ask for confirmation. Do not ask clarifying questions.");
906
- }
907
- else {
908
- lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
909
- lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
910
- }
914
+ lines.push("MAKE ALL REQUIRED FILE EDITS NOW. Do not ask for confirmation. Do not ask clarifying questions.");
915
+ lines.push("Do not explain what you found without also making the changes. Edit the files and complete the task.");
911
916
  lines.push("");
912
917
  lines.push("If PROGRESS.md exists in your working directory, read it first for context from prior attempts.");
913
918
  lines.push("If it does not exist, proceed with the objective below.");
914
919
  lines.push("");
915
- lines.push(mutationMode === "verify_only"
916
- ? "Complete the following verification-only task without making file changes."
917
- : "Complete the following coding task. Make all necessary file changes.");
920
+ lines.push("Complete the following coding task. Make all necessary file changes.");
918
921
  lines.push("When you are done, the verification commands listed below must pass.");
919
922
  lines.push("");
920
923
  lines.push("OBJECTIVE:");
@@ -952,9 +955,7 @@ function buildPrompt(request) {
952
955
  lines.push(` Attempt ${String(attemptNumber)}`);
953
956
  lines.push(` Remaining budget: $${String(request.context.remainingBudgetUsd)} USD`);
954
957
  lines.push(` Remaining iterations: ${String(request.context.remainingIterations)}`);
955
- lines.push(mutationMode === "verify_only"
956
- ? " Do not modify files; only run verification."
957
- : " Do not expand scope beyond what is needed to pass verification.");
958
+ lines.push(" Do not expand scope beyond what is needed to pass verification.");
958
959
  lines.push("");
959
960
  if (request.previousAttempts.length > 0) {
960
961
  lines.push("PRIOR FAILED ATTEMPTS (learn from these — do not repeat the same mistakes):");
@@ -87,6 +87,13 @@ export interface SpawnPlan {
87
87
  args: string[];
88
88
  }
89
89
  export declare function createSpawnPlan(command: string, args: string[], cwd: string, preserveRawForInjectedSpawn: boolean): SpawnPlan;
90
+ /**
91
+ * npm generates Windows shims (.cmd/.ps1, occasionally .bat) that ultimately just exec
92
+ * `node <real-cli-script>.js <args>` relative to the shim's own directory. Parse the shim text to
93
+ * find that real script path and return it if it resolves to a file that actually exists on disk;
94
+ * otherwise return undefined so callers fall back to the existing wrapper-shell behavior unchanged.
95
+ */
96
+ export declare function resolveNpmShimScript(shimPath: string): string | undefined;
90
97
  /**
91
98
  * Returns true if the command string contains shell operators that cannot be
92
99
  * passed as literal arguments to spawn(). These must be routed through a
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { delimiter, dirname, extname, isAbsolute, join, resolve } from "node:path";
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { diffStatsFromNumstat } from "./runtime-support.js";
5
5
  const gitRepositoryRootCache = new Map();
6
6
  export async function runSubprocess(command, args, options) {
@@ -319,20 +319,106 @@ export function createSpawnPlan(command, args, cwd, preserveRawForInjectedSpawn)
319
319
  };
320
320
  }
321
321
  const extension = extname(resolvedOrUndefined).toLowerCase();
322
- if (extension === ".cmd" || extension === ".bat") {
322
+ if (extension === ".cmd" || extension === ".bat" || extension === ".ps1") {
323
+ // npm-installed CLIs resolve to a generated shim on Windows. Wrapping that shim through an
324
+ // extra cmd.exe/powershell.exe hop adds a process layer that can lose the OS-level
325
+ // workspace-write sandbox permission when the whole tree is already nested inside another
326
+ // restricted parent process (e.g. VS Code's extension host), even though the same shim works
327
+ // fine from a top-level PowerShell window. When we can statically resolve the shim's real
328
+ // wrapped `node <script>` target, invoke that directly instead — this removes the extra hop
329
+ // for every Windows launch, nested or not, with no behavior change when resolution fails.
330
+ const directScript = resolveNpmShimScript(resolvedOrUndefined);
331
+ if (directScript !== undefined) {
332
+ // Use the system node rather than process.execPath. When MartinLoop runs
333
+ // inside Claude Code desktop or VS Code, process.execPath is Electron's
334
+ // bundled Node — which has different module resolution and may fail to
335
+ // load native CLI scripts. Prefer an explicit `node` from PATH instead.
336
+ const systemNode = resolveSystemNode();
337
+ return { command: systemNode, args: [directScript, ...args] };
338
+ }
339
+ if (extension === ".ps1") {
340
+ return {
341
+ command: "powershell.exe",
342
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", resolvedOrUndefined, ...args]
343
+ };
344
+ }
323
345
  return {
324
346
  command: process.env.ComSpec || "cmd.exe",
325
347
  args: ["/d", "/c", resolvedOrUndefined, ...args]
326
348
  };
327
349
  }
328
- if (extension === ".ps1") {
329
- return {
330
- command: "powershell.exe",
331
- args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", resolvedOrUndefined, ...args]
332
- };
333
- }
334
350
  return { command: resolvedOrUndefined, args };
335
351
  }
352
+ /**
353
+ * npm generates Windows shims (.cmd/.ps1, occasionally .bat) that ultimately just exec
354
+ * `node <real-cli-script>.js <args>` relative to the shim's own directory. Parse the shim text to
355
+ * find that real script path and return it if it resolves to a file that actually exists on disk;
356
+ * otherwise return undefined so callers fall back to the existing wrapper-shell behavior unchanged.
357
+ */
358
+ export function resolveNpmShimScript(shimPath) {
359
+ let contents;
360
+ try {
361
+ contents = readFileSync(shimPath, "utf8");
362
+ }
363
+ catch {
364
+ return undefined;
365
+ }
366
+ const shimDir = dirname(shimPath);
367
+ const scriptPathPattern = /["']?(?:%~?dp0%?|\$basedir)[\\/]([^"'\s]+\.[cm]?js)["']?/gi;
368
+ const matches = contents.matchAll(scriptPathPattern);
369
+ for (const match of matches) {
370
+ const relativeScript = match[1];
371
+ if (!relativeScript) {
372
+ continue;
373
+ }
374
+ const segments = relativeScript.split(/[\\/]+/u).filter(Boolean);
375
+ const resolvedScript = resolve(shimDir, ...segments);
376
+ if (existsSync(resolvedScript)) {
377
+ return resolvedScript;
378
+ }
379
+ }
380
+ return undefined;
381
+ }
382
+ /**
383
+ * Resolve a reliable `node` executable for spawning CLI scripts on Windows.
384
+ *
385
+ * When MartinLoop runs inside Claude Code desktop, VS Code, or the Codex IDE,
386
+ * `process.execPath` points to the host application's bundled Electron Node —
387
+ * not the system Node. Electron's Node has different module resolution paths
388
+ * and may fail to load native npm CLI scripts that expect the system Node.
389
+ *
390
+ * Strategy:
391
+ * 1. Use MARTIN_NODE_PATH env var if explicitly set (escape hatch for CI)
392
+ * 2. Look for `node` / `node.exe` on PATH — the system install
393
+ * 3. Fall back to process.execPath (Electron Node) if nothing else found
394
+ */
395
+ function resolveSystemNode() {
396
+ // Explicit override — useful in CI or restricted environments
397
+ const envOverride = process.env.MARTIN_NODE_PATH?.trim();
398
+ if (envOverride && envOverride.length > 0 && existsSync(envOverride)) {
399
+ return envOverride;
400
+ }
401
+ // Search PATH for a real `node` executable, skipping Electron binaries.
402
+ // Electron's node path typically contains "electron" or "Claude" in it.
403
+ for (const dir of windowsPathDirectories()) {
404
+ for (const candidate of ["node.exe", "node.cmd", "node"]) {
405
+ const fullPath = join(dir, candidate);
406
+ if (existsSync(fullPath) && !isElectronNode(fullPath)) {
407
+ return fullPath;
408
+ }
409
+ }
410
+ }
411
+ // Last resort: use process.execPath even if it's Electron's node
412
+ return process.execPath;
413
+ }
414
+ function isElectronNode(nodePath) {
415
+ const lower = nodePath.toLowerCase();
416
+ return (lower.includes("electron") ||
417
+ lower.includes("claude") ||
418
+ lower.includes("vscode") ||
419
+ lower.includes("code.exe") ||
420
+ lower.includes("cursor"));
421
+ }
336
422
  function resolveWindowsCommand(command, cwd) {
337
423
  const hasPathSegment = command.includes("\\") || command.includes("/");
338
424
  const baseCandidates = expandWindowsCommandCandidates(hasPathSegment ? resolve(cwd, command) : command);
@@ -1,6 +1,7 @@
1
1
  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
+ import { resolveNpmShimScript } from "./cli-bridge.js";
4
5
  const codexLaunchProbeCache = new Map();
5
6
  export const DEFAULT_CODEX_CHATGPT_MODEL = "gpt-5.4";
6
7
  const CODEX_LAUNCH_PROBE_PROMPT = [
@@ -121,17 +122,27 @@ function buildProbeCommand(command, args, platform) {
121
122
  switch (extension) {
122
123
  case ".cmd":
123
124
  case ".bat":
125
+ case ".ps1": {
126
+ // Bypass the npm shim's cmd.exe/powershell.exe wrapper hop when we can statically resolve
127
+ // the real `node <script>` target it wraps — keeps the live probe's process-nesting depth
128
+ // consistent with the real run's invocation (see createSpawnPlan in cli-bridge.ts).
129
+ const directScript = resolveNpmShimScript(command);
130
+ if (directScript !== undefined) {
131
+ return { command: process.execPath, args: [directScript, ...args], invocationMode: "direct" };
132
+ }
133
+ if (extension === ".ps1") {
134
+ return {
135
+ command: "powershell.exe",
136
+ args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args],
137
+ invocationMode: "powershell"
138
+ };
139
+ }
124
140
  return {
125
141
  command: process.env.ComSpec || "cmd.exe",
126
142
  args: ["/d", "/c", command, ...args],
127
143
  invocationMode: "cmd_shell"
128
144
  };
129
- case ".ps1":
130
- return {
131
- command: "powershell.exe",
132
- args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", command, ...args],
133
- invocationMode: "powershell"
134
- };
145
+ }
135
146
  default:
136
147
  return { command, args, invocationMode: "direct" };
137
148
  }
@@ -236,7 +247,7 @@ function classifyProbeFailure(stderr, stdout, diagnosis) {
236
247
  ...diagnosis,
237
248
  sandboxCompatible: false,
238
249
  warnings,
239
- remediation: "Update or reinstall Codex on this Windows host until `codex exec --sandbox workspace-write` can launch a simple shell command before running governed Codex work."
250
+ remediation: "MartinLoop already invokes Codex's underlying binary directly when it can resolve the npm shim (bypassing the cmd.exe/PowerShell wrapper hop), so this failure persisted even at the shallowest invocation depth available. Update or reinstall Codex on this Windows host until `codex exec --sandbox workspace-write` can launch a simple shell command before running governed Codex work."
240
251
  }
241
252
  };
242
253
  }
@@ -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
+ }
@@ -0,0 +1,24 @@
1
+ export type PolicyProfile = "strict" | "balanced" | "overnight" | "debug";
2
+ export type DestructiveActionPolicy = "never" | "approval" | "allowed";
3
+ export type TelemetryDestination = "local-only" | "control-plane";
4
+ export type GovernanceSnapshot = {
5
+ policyProfile: PolicyProfile;
6
+ maxUsd: number;
7
+ softLimitUsd: number;
8
+ maxTokens: number;
9
+ maxIterations: number;
10
+ allowedAdapters: string[];
11
+ allowedModels: string[];
12
+ destructiveActionPolicy: DestructiveActionPolicy;
13
+ approvalRequired: boolean;
14
+ verifierRules: string[];
15
+ escalationRoute: string;
16
+ telemetryDestination: TelemetryDestination;
17
+ retentionPolicy: string;
18
+ provenance: Array<{
19
+ field: string;
20
+ value: string;
21
+ source: string;
22
+ }>;
23
+ };
24
+ export declare function createGovernanceSnapshot(snapshot: GovernanceSnapshot): GovernanceSnapshot;
@@ -0,0 +1,11 @@
1
+ export function createGovernanceSnapshot(snapshot) {
2
+ return {
3
+ ...snapshot,
4
+ allowedAdapters: [...snapshot.allowedAdapters],
5
+ allowedModels: [...snapshot.allowedModels],
6
+ verifierRules: [...snapshot.verifierRules],
7
+ provenance: snapshot.provenance.map((entry) => ({
8
+ ...entry
9
+ }))
10
+ };
11
+ }
@@ -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;
@@ -0,0 +1,30 @@
1
+ export function cloneIdentityClaims(claims) {
2
+ return {
3
+ subject: claims.subject,
4
+ issuer: claims.issuer,
5
+ role: claims.role,
6
+ scopes: [...claims.scopes],
7
+ issuedAt: claims.issuedAt,
8
+ ...(claims.expiresAt ? { expiresAt: claims.expiresAt } : {}),
9
+ ...(claims.workspaceId ? { workspaceId: claims.workspaceId } : {}),
10
+ ...(claims.sessionId ? { sessionId: claims.sessionId } : {}),
11
+ ...(claims.metadata ? { metadata: { ...claims.metadata } } : {})
12
+ };
13
+ }
14
+ export function cloneIdentityToken(token) {
15
+ return {
16
+ schemaVersion: token.schemaVersion,
17
+ algorithm: token.algorithm,
18
+ keyId: token.keyId,
19
+ claims: cloneIdentityClaims(token.claims),
20
+ signature: token.signature
21
+ };
22
+ }
23
+ export function cloneIdentityAttestation(attestation) {
24
+ return {
25
+ valid: attestation.valid,
26
+ verifiedAt: attestation.verifiedAt,
27
+ reason: attestation.reason,
28
+ token: cloneIdentityToken(attestation.token)
29
+ };
30
+ }