@martinloop/mcp 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +137 -137
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +2 -0
  4. package/dist/prompts.js +19 -1
  5. package/dist/resources.d.ts +3 -0
  6. package/dist/resources.js +125 -17
  7. package/dist/server-validation.d.ts +1 -1
  8. package/dist/server-validation.js +12 -0
  9. package/dist/server.js +18 -7
  10. package/dist/tools/doctor.js +6 -0
  11. package/dist/tools/run-loop.d.ts +2 -1
  12. package/dist/tools/run-loop.js +5 -1
  13. package/dist/tools/tool-support.d.ts +1 -1
  14. package/dist/tools/tool-support.js +2 -1
  15. package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +146 -0
  16. package/dist/vendor/adapters/adapters/src/claude-cli.js +1100 -0
  17. package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +103 -0
  18. package/dist/vendor/adapters/adapters/src/cli-bridge.js +569 -0
  19. package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +78 -0
  20. package/dist/vendor/adapters/adapters/src/codex-launcher.js +663 -0
  21. package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +10 -0
  22. package/dist/vendor/adapters/adapters/src/direct-provider.js +40 -0
  23. package/dist/vendor/adapters/adapters/src/index.d.ts +8 -0
  24. package/dist/vendor/adapters/adapters/src/index.js +8 -0
  25. package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +62 -0
  26. package/dist/vendor/adapters/adapters/src/openai-compatible.js +306 -0
  27. package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +17 -0
  28. package/dist/vendor/adapters/adapters/src/runtime-support.js +59 -0
  29. package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +8 -0
  30. package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +40 -0
  31. package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +8 -0
  32. package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +12 -0
  33. package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +9 -0
  34. package/dist/vendor/adapters/adapters/src/verifier-only.js +63 -0
  35. package/dist/vendor/adapters/claude-cli.js +21 -20
  36. package/dist/vendor/adapters/cli-bridge.d.ts +7 -0
  37. package/dist/vendor/adapters/cli-bridge.js +94 -8
  38. package/dist/vendor/adapters/codex-launcher.js +18 -7
  39. package/dist/vendor/adapters/contracts/src/context-graph.d.ts +48 -0
  40. package/dist/vendor/adapters/contracts/src/context-graph.js +26 -0
  41. package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +78 -0
  42. package/dist/vendor/adapters/contracts/src/execution-policy.js +43 -0
  43. package/dist/vendor/adapters/contracts/src/governance.d.ts +24 -0
  44. package/dist/vendor/adapters/contracts/src/governance.js +11 -0
  45. package/dist/vendor/adapters/contracts/src/identity.d.ts +29 -0
  46. package/dist/vendor/adapters/contracts/src/identity.js +30 -0
  47. package/dist/vendor/adapters/contracts/src/index.d.ts +412 -0
  48. package/dist/vendor/adapters/contracts/src/index.js +225 -0
  49. package/dist/vendor/adapters/contracts/src/operator.d.ts +19 -0
  50. package/dist/vendor/adapters/contracts/src/operator.js +11 -0
  51. package/dist/vendor/adapters/contracts/src/trajectory.d.ts +26 -0
  52. package/dist/vendor/adapters/contracts/src/trajectory.js +27 -0
  53. package/dist/vendor/adapters/core/src/compiler.d.ts +52 -0
  54. package/dist/vendor/adapters/core/src/compiler.js +52 -0
  55. package/dist/vendor/adapters/core/src/context-graph.d.ts +3 -0
  56. package/dist/vendor/adapters/core/src/context-graph.js +371 -0
  57. package/dist/vendor/adapters/core/src/context-integrity.d.ts +26 -0
  58. package/dist/vendor/adapters/core/src/context-integrity.js +80 -0
  59. package/dist/vendor/adapters/core/src/grounding.d.ts +38 -0
  60. package/dist/vendor/adapters/core/src/grounding.js +273 -0
  61. package/dist/vendor/adapters/core/src/identity.d.ts +20 -0
  62. package/dist/vendor/adapters/core/src/identity.js +86 -0
  63. package/dist/vendor/adapters/core/src/index.d.ts +186 -0
  64. package/dist/vendor/adapters/core/src/index.js +1495 -0
  65. package/dist/vendor/adapters/core/src/leash.d.ts +48 -0
  66. package/dist/vendor/adapters/core/src/leash.js +497 -0
  67. package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +18 -0
  68. package/dist/vendor/adapters/core/src/persistence/compiler.js +34 -0
  69. package/dist/vendor/adapters/core/src/persistence/index.d.ts +10 -0
  70. package/dist/vendor/adapters/core/src/persistence/index.js +5 -0
  71. package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +39 -0
  72. package/dist/vendor/adapters/core/src/persistence/integrity.js +252 -0
  73. package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +23 -0
  74. package/dist/vendor/adapters/core/src/persistence/ledger.js +9 -0
  75. package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +63 -0
  76. package/dist/vendor/adapters/core/src/persistence/memory-store.js +115 -0
  77. package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +66 -0
  78. package/dist/vendor/adapters/core/src/persistence/runs-reader.js +140 -0
  79. package/dist/vendor/adapters/core/src/persistence/store.d.ts +89 -0
  80. package/dist/vendor/adapters/core/src/persistence/store.js +125 -0
  81. package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +85 -0
  82. package/dist/vendor/adapters/core/src/persistence/trace-store.js +125 -0
  83. package/dist/vendor/adapters/core/src/policy-compiler.d.ts +2 -0
  84. package/dist/vendor/adapters/core/src/policy-compiler.js +195 -0
  85. package/dist/vendor/adapters/core/src/policy.d.ts +156 -0
  86. package/dist/vendor/adapters/core/src/policy.js +704 -0
  87. package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +53 -0
  88. package/dist/vendor/adapters/core/src/repo-analyzer.js +263 -0
  89. package/dist/vendor/adapters/core/src/rollback.d.ts +15 -0
  90. package/dist/vendor/adapters/core/src/rollback.js +253 -0
  91. package/dist/vendor/adapters/core/src/routing.d.ts +82 -0
  92. package/dist/vendor/adapters/core/src/routing.js +336 -0
  93. package/dist/vendor/adapters/core/src/test-integrity.d.ts +64 -0
  94. package/dist/vendor/adapters/core/src/test-integrity.js +197 -0
  95. package/dist/vendor/adapters/core/src/trajectory.d.ts +18 -0
  96. package/dist/vendor/adapters/core/src/trajectory.js +295 -0
  97. package/dist/vendor/adapters/openai-compatible.js +84 -51
  98. package/dist/vendor/adapters/verifier-only.js +2 -2
  99. package/dist/vendor/contracts/index.d.ts +2 -2
  100. package/dist/vendor/contracts/index.js +1 -0
  101. package/dist/vendor/core/compiler.js +4 -9
  102. package/dist/vendor/core/index.d.ts +10 -2
  103. package/dist/vendor/core/index.js +32 -91
  104. package/dist/vendor/core/persistence/memory-store.d.ts +63 -0
  105. package/dist/vendor/core/persistence/memory-store.js +115 -0
  106. package/dist/vendor/core/persistence/trace-store.d.ts +85 -0
  107. package/dist/vendor/core/persistence/trace-store.js +125 -0
  108. package/dist/vendor/core/repo-analyzer.d.ts +53 -0
  109. package/dist/vendor/core/repo-analyzer.js +263 -0
  110. package/dist/vendor/core/rollback.js +2 -3
  111. package/dist/vendor/core/routing.d.ts +42 -0
  112. package/dist/vendor/core/routing.js +156 -5
  113. package/dist/vendor/core/test-integrity.d.ts +64 -0
  114. package/dist/vendor/core/test-integrity.js +197 -0
  115. package/dist/workflow-state.d.ts +1 -1
  116. package/dist/workflow-state.js +12 -4
  117. package/package.json +1 -1
  118. package/server.json +2 -2
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Test Integrity Validator
3
+ *
4
+ * Detects circular test validation — the problem where AI agents generate
5
+ * tests that only pass because they test the agent's own output, not real
6
+ * behavior. A test that passes on both the pre-change and post-change
7
+ * codebase proves nothing about the change.
8
+ *
9
+ * Strategy:
10
+ * 1. Snapshot which test files existed before the agent ran
11
+ * 2. After the run, identify NEW test files the agent created
12
+ * 3. For each new test: check if it imports/references pre-existing code
13
+ * (real test) or only references agent-generated code (circular test)
14
+ * 4. Flag tests that don't exercise any pre-existing function
15
+ *
16
+ * This module provides the analysis primitives. The verification pipeline
17
+ * calls these during the post-run verification step.
18
+ */
19
+ import { readFileSync, readdirSync, existsSync } from "node:fs";
20
+ import { join, relative } from "node:path";
21
+ const TEST_FILE_PATTERNS = [
22
+ /\.test\.[tj]sx?$/,
23
+ /\.spec\.[tj]sx?$/,
24
+ /\/__tests__\//,
25
+ /\/tests?\//
26
+ ];
27
+ const TRIVIAL_ASSERTION_PATTERNS = [
28
+ /expect\(true\)\.toBe\(true\)/,
29
+ /expect\(1\)\.toBe\(1\)/,
30
+ /expect\(\)\.toBeUndefined\(\)/,
31
+ /assert\.ok\(true\)/,
32
+ /expect\(.*\)\.toBeDefined\(\)/,
33
+ /expect\(typeof\s+\w+\)\.toBe\(['"]\w+['"]\)/
34
+ ];
35
+ const IMPORT_PATTERN = /(?:import\s+.*from\s+['"]([^'"]+)['"]|require\s*\(\s*['"]([^'"]+)['"]\s*\))/g;
36
+ /**
37
+ * Detect test files in a directory tree.
38
+ */
39
+ export function findTestFiles(rootDir) {
40
+ const results = [];
41
+ function walk(dir) {
42
+ if (!existsSync(dir))
43
+ return;
44
+ try {
45
+ const entries = readdirSync(dir, { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ const fullPath = join(dir, entry.name);
48
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git")
49
+ continue;
50
+ if (entry.isDirectory()) {
51
+ walk(fullPath);
52
+ }
53
+ else if (entry.isFile() && isTestFile(entry.name, fullPath)) {
54
+ results.push(relative(rootDir, fullPath));
55
+ }
56
+ }
57
+ }
58
+ catch {
59
+ // Permission denied or inaccessible — skip silently
60
+ }
61
+ }
62
+ walk(rootDir);
63
+ return results;
64
+ }
65
+ /**
66
+ * Check if a filename matches test file patterns.
67
+ */
68
+ export function isTestFile(filename, fullPath) {
69
+ const checkPath = fullPath ?? filename;
70
+ return TEST_FILE_PATTERNS.some((pattern) => pattern.test(checkPath));
71
+ }
72
+ /**
73
+ * Snapshot existing test files before an agent run.
74
+ */
75
+ export function snapshotTestFiles(rootDir) {
76
+ return new Set(findTestFiles(rootDir));
77
+ }
78
+ /**
79
+ * Identify new test files created during an agent run.
80
+ */
81
+ export function identifyNewTests(rootDir, preRunSnapshot) {
82
+ const currentTests = findTestFiles(rootDir);
83
+ return currentTests.filter((testPath) => !preRunSnapshot.has(testPath));
84
+ }
85
+ /**
86
+ * Extract import paths from a TypeScript/JavaScript file.
87
+ */
88
+ export function extractImports(content) {
89
+ const imports = [];
90
+ let match;
91
+ const pattern = new RegExp(IMPORT_PATTERN.source, "g");
92
+ while ((match = pattern.exec(content)) !== null) {
93
+ const importPath = match[1] ?? match[2];
94
+ if (importPath)
95
+ imports.push(importPath);
96
+ }
97
+ return imports;
98
+ }
99
+ /**
100
+ * Check if a test file contains only trivial assertions.
101
+ */
102
+ export function hasTrivialAssertionsOnly(content) {
103
+ const assertionLines = content.split("\n").filter((line) => /expect\(|assert\.|should\./i.test(line));
104
+ if (assertionLines.length === 0)
105
+ return true;
106
+ return assertionLines.every((line) => TRIVIAL_ASSERTION_PATTERNS.some((pattern) => pattern.test(line)));
107
+ }
108
+ /**
109
+ * Check if a test file imports any pre-existing source files.
110
+ */
111
+ export function importsPreExistingCode(testContent, preRunSourceFiles) {
112
+ const imports = extractImports(testContent);
113
+ return imports.some((importPath) => {
114
+ // Relative imports that reference pre-existing files
115
+ if (importPath.startsWith(".") || importPath.startsWith("/")) {
116
+ // Normalize: strip extension, check if any pre-existing source matches
117
+ const normalized = importPath.replace(/\.[tj]sx?$/, "");
118
+ return [...preRunSourceFiles].some((srcFile) => {
119
+ const normalizedSrc = srcFile.replace(/\.[tj]sx?$/, "");
120
+ return normalizedSrc.endsWith(normalized) || normalized.endsWith(normalizedSrc);
121
+ });
122
+ }
123
+ // Package imports always count as real (they reference pre-existing deps)
124
+ return true;
125
+ });
126
+ }
127
+ /**
128
+ * Validate the integrity of tests created during a governed run.
129
+ */
130
+ export function validateTestIntegrity(rootDir, preRunTestSnapshot, preRunSourceFiles) {
131
+ const newTests = identifyNewTests(rootDir, preRunTestSnapshot);
132
+ const findings = [];
133
+ for (const testPath of newTests) {
134
+ const fullPath = join(rootDir, testPath);
135
+ let content;
136
+ try {
137
+ content = readFileSync(fullPath, "utf8");
138
+ }
139
+ catch {
140
+ findings.push({
141
+ filePath: testPath,
142
+ issue: "empty_test",
143
+ severity: "high",
144
+ detail: "Test file could not be read."
145
+ });
146
+ continue;
147
+ }
148
+ if (content.trim().length === 0) {
149
+ findings.push({
150
+ filePath: testPath,
151
+ issue: "empty_test",
152
+ severity: "high",
153
+ detail: "Test file is empty."
154
+ });
155
+ continue;
156
+ }
157
+ if (hasTrivialAssertionsOnly(content)) {
158
+ findings.push({
159
+ filePath: testPath,
160
+ issue: "trivial_assertion",
161
+ severity: "high",
162
+ detail: "Test contains only trivial assertions (expect(true).toBe(true) or equivalent)."
163
+ });
164
+ continue;
165
+ }
166
+ if (!importsPreExistingCode(content, preRunSourceFiles)) {
167
+ findings.push({
168
+ filePath: testPath,
169
+ issue: "no_pre_existing_imports",
170
+ severity: "medium",
171
+ detail: "Test does not import any pre-existing source code. May be testing only agent-generated output."
172
+ });
173
+ }
174
+ }
175
+ const suspiciousTests = findings.filter((f) => f.severity === "high").length;
176
+ const realTests = newTests.length - suspiciousTests;
177
+ const verdict = suspiciousTests > 0 && suspiciousTests >= newTests.length * 0.5
178
+ ? "fail"
179
+ : suspiciousTests > 0
180
+ ? "warn"
181
+ : "pass";
182
+ const summary = newTests.length === 0
183
+ ? "No new test files detected."
184
+ : verdict === "pass"
185
+ ? `${newTests.length} new test(s) validated — all reference pre-existing code.`
186
+ : verdict === "warn"
187
+ ? `${suspiciousTests} of ${newTests.length} new test(s) flagged for review.`
188
+ : `Test integrity check failed: ${suspiciousTests} of ${newTests.length} new test(s) appear circular or trivial.`;
189
+ return {
190
+ totalNewTests: newTests.length,
191
+ realTests,
192
+ suspiciousTests,
193
+ findings,
194
+ verdict,
195
+ summary
196
+ };
197
+ }
@@ -1,5 +1,5 @@
1
1
  import type { LoopBudget, ReceiptScope } from "./vendor/contracts/index.js";
2
- type McpWorkflowStepName = "doctor" | "plan" | "preflight";
2
+ type McpWorkflowStepName = "doctor" | "plan" | "preflight" | "estimate" | "start";
3
3
  interface McpWorkflowReceipt {
4
4
  step: McpWorkflowStepName;
5
5
  recordedAt: string;
@@ -4,6 +4,7 @@ import { join, resolve } from "node:path";
4
4
  const WORKFLOW_STATE_DIRECTORY = "_martin";
5
5
  const WORKFLOW_STATE_FILENAME = "workflow-state.json";
6
6
  const DOCTOR_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const ESTIMATE_TTL_MS = 6 * 60 * 60 * 1000; // Estimate expires after 6h — re-estimate if objective changes
7
8
  const PLAN_TTL_MS = 24 * 60 * 60 * 1000;
8
9
  const PREFLIGHT_TTL_MS = 6 * 60 * 60 * 1000;
9
10
  export async function recordMcpWorkflowStep(input) {
@@ -42,6 +43,11 @@ export async function evaluateMcpRunGate(input) {
42
43
  receipt.scopeKey === scopeKey)) {
43
44
  missingSteps.push("doctor");
44
45
  }
46
+ // Estimate is required before any run — it proves the agent assessed cost first.
47
+ // The estimate TTL is 6h; it doesn't need to match the exact objective key.
48
+ if (!isFresh(mcpState["estimate"], ESTIMATE_TTL_MS, (receipt) => receipt.workingDirectory === workingDirectory)) {
49
+ missingSteps.push("estimate");
50
+ }
45
51
  if (!isFresh(mcpState["plan"], PLAN_TTL_MS, (receipt) => receipt.workingDirectory === workingDirectory &&
46
52
  receipt.scopeKey === scopeKey &&
47
53
  receipt.objectiveKey === objectiveKey)) {
@@ -65,10 +71,12 @@ export async function evaluateMcpRunGate(input) {
65
71
  };
66
72
  }
67
73
  const nextAction = missingSteps[0] === "doctor"
68
- ? "Call martin_doctor for this workingDirectory before any real run."
69
- : missingSteps[0] === "plan"
70
- ? "Call martin_plan with the exact objective before martin_run."
71
- : "Call martin_preflight with the exact objective, verifier plan, and engine before martin_run.";
74
+ ? "Call martin_doctor to confirm environment before any work."
75
+ : missingSteps[0] === "estimate"
76
+ ? "Call martin_estimate with the objective to get cost and route before spending."
77
+ : missingSteps[0] === "plan"
78
+ ? "Call martin_plan with the exact objective before martin_run."
79
+ : "Call martin_preflight with the exact objective, verifier plan, and engine before martin_run.";
72
80
  return {
73
81
  allowed: false,
74
82
  nextAction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martinloop/mcp",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "mcpName": "io.github.Keesan12/martin-loop",
5
5
  "private": false,
6
6
  "type": "module",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "url": "https://github.com/Keesan12/martin-loop",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.3.5",
10
+ "version": "0.3.7",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "@martinloop/mcp",
15
- "version": "0.3.5",
15
+ "version": "0.3.7",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  }